javascript - body - tooltip bootstrap 4




Impedir a rolagem do elemento pai quando a posição de rolagem do elemento interno atinge a parte superior/inferior? (20)

Diretiva Angular JS

Eu tive que embrulhar uma diretiva angular. O seguinte é um Mashup das outras respostas aqui. testado no Chrome e no Internet Explorer 11.

var app = angular.module('myApp');

app.directive("preventParentScroll", function () {
    return {
        restrict: "A",
        scope: false,
        link: function (scope, elm, attr) {
            elm.bind('mousewheel', onMouseWheel);
            function onMouseWheel(e) {
                elm[0].scrollTop -= (e.wheelDeltaY || (e.originalEvent && (e.originalEvent.wheelDeltaY || e.originalEvent.wheelDelta)) || e.wheelDelta || 0);
                e.stopPropagation();
                e.preventDefault();
                e.returnValue = false;
            }
        }
    }
});

Uso

<div prevent-parent-scroll>
    ...
</div>

Espera que isso ajude a próxima pessoa que chegar aqui a partir de uma pesquisa no Google.

Esta questão já tem uma resposta aqui:

Eu tenho uma pequena "caixa de ferramentas flutuante" - uma div com position:fixed; overflow:auto position:fixed; overflow:auto . Funciona muito bem.

Mas quando rolando dentro da caixa (com a roda do mouse) e alcançando a parte inferior OU superior, o elemento pai "assume" a "solicitação de rolagem": O documento atrás da caixa de ferramentas rola.
- Que é chato e não o que o usuário "pediu".

Estou usando o jQuery e pensei que poderia parar esse comportamento com event.stoppropagation ():
$("#toolBox").scroll( function(event){ event.stoppropagation() });

Ele entra na função, mas ainda assim, a propagação acontece de qualquer maneira (o documento rola)
- É surpreendentemente difícil procurar por esse tópico no SO (e no Google), então eu tenho que perguntar:
Como evitar a propagação / subida do evento de rolagem?

Editar:
Solução de trabalho graças a amustill (e Brandon Aaron para o plugin mousewheel aqui:
https://github.com/brandonaaron/jquery-mousewheel/raw/master/jquery.mousewheel.js

$(".ToolPage").bind('mousewheel', function(e, d)  
    var t = $(this);
    if (d > 0 && t.scrollTop() === 0) {
        e.preventDefault();
    }
    else {
        if (d < 0 && (t.scrollTop() == t.get(0).scrollHeight - t.innerHeight())) {
            e.preventDefault();
        }
    }
});


Aqui está uma versão simples de JavaScript:

function scroll(e) {
  var delta = (e.type === "mousewheel") ? e.wheelDelta : e.detail * -40;
  if (delta < 0 && (this.scrollHeight - this.offsetHeight - this.scrollTop) <= 0) {
    this.scrollTop = this.scrollHeight;
    e.preventDefault();
  } else if (delta > 0 && delta > this.scrollTop) {
    this.scrollTop = 0;
    e.preventDefault();
  }
}
document.querySelectorAll(".scroller").addEventListener("mousewheel", scroll);
document.querySelectorAll(".scroller").addEventListener("DOMMouseScroll", scroll);

Caso alguém ainda esteja procurando uma solução para isso, o seguinte plugin faz o trabalho http://mohammadyounes.github.io/jquery-scrollLock/

Aborda totalmente a questão de bloquear a rolagem da roda do mouse dentro de um determinado contêiner, impedindo que ela se propague para o elemento pai.

Não altera a velocidade de rolagem da roda, a experiência do usuário não será afetada. e você obtém o mesmo comportamento, independentemente da velocidade de rolagem vertical da roda do mouse do SO (no Windows, ela pode ser definida como uma tela ou uma linha até 100 linhas por corte).

Demonstração: http://mohammadyounes.github.io/jquery-scrollLock/example/

Fonte: https://github.com/MohammadYounes/jquery-scrollLock


EDIT: exemplo CodePen

Para o AngularJS, defini a seguinte diretiva:

module.directive('isolateScrolling', function () {
  return {
    restrict: 'A',
      link: function (scope, element, attr) {
        element.bind('DOMMouseScroll', function (e) {
          if (e.detail > 0 && this.clientHeight + this.scrollTop == this.scrollHeight) {
            this.scrollTop = this.scrollHeight - this.clientHeight;
            e.stopPropagation();
            e.preventDefault();
            return false;
          }
          else if (e.detail < 0 && this.scrollTop <= 0) {
            this.scrollTop = 0;
            e.stopPropagation();
            e.preventDefault();
            return false;
          }
        });
        element.bind('mousewheel', function (e) {
          if (e.deltaY > 0 && this.clientHeight + this.scrollTop >= this.scrollHeight) {
            this.scrollTop = this.scrollHeight - this.clientHeight;
            e.stopPropagation();
            e.preventDefault();
            return false;
          }
          else if (e.deltaY < 0 && this.scrollTop <= 0) {
            this.scrollTop = 0;
            e.stopPropagation();
            e.preventDefault();
            return false;
          }

          return true;
        });
      }
  };
});

E, em seguida, adicionou-o ao elemento rolável (o menu suspenso ul):

<div class="dropdown">
  <button type="button" class="btn dropdown-toggle">Rename <span class="caret"></span></button>
  <ul class="dropdown-menu" isolate-scrolling>
    <li ng-repeat="s in savedSettings | objectToArray | orderBy:'name' track by s.name">
      <a ng-click="renameSettings(s.name)">{{s.name}}</a>
    </li>
  </ul>
</div>

Testado no Chrome e no Firefox. A rolagem suave do Chrome derrota esse truque quando um grande movimento do mouse é feito próximo (mas não em) da parte superior ou inferior da região de rolagem.


Estou adicionando essa resposta para integridade porque a resposta aceita pelo @amustill não resolve corretamente o problema no Internet Explorer . Por favor, veja os comentários em minha postagem original para detalhes. Além disso, esta solução não requer nenhum plug-in - apenas o jQuery.

Em essência, o código funciona manipulando o evento mousewheel . Cada evento desse tipo contém um wheelDelta igual ao número de px qual ele moverá a área rolável. Se este valor for >0 , então estamos rolando para up . Se o wheelDelta for <0 então estamos rolando down .

FireFox : O FireFox usa DOMMouseScroll como o evento e preenche o originalEvent.detail , cujo +/- é revertido do que é descrito acima. Geralmente, ele retorna intervalos de 3 , enquanto outros navegadores retornam a rolagem em intervalos de 120 (pelo menos na minha máquina). Para corrigir, simplesmente detectamos e multiplicamos por -40 para normalizar.

A resposta de @ amustill funciona cancelando o evento se a área rolável de <div> já estiver na posição máxima superior ou inferior. No entanto, o Internet Explorer desconsidera o evento cancelado em situações em que o delta é maior que o espaço restante rolável.

Em outras palavras, se você tiver um 500px de 200px <div> contendo 500px de conteúdo com rolagem, e o scrollTop atual for 400 , um evento mousewheel que 120px o navegador a percorrer 120px mais resultará no <div> e no <body> rolagem, porque 400 + 120 > 500 .

Então - para resolver o problema, temos que fazer algo um pouco diferente, como mostrado abaixo:

O código necessário do jQuery é:

$(document).on('DOMMouseScroll mousewheel', '.Scrollable', function(ev) {
    var $this = $(this),
        scrollTop = this.scrollTop,
        scrollHeight = this.scrollHeight,
        height = $this.innerHeight(),
        delta = (ev.type == 'DOMMouseScroll' ?
            ev.originalEvent.detail * -40 :
            ev.originalEvent.wheelDelta),
        up = delta > 0;

    var prevent = function() {
        ev.stopPropagation();
        ev.preventDefault();
        ev.returnValue = false;
        return false;
    }

    if (!up && -delta > scrollHeight - height - scrollTop) {
        // Scrolling down, but this will take us past the bottom.
        $this.scrollTop(scrollHeight);
        return prevent();
    } else if (up && delta > scrollTop) {
        // Scrolling up, but this will take us past the top.
        $this.scrollTop(0);
        return prevent();
    }
});

Em essência, esse código cancela qualquer evento de rolagem que criaria a condição de borda indesejada, em seguida, usa jQuery para definir o scrollTop do <div> para o valor máximo ou mínimo, dependendo de qual direção o evento mousewheel estava solicitando.

Como o evento é cancelado inteiramente em qualquer um dos casos, ele nunca se propaga para o body e, portanto, resolve o problema no IE, bem como em todos os outros navegadores.

Eu também coloquei um exemplo de trabalho no jsFiddle .


Eu tenho uma situação semelhante e aqui está como eu resolvi isso:
Todos os meus elementos roláveis ​​obtêm a classe rolável .

$(document).on('wheel', '.scrollable', function(evt) {
  var offsetTop = this.scrollTop + parseInt(evt.originalEvent.deltaY, 10);
  var offsetBottom = this.scrollHeight - this.getBoundingClientRect().height - offsetTop;

  if (offsetTop < 0 || offsetBottom < 0) {
    evt.preventDefault();
  } else {
    evt.stopImmediatePropagation();
  }
});

stopImmediatePropagation () certifica-se de não rolar a área de rolagem pai da área filha rolável.

Aqui está uma implementação JS vanilla dele: http://jsbin.com/lugim/2/edit?js,output


Há toneladas de perguntas como essa por aí, com muitas respostas, mas não consegui encontrar uma solução satisfatória que não envolvesse eventos, scripts, plugins, etc. Eu queria manter isso em HTML e CSS. Eu finalmente encontrei uma solução que funcionou, embora envolvesse a reestruturação da marcação para quebrar a cadeia de eventos.

1. problema básico

A entrada de rolagem (por exemplo: mousewheel) aplicada ao elemento modal entrará em um elemento ancestral e rolará na mesma direção, se algum desses elementos for rolável:

(Todos os exemplos devem ser visualizados em resoluções de desktop)

https://jsfiddle.net/ybkbg26c/5/

HTML:

<div id="parent">
  <div id="modal">
    This text is pretty long here.  Hope fully, we will get some scroll bars.
  </div>
</div>

CSS:

#modal {
  position: absolute;
  height: 100px;
  width: 100px;
  top: 20%;
  left: 20%;
  overflow-y: scroll;
}
#parent {
  height: 4000px;
}

2. Nenhum pergaminho pai na rolagem modal

A razão pela qual o ancestral acaba rolando é porque as bolhas de evento de rolagem e algum elemento na cadeia são capazes de manipulá-lo. Uma maneira de impedir isso é certificar-se de que nenhum dos elementos da cadeia saiba como lidar com o pergaminho. Em termos do nosso exemplo, podemos refatorar a árvore para mover o modal para fora do elemento pai. Por motivos obscuros, não é suficiente manter o pai e os irmãos DOM modais; o pai deve ser empacotado por outro elemento que estabeleça um novo contexto de empilhamento. Um wrapper absolutamente posicionado em torno do pai pode fazer o truque.

O resultado que obtemos é que, desde que o modal receba o evento de rolagem, o evento não será enviado para o elemento "pai".

Normalmente deve ser possível redesenhar a árvore DOM para suportar esse comportamento sem afetar o que o usuário final vê.

https://jsfiddle.net/0bqq31Lv/3/

HTML:

<div id="context">
  <div id="parent">
  </div>
</div>
<div id="modal">
  This text is pretty long here.  Hope fully, we will get some scroll bars.
</div>

CSS (apenas novo):

#context {
  position: absolute;
  overflow-y: scroll;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
}

3. Não rolar em qualquer lugar, exceto no modal enquanto estiver

A solução acima ainda permite que o pai receba eventos de rolagem, desde que eles não sejam interceptados pela janela modal (ou seja, se acionado por mousewheel enquanto o cursor não estiver sobre o modal). Isso às vezes é indesejável e podemos querer proibir a rolagem em segundo plano enquanto o modal estiver ativo. Para fazer isso, precisamos inserir um contexto de empilhamento extra que abranja toda a viewport por trás do modal. Podemos fazer isso exibindo uma sobreposição absolutamente posicionada, que pode ser totalmente transparente, se necessário (mas não a visibility:hidden ).

https://jsfiddle.net/0bqq31Lv/2/

HTML:

<div id="context">
  <div id="parent">
  </div>
</div>
<div id="overlay">  
</div>
<div id="modal">
  This text is pretty long here.  Hope fully, we will get some scroll bars.
</div>

CSS (novo no topo de # 2):

#overlay {
  background-color: transparent;
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
}

Novo web dev aqui. Isso funcionou como um charme para mim no IE e no Chrome.

static preventScrollPropagation(e: HTMLElement) {
    e.onmousewheel = (ev) => {
        var preventScroll = false;
        var isScrollingDown = ev.wheelDelta < 0;
        if (isScrollingDown) {
            var isAtBottom = e.scrollTop + e.clientHeight == e.scrollHeight;
            if (isAtBottom) {
                preventScroll = true;
            }
        } else {
            var isAtTop = e.scrollTop == 0;
            if (isAtTop) {
                preventScroll = true;
            }
        }
        if (preventScroll) {
            ev.preventDefault();
        }
    }
}

Não deixe o número de linhas enganá-lo, é bastante simples - apenas um pouco detalhado para a legibilidade (auto documentando código ftw certo?)

Também devo mencionar que a linguagem aqui é TypeScript , mas como sempre, é fácil convertê-lo em JS.


Para aqueles que usam o MooTools, aqui está o código equivalente:

            'mousewheel': function(event){
            var height = this.getSize().y;
            height -= 2;    // Not sure why I need this bodge
            if ((this.scrollTop === (this.scrollHeight - height) && event.wheel < 0) || 
                (this.scrollTop === 0 && event.wheel > 0)) {
                event.preventDefault();
            }

Tenha em mente que eu, como alguns outros, tive que ajustar um valor por um par de px, que é o que a altura - = 2 é para.

Basicamente a principal diferença é que no MooTools, a informação delta vem de event.wheel ao invés de um parâmetro extra passado para o evento.

Além disso, tive problemas se eu vinculasse esse código a qualquer coisa (event.target.scrollHeight para uma função ligada não é igual a this.scrollHeight para uma não vinculada)

Espero que isso ajude alguém tanto quanto este post me ajudou;)


Usando as propriedades de rolagem do elemento nativo com o valor delta do plug-in mousewheel:

$elem.on('mousewheel', function (e, delta) {
    // Restricts mouse scrolling to the scrolling range of this element.
    if (
        this.scrollTop < 1 && delta > 0 ||
        (this.clientHeight + this.scrollTop) === this.scrollHeight && delta < 0
    ) {
        e.preventDefault();
    }
});

o método acima não é tão natural, depois de algum googling eu acho uma solução mais legal, e não há necessidade de jQuery. veja [1] e demonstração [2].

  var element = document.getElementById('uf-notice-ul');

  var isMacWebkit = (navigator.userAgent.indexOf("Macintosh") !== -1 &&
    navigator.userAgent.indexOf("WebKit") !== -1);
  var isFirefox = (navigator.userAgent.indexOf("firefox") !== -1);

  element.onwheel = wheelHandler; // Future browsers
  element.onmousewheel = wheelHandler; // Most current browsers
  if (isFirefox) {
    element.scrollTop = 0;
    element.addEventListener("DOMMouseScroll", wheelHandler, false);
  }
  // prevent from scrolling parrent elements
  function wheelHandler(event) {
    var e = event || window.event; // Standard or IE event object

    // Extract the amount of rotation from the event object, looking
    // for properties of a wheel event object, a mousewheel event object 
    // (in both its 2D and 1D forms), and the Firefox DOMMouseScroll event.
    // Scale the deltas so that one "click" toward the screen is 30 pixels.
    // If future browsers fire both "wheel" and "mousewheel" for the same
    // event, we'll end up double-counting it here. Hopefully, however,
    // cancelling the wheel event will prevent generation of mousewheel.
    var deltaX = e.deltaX * -30 || // wheel event
      e.wheelDeltaX / 4 || // mousewheel
      0; // property not defined
    var deltaY = e.deltaY * -30 || // wheel event
      e.wheelDeltaY / 4 || // mousewheel event in Webkit
      (e.wheelDeltaY === undefined && // if there is no 2D property then 
        e.wheelDelta / 4) || // use the 1D wheel property
      e.detail * -10 || // Firefox DOMMouseScroll event
      0; // property not defined

    // Most browsers generate one event with delta 120 per mousewheel click.
    // On Macs, however, the mousewheels seem to be velocity-sensitive and
    // the delta values are often larger multiples of 120, at 
    // least with the Apple Mouse. Use browser-testing to defeat this.
    if (isMacWebkit) {
      deltaX /= 30;
      deltaY /= 30;
    }
    e.currentTarget.scrollTop -= deltaY;
    // If we ever get a mousewheel or wheel event in (a future version of)
    // Firefox, then we don't need DOMMouseScroll anymore.
    if (isFirefox && e.type !== "DOMMouseScroll") {
      element.removeEventListener("DOMMouseScroll", wheelHandler, false);
    }
    // Don't let this event bubble. Prevent any default action.
    // This stops the browser from using the mousewheel event to scroll
    // the document. Hopefully calling preventDefault() on a wheel event
    // will also prevent the generation of a mousewheel event for the
    // same rotation.
    if (e.preventDefault) e.preventDefault();
    if (e.stopPropagation) e.stopPropagation();
    e.cancelBubble = true; // IE events
    e.returnValue = false; // IE events
    return false;
  }

[1] https://dimakuzmich.wordpress.com/2013/07/16/prevent-scrolling-of-parent-element-with-javascript/

[2] http://jsfiddle.net/dima_k/5mPkB/1/


A melhor solução que pude encontrar foi ouvir o evento de rolagem na janela e definir o scrollTop como o scrollTop anterior se o div filho estivesse visível.

prevScrollPos = 0
$(window).scroll (ev) ->
    if $('#mydiv').is(':visible')
        document.body.scrollTop = prevScrollPos
    else
        prevScrollPos = document.body.scrollTop

Há um piscar de olhos no plano de fundo da div infantil se você disparar muitos eventos de rolagem, portanto, isso pode ser ajustado, mas dificilmente é percebido e é suficiente para o meu caso de uso.


Há ES crossbrowser ES + decisão vanila js móvel:

function stopParentScroll(selector) {
    let last_touch;
    let MouseWheelHandler = (e, selector) => {
        let delta;
        if(e.deltaY)
            delta = e.deltaY;
        else if(e.wheelDelta)
            delta = e.wheelDelta;
        else if(e.changedTouches){
            if(!last_touch){
                last_touch = e.changedTouches[0].clientY;
            }
            else{
                if(e.changedTouches[0].clientY > last_touch){
                    delta = -1;
                }
                else{
                    delta = 1;
                }
            }
        }
        let prevent = function() {
            e.stopPropagation();
            e.preventDefault();
            e.returnValue = false;
            return false;
        };

        if(selector.scrollTop === 0 && delta < 0){
            return prevent();
        }
        else if(selector.scrollTop === (selector.scrollHeight - selector.clientHeight) && delta > 0){
            return prevent();
        }
    };

    selector.onwheel = e => {MouseWheelHandler(e, selector)}; 
    selector.onmousewheel = e => {MouseWheelHandler(e, selector)}; 
    selector.ontouchmove  = e => {MouseWheelHandler(e, selector)};
}

Solução simples com evento mouseweel:

$('.element').bind('mousewheel', function(e, d) {
    console.log(this.scrollTop,this.scrollHeight,this.offsetHeight,d);
    if((this.scrollTop === (this.scrollHeight - this.offsetHeight) && d < 0)
        || (this.scrollTop === 0 && d > 0)) {
        e.preventDefault();
    }
});

Você pode tentar assim:

$('#element').on('shown', function(){ 
   $('body').css('overflow-y', 'hidden');
   $('body').css('margin-left', '-17px');
});

$('#element').on('hide', function(){ 
   $('body').css('overflow-y', 'scroll');
   $('body').css('margin-left', '0px');
});

Confira o código de Leland Kwong .

A idéia básica é vincular o evento wheeling ao elemento filho e, em seguida, usar a propriedade javascript nativa scrollHeighte a propriedade jquery outerHeightdo elemento filho para detectar o final do deslocamento, após o qual return falseo evento wheeling evita qualquer rolagem.

var scrollableDist,curScrollPos,wheelEvent,dY;
$('#child-element').on('wheel', function(e){
  scrollableDist = $(this)[0].scrollHeight - $(this).outerHeight();
  curScrollPos = $(this).scrollTop();
  wheelEvent = e.originalEvent;
  dY = wheelEvent.deltaY;
  if ((dY>0 && curScrollPos >= scrollableDist) ||
      (dY<0 && curScrollPos <= 0)) {
    return false;
  }
});

Eu chamei isso da biblioteca escolhida: https://github.com/harvesthq/chosen/blob/master/coffee/chosen.jquery.coffee

function preventParentScroll(evt) {
    var delta = evt.deltaY || -evt.wheelDelta || (evt && evt.detail)
    if (delta) {
        evt.preventDefault()
        if (evt.type ==  'DOMMouseScroll') {
            delta = delta * 40  
        }
        fakeTable.scrollTop = delta + fakeTable.scrollTop
    }
}
var el = document.getElementById('some-id')
el.addEventListener('mousewheel', preventParentScroll)
el.addEventListener('DOMMouseScroll', preventParentScroll)

Isso funciona para mim.


Há também um truque engraçado para bloquear os pais scrollTopquando o mouse passa sobre um elemento rolável. Dessa forma, você não precisa implementar sua própria rolagem de roda.

Aqui está um exemplo para evitar a rolagem de documentos, mas pode ser ajustado para qualquer elemento.

scrollable.mouseenter(function ()
{
  var scroll = $(document).scrollTop();
  $(document).on('scroll.trap', function ()
  {
    if ($(document).scrollTop() != scroll) $(document).scrollTop(scroll);
  });
});

scrollable.mouseleave(function ()
{
  $(document).off('scroll.trap');
});

Não use overflow: hidden;em body. Rola automaticamente tudo para o topo. Não há necessidade de JavaScript também. Fazer uso deoverflow: auto; :

Estrutura HTML

<div class="overlay">
    <div class="overlay-content"></div>
</div>

<div class="background-content">
    lengthy content here
</div>

Styling

.overlay{
    position: fixed;
    top: 0px;
    left: 0px;
    right: 0px;
    bottom: 0px;
    background-color: rgba(0, 0, 0, 0.8);

    .overlay-content {
        height: 100%;
        overflow: scroll;
    }
}

.background-content{
    height: 100%;
    overflow: auto;
}

Jogue com a demonstração here .





event-propagation