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.)
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.)
Respostas:
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
);
}
getBoundingClientRect
especificamente para o propósito de encontrar coordenadas de elemento ... Por que não o usamos?
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() */
);
}
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 onrepaint
evento 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 DOMContentLoaded
disparado, os estilos são aplicados , mas as imagens ainda não foram carregadas . Portanto, devemos adicionar o window.onload
ouvinte 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:
z-index
overflow-scroll
no contêiner do elementoreturn (rect.bottom >= 0 && rect.right >= 0 && rect.top <= (window.innerHeight || document.documentElement.clientHeight) && rect.left <= (window.innerWidth || document.documentElement.clientWidth));
isElementInViewport(document.getElementById('elem'))
) e não o objeto jQuery (por exemplo, isElementInViewport($("#elem))
). O jQuery equivalente é adicionar [0]
assim: isElementInViewport($("#elem)[0])
.
el is not defined
Nos navegadores modernos, você pode conferir a API do Intersection Observer, que oferece os seguintes benefícios:
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.
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:
clip
propriedade CSSEssas limitações são demonstradas nos seguintes resultados de um teste simples :
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:
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.
doc
um alias document
. Sim, eu gosto de pensar nisso como uma solução decente para os casos extremos.
element.contains(efp(rect.right - (rect.width / 2), rect.bottom - (rect.height / 2)))
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() */;
}
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);
}
<!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>
isElementPartiallyInViewport
é muito útil também. Agradável.
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 true
se alguma parte do elemento estiver na janela de exibição.
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/
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
}
});
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>
<!DOCTYPE html>
ao HTML
IntersectionObserver
é um recurso experimental (que pode mudar no futuro).
IntersectionObserver
somente aciona o retorno de chamada após o movimento do destino em relação à raiz.
observe
evento de chamada é acionado imediatamente, informando o estado atual da interseção do elemento rastreado. Então, de alguma forma - ele aborda.
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.
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");
}
});
$window = $(window)
fora do manipulador de rolagem.
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;
}
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)
);
};
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);
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).
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.
$window = $(window)
fora do manipulador de rolagem
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
visible
quando apenas um pixel de qualquer lado aparecer e não for um canto,-Você ainda pode retornar
visible
enquanto 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-scroll
no 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 crossSearchAlgorithm
qual é ú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 crossSearchAlgorithm
que permite que você ainda retorne visible
quando 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.
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;
}
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
);
}
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}
}
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.');
}
};
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');
}
})
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 já 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.
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;
}
}
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;
this
refere-se ao elemento que você deseja saber se é, por exemplo, overTop
ou overBottom
- você só deve entender o que está acontecendo ...