当前位置:  开发笔记 > 编程语言 > 正文

如何判断DOM元素在当前视口中是否可见?

如何解决《如何判断DOM元素在当前视口中是否可见?》经验,为你挑选了12个好方法。

是否有一种有效的方法来判断DOM元素(在HTML文档中)当前是否可见(出现在视口中)?

(关于Firefox的问题)



1> Dan..:

现在大多数浏览器都支持getBoundingClientRect方法,这已成为最佳实践.使用旧的答案非常慢,不准确,并有几个错误.

选择正确的解决方案几乎从不精确.您可以阅读有关其错误的更多信息.


该解决方案在IE7 +,iOS5 + Safari,Android2 +,Blackberry,Opera Mobile和IE Mobile 10上进行了测试.


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() */
    );
}

如何使用:

您可以确定上面给出的函数在调用时返回正确的答案,但是跟踪元素作为事件的可见性呢?

将以下代码放在代码的底部:

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); // IE9+ :(
    attachEvent('onload', handler);
    attachEvent('onscroll', handler);
    attachEvent('onresize', handler);
}
*/

如果您进行任何DOM修改,它们可以改变元素的可见性.

准则和常见陷阱:

也许你需要跟踪页面缩放/移动设备捏?jQuery应该处理缩放/捏交叉浏览器,否则第一个或第二个链接应该可以帮助你.

如果修改DOM,则会影响元素的可见性.您应该控制它并handler()手动调用.不幸的是,我们没有跨浏览器onrepaint事件.另一方面,它允许我们进行优化并仅对可以改变元素可见性的DOM修改执行重新检查.

从来没有在jQuery $(document).ready()中使用它,因为此刻没有保证CSS.您的代码可以在硬盘驱动器上与您的CSS本地工作,但一旦放在远程服务器上它将失败.

之后DOMContentLoaded被解雇,样式应用于,但图像尚未加载.所以,我们应该添加window.onload事件监听器.

我们还无法捕捉缩放/捏合事件.

最后的手段可能是以下代码:

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

如果您关心网页的标签是否有效且可见,您可以使用精彩的功能页面Visibiliy HTML5 API.

TODO:此方法不处理两种情况:

重叠使用 z-index

使用overflow-scroll位于元素的容器

尝试新的东西https://pawelgrzybek.com/the-intersection-observer-api-explained/


计算假设元素小于屏幕.如果你有高或宽的元素,使用`return(rect.bottom> = 0 && rect.right> = 0 && rect.top <=(window.innerHeight || document.documentElement.clientHeight)&&可能更准确rect.left <=(window.innerWidth || document.documentElement.clientWidth));`
提示:对于那些试图用jQuery实现这一点的人,只需要传递HTML DOM对象的友好提示(例如,`isElementInViewport(document.getElementById('elem'))`)而不是jQuery对象(例如,`isElementInViewport( $("#elem))`).jQuery等价于添加`[0]`,如:`isElementInViewport($("#elem)[0])`.
`el没有定义`
我正在使用这个解决方案(但要注意"botom"错字).当我们考虑的元素将图像放入其中时,还有一些需要注意的事项.Chrome(至少)必须等待加载图像才能获得boundingRectangle的确切值.似乎Firefox没有这个"问题"
在体内容器中启用滚动时是否有效.例如,它在这里不起作用 - http://agaase.github.io/webpages/demo/isonscreen2.html isElementInViewport(document.getElementById("innerele")).innerele存在于启用了滚动的容器内.
如果您不需要支持IE 5.5及更低版本(您可能不支持),则可以使用`(window.innerWidth || document.documentElement.clientWidth)`以及跨浏览器解决方案的高度等效项.
这里是我的版本,支持的容器元素(例如,如果该元素是另外一个滚动的元素之内),全部或部分的可见性(jQuery的只有尽管):https://gist.github.com/anonymous/f09b5cd487b5ad55c46e

2> Prestaul..:

更新:时间推进,我们的浏览器也是如此.如果您不需要支持IE <7,则不再推荐使用此技术,您应该使用@ Dan的解决方案(/sf/ask/17360801/).

原始解决方案(现已过时):

这将检查元素在当前视口中是否完全可见:

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)
  );
}

您可以简单地修改它以确定元素的任何部分是否在视口中可见:

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
  );
}


如果元素存在于可滚动的div中并滚动出视图,该怎么办?
请查看下面的较新版本的脚本

3> Andy E..:

更新

在现代浏览器中,您可能需要查看Intersection Observer API,它提供以下好处:

比收听滚动事件更好的性能

适用于跨域iframe

可以判断一个元素是否阻碍/交叉另一个元素

Intersection Observer正在成为一个成熟的标准,并已在Chrome 51 +,Edge 15+和Firefox 55+中得到支持,目前正在开发Safari.还有一个polyfill可用.


以前的答案

Dan提供的答案存在一些问题,可能会使其在某些情况下成为不合适的方法.其中一些问题在他的答案附近指出,他的代码将给出以下元素的误报:

被测试者面前的另一个元素隐藏

在父元素或祖先元素的可见区域之外

使用CSS clip属性隐藏的元素或其子元素

这些限制在以下简单测试结果中得到证明:

使用isElementInViewport进行测试失败

解决方案: isElementVisible()

以下是这些问题的解决方案,下面是测试结果,并解释了代码的某些部分.

function isElementVisible(el) {
    var rect     = el.getBoundingClientRect(),
        vWidth   = window.innerWidth || doc.documentElement.clientWidth,
        vHeight  = window.innerHeight || doc.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))
    );
}

通过测试: http ://jsfiddle.net/AndyE/cAY8c/

结果如下:

通过测试,使用isElementVisible

补充说明

然而,这种方法并非没有自身的局限性.例如,即使前面的元素实际上没有隐藏它的任何部分,使用比同一位置的另一个元素更低的z-index测试的元素也将被识别为隐藏.尽管如此,这种方法在某些情况下仍然使用Dan的解决方案.

两者element.getBoundingClientRect()document.elementFromPoint()是CSSOM工作草案规范的一部分,并且在至少IE 6和更高版本,并支持大多数时间长的桌面浏览器(尽管,不完全).有关更多信息,请参阅这些函数的Quirksmode.

contains()用于查看返回document.elementFromPoint()的元素是否是我们正在测试可见性的元素的子节点.如果返回的元素是相同的元素,它也返回true.这只会使检查更加健壮.所有主流浏览器都支持它,Firefox 9.0是最后一个添加它的人.对于较早的Firefox支持,请查看此答案的历史记录.

如果你想在元素周围测试更多的点以获得可见性 - 也就是说,为了确保元素不被覆盖超过50%,那么调整答案的最后部分并不需要太多.但是,请注意,如果检查每个像素以确保它是100%可见,则可能会非常慢.


如果你有圆角或应用了变换,检查元素的中心是否可见也是有益的,因为边界角可能不会返回预期的元素:`element.contains(efp(rect.right - (rect. width/2),rect.bottom - (rect.height/2)))`
您是要使用doc.documentElement.clientWidth吗?应该是“ document.documentElement”吗?另外,这是唯一的方法,也适用于用例,例如使用CSS'clip'属性隐藏元素的内容以实现可访问性:http://snook.ca/archives/html_and_css/hiding-content-for -可访问性
对我来说,这是行不通的。但是先前答案中的inViewport()在FF中有效。
对我来说不起作用(Chrome Canary 50)。不知道为什么,也许是本地的圆角?我不得不稍微降低坐标才能使其工作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))

4> Walf..:

我试过Dan的答案 但是用于确定边界的代数意味着该元素必须既是视口大小又完全在视口内部才能获得true,容易导致误判.如果你想确定一个元素是否在视口中,ryanve的答案是关闭的,但被测试的元素应该与视口重叠,所以试试这个:

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() */;
}



5> Stefan Steig..:

作为公共服务:
Dan的回答是正确的计算(元素可以是>窗口,特别是在手机屏幕上),正确的jQuery测试,以及添加isElementPartiallyInViewport:

顺便说一句,window.innerWidth和document.documentElement.clientWidth之间的区别在于clientWidth/clientHeight不包含滚动条,而window.innerWidth/Height则不包括滚动条.

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);
}

测试用例




    
    
    
    
    Test
    

    




    




































t



















`isElementPartiallyInViewport`也非常有用.好一个.

6> ryanve..:

查看verge的源代码,它使用getBoundingClientRect.就像是:

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 
    );

}

true如果元素的任何部分位于视口中,则返回.



7> 小智..:

有一个名为inview的 jQuery插件可以完成这项工作


我的团队遇到了一些关于这个插件的严重性能问题所以我去了一个分叉,改变了实现以使用getBoundingClientRect().请在此处查看:https://github.com/mmmeff/jquery.inview2

8> Eric Chen..:

我更短更快的版本.

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

根据需要添加jsFiddle https://jsfiddle.net/on1g619L/1/


我的解决方案更贪婪和更快,当元素在视口中有任何像素时,它将返回false.

9> r3wt..:

我发现没有jQuery可用的功能的中心版本令人不安.当我遇到Dan的解决方案时,我发现有机会为那些喜欢以jQueryOO风格编程的人提供一些东西.请务必向上滚动并在Dan的代码上保留一个upvote.它漂亮而活泼,对我来说就像是一种魅力.

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
//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();
});

用法

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

    if( $('footer').inView() ) {
        // do cool stuff
    }

});



10> Adam Rehal..:

我发现这里接受的答案对于大多数用例而言过于复杂.此代码可以很好地完成工作(使用JQuery),并区分完全可见和部分可见的元素.

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");
    }
});



11> Domysee..:

我在这里遇到的所有答案只检查元素是否位于当前视口内.但这并不意味着它是可见的.
如果给定元素位于具有溢出内容的div内,并且滚动出视图,该怎么办?

要解决这个问题,您必须检查所有父项是否包含该元素.
我的解决方案正是如此:

它还允许您指定要显示的元素数量.

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;
};

该解决方案忽略了由于其他事实而导致元素可能不可见的事实,例如opacity: 0.

我在Chrome和Internet Explorer 11中测试了此解决方案.



12> Randy Casbur..:

新的Intersection Observer API非常直接地解决了这个问题。

此解决方案将需要一个polyfill,因为Safari,Opera和IE尚不支持此功能。(溶液中包含了polyfill)。

在此解决方案中,看不见的框是目标(观察到的)。当它出现时,标题顶部的按钮被隐藏。框离开视图后即显示。

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;
}

推荐阅读
mobiledu2402852357
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有