Como posso saber se um elemento DOM está visível na viewport atual?

949

Existe uma maneira eficiente de saber se um elemento DOM (em um documento HTML) está atualmente visível (aparece na janela de exibição )?

(A pergunta refere-se ao Firefox.)

benzaita
fonte
Depende do que você quer dizer com visível. Se você quer dizer que ele está atualmente exibido na página, dada a posição de rolagem, é possível calculá-lo com base nos elementos y offset e na posição atual de rolagem.
roryf 23/09/08
18
Esclarecimento: visível, como no retângulo atualmente exibido. Visível, como em não oculto ou exibido: nenhum? Visível, como não está por trás de outra coisa na ordem Z?
Adam Wright
6
como dentro do retângulo atualmente exibido. Obrigado. Reformulado.
benzaita 23/09/08
2
Observe que todas as respostas aqui fornecerão um falso positivo para itens que estão fora da área visível do elemento pai (eh com o estouro oculto), mas dentro da área da janela de visualização.
Andy E
1
Há um milhão de respostas e a maioria é ridiculamente longa. Veja aqui para obter duas
linhas

Respostas:

347

Atualização: o tempo continua e nossos navegadores também. Essa técnica não é mais recomendada e você deve usar a solução de Dan se não precisar oferecer suporte à versão do Internet Explorer anterior ao 7.

Solução original (agora desatualizada):

Isso verificará se o elemento está totalmente visível na viewport atual:

function elementInViewport(el) {
  var top = el.offsetTop;
  var left = el.offsetLeft;
  var width = el.offsetWidth;
  var height = el.offsetHeight;

  while(el.offsetParent) {
    el = el.offsetParent;
    top += el.offsetTop;
    left += el.offsetLeft;
  }

  return (
    top >= window.pageYOffset &&
    left >= window.pageXOffset &&
    (top + height) <= (window.pageYOffset + window.innerHeight) &&
    (left + width) <= (window.pageXOffset + window.innerWidth)
  );
}

Você pode modificar isso simplesmente para determinar se alguma parte do elemento está visível na janela de exibição:

function elementInViewport2(el) {
  var top = el.offsetTop;
  var left = el.offsetLeft;
  var width = el.offsetWidth;
  var height = el.offsetHeight;

  while(el.offsetParent) {
    el = el.offsetParent;
    top += el.offsetTop;
    left += el.offsetLeft;
  }

  return (
    top < (window.pageYOffset + window.innerHeight) &&
    left < (window.pageXOffset + window.innerWidth) &&
    (top + height) > window.pageYOffset &&
    (left + width) > window.pageXOffset
  );
}
Prestaul
fonte
1
A função original postada teve um erro. Necessário salvar a largura / altura antes de reatribuir ...
Prestaul
15
E se o elemento estiver em uma div rolável e sair de uma visualização ??
amartynov
2
Consulte uma versão mais recente do script abaixo
Dan
1
Também curioso sobre a pergunta de @ amartynov. Alguém sabe como simplesmente dizer se um elemento está oculto devido ao estouro de um elemento ancestral? Bônus se isso puder ser detectado, independentemente da profundidade da criança.
11138 Eric Nguyen #
1
@deadManN recorrente no DOM é notoriamente lento. Isso é motivo suficiente, mas os fornecedores de navegadores também criaram getBoundingClientRectespecificamente para o propósito de encontrar coordenadas de elemento ... Por que não o usamos?
Prestaul
1402

Agora a maioria dos navegadores oferece suporte ao método getBoundingClientRect , que se tornou a melhor prática. O uso de uma resposta antiga é muito lento , não é preciso e possui vários erros .

A solução selecionada como correta quase nunca é precisa . Você pode ler mais sobre seus erros.


Esta solução foi testada no Internet Explorer 7 (e posterior), iOS 5 (e posterior) Safari, Android 2.0 (Eclair) e posterior, BlackBerry, Opera Mobile e Internet Explorer Mobile 9 .


function isElementInViewport (el) {

    // Special bonus for those using jQuery
    if (typeof jQuery === "function" && el instanceof jQuery) {
        el = el[0];
    }

    var rect = el.getBoundingClientRect();

    return (
        rect.top >= 0 &&
        rect.left >= 0 &&
        rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /* or $(window).height() */
        rect.right <= (window.innerWidth || document.documentElement.clientWidth) /* or $(window).width() */
    );
}

Como usar:

Você pode ter certeza de que a função fornecida acima retorna a resposta correta no momento em que é chamada, mas e quanto à visibilidade do elemento de rastreamento como um evento?

Coloque o seguinte código na parte inferior da sua <body>tag:

function onVisibilityChange(el, callback) {
    var old_visible;
    return function () {
        var visible = isElementInViewport(el);
        if (visible != old_visible) {
            old_visible = visible;
            if (typeof callback == 'function') {
                callback();
            }
        }
    }
}

var handler = onVisibilityChange(el, function() {
    /* Your code go here */
});


// jQuery
$(window).on('DOMContentLoaded load resize scroll', handler);

/* // Non-jQuery
if (window.addEventListener) {
    addEventListener('DOMContentLoaded', handler, false);
    addEventListener('load', handler, false);
    addEventListener('scroll', handler, false);
    addEventListener('resize', handler, false);
} else if (window.attachEvent)  {
    attachEvent('onDOMContentLoaded', handler); // Internet Explorer 9+ :(
    attachEvent('onload', handler);
    attachEvent('onscroll', handler);
    attachEvent('onresize', handler);
}
*/

Se você fizer modificações no DOM, elas poderão alterar a visibilidade do seu elemento, é claro.

Diretrizes e armadilhas comuns:

Talvez você precise acompanhar o zoom da página / pitada de dispositivo móvel? O jQuery deve lidar com o zoom / pitada de navegador, caso contrário, o primeiro ou o segundo link deve ajudá-lo.

Se você modificar o DOM , isso poderá afetar a visibilidade do elemento. Você deve assumir o controle sobre isso e ligar handler()manualmente. Infelizmente, não temos nenhum onrepaintevento entre navegadores . Por outro lado, isso nos permite fazer otimizações e executar a verificação novamente apenas nas modificações do DOM que podem alterar a visibilidade de um elemento.

Nunca Nunca use-o somente no jQuery $ (document) .ready () , porque não há garantia de que o CSS tenha sido aplicado neste momento. Seu código pode funcionar localmente com seu CSS em um disco rígido, mas uma vez colocado em um servidor remoto, ele falhará.

Depois de DOMContentLoadeddisparado, os estilos são aplicados , mas as imagens ainda não foram carregadas . Portanto, devemos adicionar o window.onloadouvinte de eventos.

Ainda não podemos capturar o evento zoom / pitada.

O último recurso pode ser o seguinte código:

/* TODO: this looks like a very bad code */
setInterval(handler, 600);

Você pode usar o incrível recurso pageVisibiliy da API HTML5 se quiser saber se a guia da sua página da web está ativa e visível.

TODO: esse método não lida com duas situações:

Dan
fonte
6
Estou usando esta solução (cuidado com o erro de digitação "botom"). Também há algo para estar ciente, quando o elemento que estamos considerando tiver imagens nele. O Chrome (pelo menos) deve esperar que a imagem seja carregada para ter o valor exato para o boundingRectangle. Parece que o Firefox não tem esse "problema"
Claudio
4
Funciona quando você tem a rolagem ativada em um contêiner dentro do corpo. Por exemplo, ele não funciona aqui - agaase.github.io/webpages/demo/isonscreen2.html isElementInViewport (document.getElementById ("innerele")). innerele está presente dentro de um contêiner com a rolagem ativada.
agaase
70
Os cálculos assumem que o elemento é menor que a tela. Se tiver elementos de alta ou de largura, pode ser mais preciso usarreturn (rect.bottom >= 0 && rect.right >= 0 && rect.top <= (window.innerHeight || document.documentElement.clientHeight) && rect.left <= (window.innerWidth || document.documentElement.clientWidth));
Roonaan
11
Dica: Para aqueles que tentam implementar isso com o jQuery, apenas um lembrete amigável para passar o objeto HTML DOM (por exemplo, isElementInViewport(document.getElementById('elem'))) e não o objeto jQuery (por exemplo, isElementInViewport($("#elem))). O jQuery equivalente é adicionar [0]assim: isElementInViewport($("#elem)[0]).
Jiminy # jiminy #
11
el is not defined
M_Willett
176

Atualizar

Nos navegadores modernos, você pode conferir a API do Intersection Observer, que oferece os seguintes benefícios:

  • Melhor desempenho do que ouvir eventos de rolagem
  • Funciona em iframes entre domínios
  • Pode dizer se um elemento está obstruindo / cruzando outro

O Intersection Observer está a caminho de ser um padrão completo e já é suportado no Chrome 51+, Edge 15+ e Firefox 55+ e está em desenvolvimento para o Safari. Há também um polyfill disponível.


Resposta anterior

Existem alguns problemas com a resposta fornecida por Dan que podem torná-la uma abordagem inadequada para algumas situações. Algumas dessas questões são apontadas em sua resposta, na parte inferior, que seu código fornecerá falsos positivos para elementos que são:

  • Escondido por outro elemento na frente do que está sendo testado
  • Fora da área visível de um elemento pai ou ancestral
  • Um elemento ou seus filhos ocultos usando a clippropriedade CSS

Essas limitações são demonstradas nos seguintes resultados de um teste simples :

Teste com falha, usando isElementInViewport

A solução: isElementVisible()

Aqui está uma solução para esses problemas, com o resultado do teste abaixo e uma explicação de algumas partes do código.

function isElementVisible(el) {
    var rect     = el.getBoundingClientRect(),
        vWidth   = window.innerWidth || document.documentElement.clientWidth,
        vHeight  = window.innerHeight || document.documentElement.clientHeight,
        efp      = function (x, y) { return document.elementFromPoint(x, y) };     

    // Return false if it's not in the viewport
    if (rect.right < 0 || rect.bottom < 0 
            || rect.left > vWidth || rect.top > vHeight)
        return false;

    // Return true if any of its four corners are visible
    return (
          el.contains(efp(rect.left,  rect.top))
      ||  el.contains(efp(rect.right, rect.top))
      ||  el.contains(efp(rect.right, rect.bottom))
      ||  el.contains(efp(rect.left,  rect.bottom))
    );
}

Teste de aprovação : http://jsfiddle.net/AndyE/cAY8c/

E o resultado:

Teste aprovado, usando isElementVisible

Notas Adicionais

Este método não é sem suas próprias limitações, no entanto. Por exemplo, um elemento sendo testado com um índice z menor do que outro elemento no mesmo local seria identificado como oculto, mesmo que o elemento na frente não oculte realmente nenhuma parte dele. Ainda assim, esse método tem seus usos em alguns casos que a solução de Dan não cobre.

Ambos element.getBoundingClientRect()e document.elementFromPoint()fazem parte do Projeto de especificação CSSOM de Trabalho e são apoiados em pelo menos IE 6 e posterior e a maioria dos navegadores de desktop por um longo tempo (embora não perfeitamente). Consulte Quirksmode nestas funções para obter mais informações.

contains()é usado para ver se o elemento retornado por document.elementFromPoint()é um nó filho do elemento que estamos testando para obter visibilidade. Também retorna true se o elemento retornado for o mesmo elemento. Isso apenas torna a verificação mais robusta. É suportado em todos os principais navegadores, sendo o Firefox 9.0 o último a adicioná-lo. Para suporte mais antigo ao Firefox, verifique o histórico desta resposta.

Se você quiser testar mais pontos em torno do elemento para obter visibilidade - ou seja, para garantir que o elemento não seja coberto por mais de, digamos, 50% -, não seria preciso muito para ajustar a última parte da resposta. No entanto, esteja ciente de que provavelmente seria muito lento se você verificasse cada pixel para garantir que estivesse 100% visível.

Andy E
fonte
2
Você queria usar doc.documentElement.clientWidth? Em vez disso, deveria ser 'document.documentElement'? Em uma observação diferente, este é o único método que também funciona para casos de uso, como ocultar o conteúdo de um elemento para acessibilidade usando a propriedade 'clip' do CSS: snook.ca/archives/html_and_css/hiding-content-for-accessibility
Kevin Atualizando
@ klamping: boa captura, obrigado. Eu o copiei diretamente do meu código para o qual estava usando docum alias document. Sim, eu gosto de pensar nisso como uma solução decente para os casos extremos.
Andy E
2
Para mim, não está funcionando. Mas inViewport () na resposta anterior está funcionando no FF.
Satya Prakash
9
Também pode ser benéfico verificar se o centro do elemento está visível se você tiver cantos arredondados ou uma transformação aplicada, pois os cantos delimitadores podem não retornar o elemento esperado:element.contains(efp(rect.right - (rect.width / 2), rect.bottom - (rect.height / 2)))
Jared
2
Não funcionou em entradas para mim (chrome canary 50). Não sabe por que, talvez cantos arredondados nativos? Eu tive que reduzir um pouco as cordas para que ele funcionasse el.contains (efp (rect.left + 1, rect.top + 1)) || el.contains (efp (rect.right-1, rect.top + 1)) || el.contains (efp (rect.right-1, rect.bottom-1)) || el.contains (EFP (rect.left + 1, rect.bottom-1))
Rayjax
65

Eu tentei a resposta de Dan . No entanto , a álgebra usada para determinar os limites significa que o elemento deve ser ≤ o tamanho da viewport e estar completamente dentro da viewport para obter true, levando facilmente a falsos negativos. Se você deseja determinar se um elemento está na janela de exibição, a resposta do ryanve está próxima, mas o elemento que está sendo testado deve se sobrepor à janela de exibição, então tente o seguinte:

function isElementInViewport(el) {
    var rect = el.getBoundingClientRect();

    return rect.bottom > 0 &&
        rect.right > 0 &&
        rect.left < (window.innerWidth || document.documentElement.clientWidth) /* or $(window).width() */ &&
        rect.top < (window.innerHeight || document.documentElement.clientHeight) /* or $(window).height() */;
}
Walf
fonte
33

Como um serviço público:
a resposta de Dan com os cálculos corretos (o elemento pode ser> janela, especialmente nas telas do telefone celular) e o teste jQuery correto, além de adicionar isElementPartiallyInViewport:

A propósito, a diferença entre window.innerWidth e document.documentElement.clientWidth é que clientWidth / clientHeight não inclui a barra de rolagem, enquanto window.innerWidth / Height inclui.

function isElementPartiallyInViewport(el)
{
    // Special bonus for those using jQuery
    if (typeof jQuery !== 'undefined' && el instanceof jQuery) 
        el = el[0];

    var rect = el.getBoundingClientRect();
    // DOMRect { x: 8, y: 8, width: 100, height: 100, top: 8, right: 108, bottom: 108, left: 8 }
    var windowHeight = (window.innerHeight || document.documentElement.clientHeight);
    var windowWidth = (window.innerWidth || document.documentElement.clientWidth);

    // http://stackoverflow.com/questions/325933/determine-whether-two-date-ranges-overlap
    var vertInView = (rect.top <= windowHeight) && ((rect.top + rect.height) >= 0);
    var horInView = (rect.left <= windowWidth) && ((rect.left + rect.width) >= 0);

    return (vertInView && horInView);
}


// http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport
function isElementInViewport (el)
{
    // Special bonus for those using jQuery
    if (typeof jQuery !== 'undefined' && el instanceof jQuery) 
        el = el[0];

    var rect = el.getBoundingClientRect();
    var windowHeight = (window.innerHeight || document.documentElement.clientHeight);
    var windowWidth = (window.innerWidth || document.documentElement.clientWidth);

    return (
           (rect.left >= 0)
        && (rect.top >= 0)
        && ((rect.left + rect.width) <= windowWidth)
        && ((rect.top + rect.height) <= windowHeight)
    );
}


function fnIsVis(ele)
{
    var inVpFull = isElementInViewport(ele);
    var inVpPartial = isElementPartiallyInViewport(ele);
    console.clear();
    console.log("Fully in viewport: " + inVpFull);
    console.log("Partially in viewport: " + inVpPartial);
}

Caso de teste

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="">
    <meta name="author" content="">
    <title>Test</title>
    <!--
    <script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
    <script src="scrollMonitor.js"></script>
    -->

    <script type="text/javascript">

        function isElementPartiallyInViewport(el)
        {
            // Special bonus for those using jQuery
            if (typeof jQuery !== 'undefined' && el instanceof jQuery) 
                el = el[0];

            var rect = el.getBoundingClientRect();
            // DOMRect { x: 8, y: 8, width: 100, height: 100, top: 8, right: 108, bottom: 108, left: 8 }
            var windowHeight = (window.innerHeight || document.documentElement.clientHeight);
            var windowWidth = (window.innerWidth || document.documentElement.clientWidth);

            // http://stackoverflow.com/questions/325933/determine-whether-two-date-ranges-overlap
            var vertInView = (rect.top <= windowHeight) && ((rect.top + rect.height) >= 0);
            var horInView = (rect.left <= windowWidth) && ((rect.left + rect.width) >= 0);

            return (vertInView && horInView);
        }


        // http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport
        function isElementInViewport (el)
        {
            // Special bonus for those using jQuery
            if (typeof jQuery !== 'undefined' && el instanceof jQuery) 
                el = el[0];

            var rect = el.getBoundingClientRect();
            var windowHeight = (window.innerHeight || document.documentElement.clientHeight);
            var windowWidth = (window.innerWidth || document.documentElement.clientWidth);

            return (
                   (rect.left >= 0)
                && (rect.top >= 0)
                && ((rect.left + rect.width) <= windowWidth)
                && ((rect.top + rect.height) <= windowHeight)
            );
        }


        function fnIsVis(ele)
        {
            var inVpFull = isElementInViewport(ele);
            var inVpPartial = isElementPartiallyInViewport(ele);
            console.clear();
            console.log("Fully in viewport: " + inVpFull);
            console.log("Partially in viewport: " + inVpPartial);
        }


        // var scrollLeft = (window.pageXOffset !== undefined) ? window.pageXOffset : (document.documentElement || document.body.parentNode || document.body).scrollLeft,
        // var scrollTop = (window.pageYOffset !== undefined) ? window.pageYOffset : (document.documentElement || document.body.parentNode || document.body).scrollTop;
    </script>
</head>

<body>
    <div style="display: block; width: 2000px; height: 10000px; background-color: green;">

        <br /><br /><br /><br /><br /><br />
        <br /><br /><br /><br /><br /><br />
        <br /><br /><br /><br /><br /><br />

        <input type="button" onclick="fnIsVis(document.getElementById('myele'));" value="det" />

        <br /><br /><br /><br /><br /><br />
        <br /><br /><br /><br /><br /><br />
        <br /><br /><br /><br /><br /><br />

        <div style="background-color: crimson; display: inline-block; width: 800px; height: 500px;" ></div>
        <div id="myele" onclick="fnIsVis(this);" style="display: inline-block; width: 100px; height: 100px; background-color: hotpink;">
        t
        </div>

        <br /><br /><br /><br /><br /><br />
        <br /><br /><br /><br /><br /><br />
        <br /><br /><br /><br /><br /><br />

        <input type="button" onclick="fnIsVis(document.getElementById('myele'));" value="det" />
    </div>

    <!--
    <script type="text/javascript">

        var element = document.getElementById("myele");
        var watcher = scrollMonitor.create(element);

        watcher.lock();

        watcher.stateChange(function() {
            console.log("state changed");
            // $(element).toggleClass('fixed', this.isAboveViewport)
        });
    </script>
    -->
</body>
</html>
Stefan Steiger
fonte
2
isElementPartiallyInViewporté muito útil também. Agradável.
RJ Cuthbertson
Para isElementInViewport (e): Seu código nem está carregando imagens, este está correto: function isElementInViewport (e) {var t = e.getBoundingClientRect (); return t.top> = 0 && t.left> = 0 && t.bottom <= (window.innerHeight || document.documentElement.clientHeight) && t.right <= (window.innerWidth || document.documentElement.clientWidth) }
Arun chauhan
1
@Arun chauhan: Nenhum dos meus códigos está carregando imagens, por que deveria e a fórmula está correta.
Stefan Steiger
1
@targumon: O motivo é o suporte a navegadores antigos.
Stefan Steiger
1
@StefanSteiger, de acordo com o MDN, é suportado desde o IE9, por isso é praticamente seguro (pelo menos no meu caso) usar apenas o window.innerHeight diretamente. Obrigado!
targumon
28

Veja a fonte da margem , que usa getBoundingClientRect . É como:

function inViewport (el) {

    var r, html;
    if ( !el || 1 !== el.nodeType ) { return false; }
    html = document.documentElement;
    r = el.getBoundingClientRect();

    return ( !!r
      && r.bottom >= 0
      && r.right >= 0
      && r.top <= html.clientHeight
      && r.left <= html.clientWidth
    );

}

Retorna truese alguma parte do elemento estiver na janela de exibição.

ryanve
fonte
27

Minha versão mais curta e mais rápida:

function isElementOutViewport(el){
    var rect = el.getBoundingClientRect();
    return rect.bottom < 0 || rect.right < 0 || rect.left > window.innerWidth || rect.top > window.innerHeight;
}

E um jsFiddle conforme necessário: https://jsfiddle.net/on1g619L/1/

Eric Chen
fonte
4
Minha solução é mais gananciosa e rápida, quando um elemento tem algum pixel na janela de exibição, ele retornará falso.
Eric Chen
1
Eu gosto disso. Conciso. Você pode remover os espaços entre o nome da função e parênteses, e entre parênteses e chaves, na primeira linha. Nunca gostei desses espaços. Talvez seja apenas o meu editor de texto que codifica com cores tudo que facilita a leitura. function aaa (arg) {declarações} Eu sei que isso não faz com que seja executado mais rapidamente, mas é minimizado.
YK
21

Achei preocupante o fato de não haver uma versão centralizada do jQuery da funcionalidade disponível. Quando me deparei a solução de Dan, vi a oportunidade de oferecer algo para as pessoas que gostam de programar no estilo jQuery OO. É agradável e ágil e funciona como um encanto para mim.

Bada bing bada boom

$.fn.inView = function(){
    if(!this.length) 
        return false;
    var rect = this.get(0).getBoundingClientRect();

    return (
        rect.top >= 0 &&
        rect.left >= 0 &&
        rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
        rect.right <= (window.innerWidth || document.documentElement.clientWidth)
    );

};

// Additional examples for other use cases
// Is true false whether an array of elements are all in view
$.fn.allInView = function(){
    var all = [];
    this.forEach(function(){
        all.push( $(this).inView() );
    });
    return all.indexOf(false) === -1;
};

// Only the class elements in view
$('.some-class').filter(function(){
    return $(this).inView();
});

// Only the class elements not in view
$('.some-class').filter(function(){
    return !$(this).inView();
});

Uso

$(window).on('scroll',function(){

    if( $('footer').inView() ) {
        // Do cool stuff
    }
});
r3wt
fonte
1
A cinta encaracolada seria suficiente para fechar a declaração if?
calasyr 24/05
1
Eu não poderia fazê-lo funcionar com vários elementos de uma classe idêntica.
O Whiz de Oz
1
@TheWhizofOz, atualizei minha resposta para dar exemplos dos outros possíveis casos de uso que você mencionou. boa sorte.
r3wt
10

A nova API do Intersection Observer aborda essa questão muito diretamente.

Esta solução precisará de um polyfill, pois o Safari, Opera e Internet Explorer ainda não suportam isso (o polyfill está incluído na solução).

Nesta solução, há uma caixa fora da vista que é o alvo (observado). Quando aparece, o botão na parte superior do cabeçalho fica oculto. É mostrado quando a caixa sai da vista.

const buttonToHide = document.querySelector('button');

const hideWhenBoxInView = new IntersectionObserver((entries) => {
  if (entries[0].intersectionRatio <= 0) { // If not in view
    buttonToHide.style.display = "inherit";
  } else {
    buttonToHide.style.display = "none";
  }
});

hideWhenBoxInView.observe(document.getElementById('box'));
header {
  position: fixed;
  top: 0;
  width: 100vw;
  height: 30px;
  background-color: lightgreen;
}

.wrapper {
  position: relative;
  margin-top: 600px;
}

#box {
  position: relative;
  left: 175px;
  width: 150px;
  height: 135px;
  background-color: lightblue;
  border: 2px solid;
}
<script src="https://polyfill.io/v2/polyfill.min.js?features=IntersectionObserver"></script>
<header>
  <button>NAVIGATION BUTTON TO HIDE</button>
</header>
  <div class="wrapper">
    <div id="box">
    </div>
  </div>

Randy Casburn
fonte
3
Boa implementação, e de acordo com o link em esta resposta que deve trabalhar em safari, adicionando <!DOCTYPE html>ao HTML
Alon Eitan
Observe que IntersectionObserver é um recurso experimental (que pode mudar no futuro).
Karthik Chintala
2
@KarthikChintala - é suportado em todos os navegadores, exceto o IE - e também há um polyfill disponível.
Randy Casburn
Não aborda a questão do OP, pois apenas detecta alterações : IntersectionObserversomente aciona o retorno de chamada após o movimento do destino em relação à raiz.
Brandon Hill
Quando o observeevento de chamada é acionado imediatamente, informando o estado atual da interseção do elemento rastreado. Então, de alguma forma - ele aborda.
Mesqalito 24/01
8

Todas as respostas que encontrei aqui apenas verificam se o elemento está posicionado dentro da viewport atual . Mas isso não significa que seja visível .
E se o elemento fornecido estiver dentro de uma div com conteúdo excedente e for deslocado para fora da vista?

Para resolver isso, você teria que verificar se o elemento está contido por todos os pais.
Minha solução faz exatamente isso:

Também permite especificar quanto do elemento deve estar visível.

Element.prototype.isVisible = function(percentX, percentY){
    var tolerance = 0.01;   //needed because the rects returned by getBoundingClientRect provide the position up to 10 decimals
    if(percentX == null){
        percentX = 100;
    }
    if(percentY == null){
        percentY = 100;
    }

    var elementRect = this.getBoundingClientRect();
    var parentRects = [];
    var element = this;

    while(element.parentElement != null){
        parentRects.push(element.parentElement.getBoundingClientRect());
        element = element.parentElement;
    }

    var visibleInAllParents = parentRects.every(function(parentRect){
        var visiblePixelX = Math.min(elementRect.right, parentRect.right) - Math.max(elementRect.left, parentRect.left);
        var visiblePixelY = Math.min(elementRect.bottom, parentRect.bottom) - Math.max(elementRect.top, parentRect.top);
        var visiblePercentageX = visiblePixelX / elementRect.width * 100;
        var visiblePercentageY = visiblePixelY / elementRect.height * 100;
        return visiblePercentageX + tolerance > percentX && visiblePercentageY + tolerance > percentY;
    });
    return visibleInAllParents;
};

Esta solução ignorou o fato de que elementos podem não estar visíveis devido a outros fatos, como opacity: 0.

Eu testei esta solução no Chrome e no Internet Explorer 11.

Domysee
fonte
8

Acho que a resposta aceita aqui é excessivamente complicada para a maioria dos casos de uso. Esse código funciona bem (usando jQuery) e diferencia entre elementos totalmente visíveis e parcialmente visíveis:

var element         = $("#element");
var topOfElement    = element.offset().top;
var bottomOfElement = element.offset().top + element.outerHeight(true);
var $window         = $(window);

$window.bind('scroll', function() {

    var scrollTopPosition   = $window.scrollTop()+$window.height();
    var windowScrollTop     = $window.scrollTop()

    if (windowScrollTop > topOfElement && windowScrollTop < bottomOfElement) {
        // Element is partially visible (above viewable area)
        console.log("Element is partially visible (above viewable area)");

    } else if (windowScrollTop > bottomOfElement && windowScrollTop > topOfElement) {
        // Element is hidden (above viewable area)
        console.log("Element is hidden (above viewable area)");

    } else if (scrollTopPosition < topOfElement && scrollTopPosition < bottomOfElement) {
        // Element is hidden (below viewable area)
        console.log("Element is hidden (below viewable area)");

    } else if (scrollTopPosition < bottomOfElement && scrollTopPosition > topOfElement) {
        // Element is partially visible (below viewable area)
        console.log("Element is partially visible (below viewable area)");

    } else {
        // Element is completely visible
        console.log("Element is completely visible");
    }
});
Adam Rehal
fonte
Você definitivamente deve fazer cache $window = $(window)fora do manipulador de rolagem.
sam
4

A solução mais simples como o suporte a Element.getBoundingClientRect () tornou- se perfeita :

function isInView(el) {
    let box = el.getBoundingClientRect();
    return box.top < window.innerHeight && box.bottom >= 0;
}
leonheess
fonte
Como isso se comporta nos navegadores móveis? A maioria deles tem problemas com a viewport, com o cabeçalho subindo ou descendo na rolagem e comportamento diferente quando o teclado é exibido, dependendo do Android ou do iOS, etc.
Kev
@Kev Deverá funcionar perfeitamente, dependendo de quando você chamar esse método. Se você chamá-lo e redimensionar a janela, o resultado pode obviamente não estar mais correto. Você pode chamá-lo em todos os eventos de redimensionamento, dependendo do tipo de funcionalidade desejada. Sinta-se à vontade para fazer uma pergunta separada sobre seu caso de uso específico e faça ping aqui.
leonheess 26/01
3

Eu acho que essa é uma maneira mais funcional de fazer isso. A resposta de Dan não funciona em um contexto recursivo.

Essa função resolve o problema quando seu elemento está dentro de outras divs roláveis, testando qualquer nível recursivamente até a tag HTML e para no primeiro falso.

/**
 * fullVisible=true only returns true if the all object rect is visible
 */
function isReallyVisible(el, fullVisible) {
    if ( el.tagName == "HTML" )
            return true;
    var parentRect=el.parentNode.getBoundingClientRect();
    var rect = arguments[2] || el.getBoundingClientRect();
    return (
            ( fullVisible ? rect.top    >= parentRect.top    : rect.bottom > parentRect.top ) &&
            ( fullVisible ? rect.left   >= parentRect.left   : rect.right  > parentRect.left ) &&
            ( fullVisible ? rect.bottom <= parentRect.bottom : rect.top    < parentRect.bottom ) &&
            ( fullVisible ? rect.right  <= parentRect.right  : rect.left   < parentRect.right ) &&
            isReallyVisible(el.parentNode, fullVisible, rect)
    );
};
tonelada
fonte
3

As respostas mais aceitas não funcionam ao ampliar o Google Chrome no Android. Em combinação com a resposta de Dan , para dar conta do Chrome no Android, o visualViewport deve ser usado. O exemplo a seguir leva em consideração apenas a verificação vertical e usa jQuery para a altura da janela:

var Rect = YOUR_ELEMENT.getBoundingClientRect();
var ElTop = Rect.top, ElBottom = Rect.bottom;
var WindowHeight = $(window).height();
if(window.visualViewport) {
    ElTop -= window.visualViewport.offsetTop;
    ElBottom -= window.visualViewport.offsetTop;
    WindowHeight = window.visualViewport.height;
}
var WithinScreen = (ElTop >= 0 && ElBottom <= WindowHeight);
Dakusan
fonte
2

Aqui está a minha solução. Funcionará se um elemento estiver oculto dentro de um contêiner rolável.

Aqui está uma demonstração (tente redimensionar a janela para)

var visibleY = function(el){
    var top = el.getBoundingClientRect().top, rect, el = el.parentNode;
    do {
        rect = el.getBoundingClientRect();
        if (top <= rect.bottom === false)
            return false;
        el = el.parentNode;
    } while (el != document.body);
    // Check it's within the document viewport
    return top <= document.documentElement.clientHeight;
};

Eu só precisava verificar se ele está visível no eixo Y (para um recurso de rolagem Ajax load-more-records).

Aliado
fonte
2

Com base na solução de dan , eu tentei limpar a implementação para facilitar o uso dela várias vezes na mesma página:

$(function() {

  $(window).on('load resize scroll', function() {
    addClassToElementInViewport($('.bug-icon'), 'animate-bug-icon');
    addClassToElementInViewport($('.another-thing'), 'animate-thing');
    // 👏 repeat as needed ...
  });

  function addClassToElementInViewport(element, newClass) {
    if (inViewport(element)) {
      element.addClass(newClass);
    }
  }

  function inViewport(element) {
    if (typeof jQuery === "function" && element instanceof jQuery) {
      element = element[0];
    }
    var elementBounds = element.getBoundingClientRect();
    return (
      elementBounds.top >= 0 &&
      elementBounds.left >= 0 &&
      elementBounds.bottom <= $(window).height() &&
      elementBounds.right <= $(window).width()
    );
  }

});

O jeito que eu estou usando é que, quando o elemento aparece na tela, estou adicionando uma classe que aciona uma animação de quadro-chave CSS. É bem direto e funciona especialmente bem quando você tem mais de 10 itens para animar condicionalmente em uma página.

Pirijan
fonte
Você deve definitivamente cache de $window = $(window)fora do manipulador de rolagem
Adam Rehal
2

A maioria dos usos nas respostas anteriores está falhando nesses pontos:

-Quando qualquer pixel de um elemento estiver visível, mas não " um canto ",

-Quando um elemento é maior que a porta de visualização e centralizado ,

-A maioria deles está verificando apenas um elemento singular dentro de um documento ou janela .

Bem, para todos esses problemas, eu tenho uma solução e os lados positivos são:

-Você pode retornar visiblequando apenas um pixel de qualquer lado aparecer e não for um canto,

-Você ainda pode retornar visibleenquanto o elemento for maior que a viewport,

-Você pode escolher o seu parent element ou pode deixá-lo escolher automaticamente,

-Funciona também em elementos adicionados dinamicamente .

Se você verificar os trechos abaixo, verá que a diferença de uso overflow-scrollno contêiner de elemento não causará nenhum problema e verá que, ao contrário de outras respostas aqui, mesmo que um pixel apareça de qualquer lado ou quando um elemento seja maior que a viewport e estamos vendo o interior pixels do elemento ainda funciona.

O uso é simples:

// For checking element visibility from any sides
isVisible(element)

// For checking elements visibility in a parent you would like to check
var parent = document; // Assuming you check if 'element' inside 'document'
isVisible(element, parent)

// For checking elements visibility even if it's bigger than viewport
isVisible(element, null, true) // Without parent choice
isVisible(element, parent, true) // With parent choice

Uma demonstração sem a crossSearchAlgorithmqual é útil para elementos maiores que a viewport, verifique os pixels internos do elemento3 para ver:

Veja bem, quando você está dentro do elemento3, ele não sabe se é visível ou não, porque estamos verificando apenas se o elemento é visível dos lados ou dos cantos .

E este inclui o crossSearchAlgorithmque permite que você ainda retorne visiblequando o elemento for maior que a viewport:

JSFiddle para jogar: http://jsfiddle.net/BerkerYuceer/grk5az2c/

Esse código é criado para obter informações mais precisas se alguma parte do elemento for mostrada na visualização ou não. Para opções de desempenho ou apenas slides verticais, não use isso! Esse código é mais eficaz em casos de desenho.

Berker Yüceer
fonte
1

Uma solução melhor:

function getViewportSize(w) {
    var w = w || window;
    if(w.innerWidth != null)
        return {w:w.innerWidth, h:w.innerHeight};
    var d = w.document;
    if (document.compatMode == "CSS1Compat") {
        return {
            w: d.documentElement.clientWidth,
            h: d.documentElement.clientHeight
        };
    }
    return { w: d.body.clientWidth, h: d.body.clientWidth };
}


function isViewportVisible(e) {
    var box = e.getBoundingClientRect();
    var height = box.height || (box.bottom - box.top);
    var width = box.width || (box.right - box.left);
    var viewport = getViewportSize();
    if(!height || !width)
        return false;
    if(box.top > viewport.h || box.bottom < 0)
        return false;
    if(box.right < 0 || box.left > viewport.w)
        return false;
    return true;
}
rainyjune
fonte
12
Você deve tentar explicar por que sua versão é melhor. Tal como está, parece mais ou menos o mesmo que as outras soluções.
Andy E
1
Ótima solução, ele possui um BOX / ScrollContainer e não está usando a JANELA (apenas se não for especificado). Dê uma olhada no código do que a taxa é uma solução mais universal (Foi procurá-lo muito)
teter
1

Tão simples quanto possível, IMO:

function isVisible(elem) {
  var coords = elem.getBoundingClientRect();
  return Math.abs(coords.top) <= coords.height;
}
JuanM.
fonte
0

Aqui está uma função que informa se um elemento está visível na viewport atual de um elemento pai :

function inParentViewport(el, pa) {
    if (typeof jQuery === "function"){
        if (el instanceof jQuery)
            el = el[0];
        if (pa instanceof jQuery)
            pa = pa[0];
    }

    var e = el.getBoundingClientRect();
    var p = pa.getBoundingClientRect();

    return (
        e.bottom >= p.top &&
        e.right >= p.left &&
        e.top <= p.bottom &&
        e.left <= p.right
    );
}
ssten
fonte
0

Isso verifica se um elemento está pelo menos parcialmente visível (dimensão vertical):

function inView(element) {
    var box = element.getBoundingClientRect();
    return inViewBox(box);
}

function inViewBox(box) {
    return ((box.bottom < 0) || (box.top > getWindowSize().h)) ? false : true;
}


function getWindowSize() {
    return { w: document.body.offsetWidth || document.documentElement.offsetWidth || window.innerWidth, h: document.body.offsetHeight || document.documentElement.offsetHeight || window.innerHeight}
}
Lumic
fonte
0

Eu tive a mesma pergunta e descobri usando getBoundingClientRect ().

Esse código é completamente 'genérico' e só precisa ser escrito uma vez para que funcione (você não precisa escrevê-lo para cada elemento que deseja saber que está na janela de exibição).

Esse código verifica apenas se está verticalmente na janela de exibição, não horizontalmente . Nesse caso, a variável (matriz) 'elementos' mantém todos os elementos que você está verificando na vertical na janela de exibição, então pegue qualquer elemento que desejar em qualquer lugar e armazene-o lá.

O 'for loop', percorre cada elemento e verifica se está verticalmente na viewport. Esse código é executado toda vez que o usuário rola! Se o getBoudingClientRect (). Top for inferior a 3/4 da janela de visualização (o elemento está a um quarto da janela de visualização), ele será registrado como 'na janela de visualização'.

Como o código é genérico, você desejará saber 'qual' elemento está na janela de exibição. Para descobrir isso, você pode determiná-lo por atributo personalizado, nome do nó, ID, nome da classe e muito mais.

Aqui está o meu código (diga-me se não funcionar; foi testado no Internet Explorer 11, Firefox 40.0.3, Chrome versão 45.0.2454.85 m, Opera 31.0.1889.174 e Edge with Windows 10, [ainda não no Safari ]) ...

// Scrolling handlers...
window.onscroll = function(){
  var elements = document.getElementById('whatever').getElementsByClassName('whatever');
  for(var i = 0; i != elements.length; i++)
  {
   if(elements[i].getBoundingClientRect().top <= window.innerHeight*0.75 &&
      elements[i].getBoundingClientRect().top > 0)
   {
      console.log(elements[i].nodeName + ' ' +
                  elements[i].className + ' ' +
                  elements[i].id +
                  ' is in the viewport; proceed with whatever code you want to do here.');
   }
};
www139
fonte
0

Esta é a solução fácil e pequena que funcionou para mim.

Exemplo : você deseja ver se o elemento está visível no elemento pai que possui rolagem excedente.

$(window).on('scroll', function () {

     var container = $('#sidebar');
     var containerHeight = container.height();
     var scrollPosition = $('#row1').offset().top - container.offset().top;

     if (containerHeight < scrollPosition) {
         console.log('not visible');
     } else {
         console.log('visible');
     }
})
Stevan Tosic
fonte
0

Todas as respostas aqui estão determinando se o elemento está totalmente contido na janela de exibição, e não apenas visível de alguma forma. Por exemplo, se apenas metade da imagem estiver visível na parte inferior da visualização, as soluções aqui falharão, considerando que "fora".

Eu tive um caso de uso em que estou carregando preguiçosamente via IntersectionObserver, mas devido às animações que ocorrem durante o pop-in, não queria observar nenhuma imagem que estivesse interceptada no carregamento da página. Para fazer isso, usei o seguinte código:

const bounding = el.getBoundingClientRect();
const isVisible = (0 < bounding.top && bounding.top < (window.innerHeight || document.documentElement.clientHeight)) ||
        (0 < bounding.bottom && bounding.bottom < (window.innerHeight || document.documentElement.clientHeight));

Basicamente, isso é verificar se o limite superior ou inferior está independentemente na janela de exibição. O lado oposto pode estar do lado de fora, mas enquanto um lado estiver dentro, ele será "visível" pelo menos parcialmente.

Chris Pratt
fonte
-1

Eu uso essa função (ela apenas verifica se y está na tela, pois na maioria das vezes o x não é necessário)

function elementInViewport(el) {
    var elinfo = {
        "top":el.offsetTop,
        "height":el.offsetHeight,
    };

    if (elinfo.top + elinfo.height < window.pageYOffset || elinfo.top > window.pageYOffset + window.innerHeight) {
        return false;
    } else {
        return true;
    }

}
Sander Jonk
fonte
-3

Para um desafio semelhante, eu realmente gostei dessa essência, que expõe um polyfill para scrollIntoViewIfNeeded () .

Todo o Kung Fu necessário para responder está dentro deste bloco:

var parent = this.parentNode,
    parentComputedStyle = window.getComputedStyle(parent, null),
    parentBorderTopWidth = parseInt(parentComputedStyle.getPropertyValue('border-top-width')),
    parentBorderLeftWidth = parseInt(parentComputedStyle.getPropertyValue('border-left-width')),
    overTop = this.offsetTop - parent.offsetTop < parent.scrollTop,
    overBottom = (this.offsetTop - parent.offsetTop + this.clientHeight - parentBorderTopWidth) > (parent.scrollTop + parent.clientHeight),
    overLeft = this.offsetLeft - parent.offsetLeft < parent.scrollLeft,
    overRight = (this.offsetLeft - parent.offsetLeft + this.clientWidth - parentBorderLeftWidth) > (parent.scrollLeft + parent.clientWidth),
    alignWithTop = overTop && !overBottom;

thisrefere-se ao elemento que você deseja saber se é, por exemplo, overTopou overBottom- você só deve entender o que está acontecendo ...

Philzen
fonte