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

时间:2008-09-23 21:24:56

标签: javascript html firefox dom browser

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

(问题是指Firefox)

27 个答案:

答案 0 :(得分:1283)

现在most browsers支持getBoundingClientRect方法,这已成为最佳做法。使用旧答案为very slownot accuratehas several bugs

选择正确的解决方案是almost never precise。你可以read more了解它的错误。


此解决方案在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() */
    );
}

如何使用:

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

将以下代码放在<body>代码的底部:

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应该处理zoom/pinch跨浏览器,否则firstsecond链接可以帮助你。

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

永远不会仅在jQuery $(document).ready()中使用它,因为此时there is no warranty CSS has been applied。您的代码可以在硬盘驱动器上本地使用CSS,但是一旦放在远程服务器上,它就会失败。

DOMContentLoaded被解雇后,styles are applied,但the images are not loaded yet。因此,我们应该添加window.onload事件监听器。

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

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

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

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

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

答案 1 :(得分:318)

更新:时间推移,我们的浏览器也是如此。 不再推荐使用此技术如果您不需要支持IE&lt; 7,则应使用@ Dan的解决方案(https://stackoverflow.com/a/7557433/5628)。

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

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

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

答案 2 :(得分:149)

更新

在现代浏览器中,您可能需要查看具有以下优势的Intersection Observer API

  • 比收听滚动事件更好的表现
  • 适用于跨域iframe
  • 可以判断一个元素是否阻碍/交叉另一个

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


以前的回答

answer provided by Dan存在一些问题,这些问题可能会使某些问题变得不合适。其中一些问题在他的答案附近指出,他的代码将给出以下元素的误报:

  • 被测试者面前的另一个元素隐藏
  • 父母或祖先元素的可见区域之外
  • 使用CSS clip属性
  • 隐藏的元素或其子元素

simple test的以下结果证明了这些限制:

Failed test, using 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/

结果:

Passed test, using isElementVisible

附加说明

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

element.getBoundingClientRect()document.elementFromPoint()都是CSSOM工作草案规范的一部分,并且至少在IE 6及更高版本以及大多数桌面浏览器中受到长时间支持(尽管,不完美)。有关详细信息,请参阅Quirksmode on these functions

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

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

答案 3 :(得分:57)

我尝试Dan's answer 然而用于确定边界的代数意味着该元素必须同时≤视口大小并完全在视口内才能获得true,导致漏报。如果要确定元素是否在视口中,ryanve's answer已关闭,但正在测试的元素应与视口重叠,请尝试以下操作:

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

答案 4 :(得分:30)

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

顺便说一句,window.innerWidth和document.documentElement.clientWidth之间的difference是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);
}

试验情况下

<!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>

答案 5 :(得分:25)

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

答案 6 :(得分:24)

查看使用vergegetBoundingClientRect来源。就像:

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 :(得分:23)

我更短更快的版本。

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/

答案 8 :(得分:19)

我发现令人不安的是,没有可用的jQuery中心版本的功能。当我遇到Dan's solution时,我发现有机会为那些喜欢以jQuery OO风格编程的人提供一些东西。请务必向上滚动并在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
    }

});

答案 9 :(得分:8)

我发现这里接受的答案对于大多数用例而言过于复杂。此代码可以很好地完成工作(使用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");
    }
});

答案 10 :(得分:5)

我在这里遇到的所有答案只检查元素是否位于当前视口内。但并不代表它是可见的 如果给定元素位于具有溢出内容的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中测试过此解决方案。

答案 11 :(得分:4)

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

答案 12 :(得分:3)

我认为这是一种更实用的方法。 Dan的答案在递归上下文中不起作用。

当你的元素在其他人可滚动的div中时,通过以递归方式测试HTML标记的任何级别来解决问题,并在第一个false中停止。

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

答案 13 :(得分:2)

基于@ dan的上述解决方案(https://stackoverflow.com/a/7557433/5628),我开始清理实施,以便在同一页面上多次使用它更容易:

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

});

我使用它的方式是当元素滚动到视图中时,我添加了一个触发css关键帧动画的类。它非常简单,当您在页面上有条件地设置动画时,可以使用10多个内容。

希望它有所帮助!

答案 14 :(得分:2)

我们现在有一个原生的 javascript Intersection Observer API 我们可以从中检测元素是否在视口中。

这是一个例子

const el = document.querySelector('#el')
const observer = new window.IntersectionObserver(([entry]) => {
  if (entry.isIntersecting) {
    console.log('ENTER')
    return
  }
  console.log('LEAVE')
}, {
  root: null,
  threshold: 0.1, // set offset 0.1 means trigger if atleast 10% of element in viewport
})

observer.observe(el);
body {
  height: 300vh;
}

#el {
  margin-top: 100vh;
}
<div id="el">this is element</div>

答案 15 :(得分:1)

这是我的解决方案,如果元素隐藏在可滚动容器中,它将起作用。

Here's a demo(尝试重新调整窗口大小)

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 its within the document viewport
    return top <= document.documentElement.clientHeight;
};

我只需要检查它是否在Y轴上可见(对于滚动ajax加载更多记录功能)。

答案 16 :(得分:1)

对我有用的简单小巧的解决方案。

示例您想要查看元素在具有溢出滚动的父元素中是否可见。

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

答案 17 :(得分:1)

可以轻松获得IMO:

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

答案 18 :(得分:1)

注意大家!

此处的大多数用法在以下几点均失败:

  

-当元素的任何像素可见但“ 一个角”不可见时,

     

-当元素大于视口并居中时,

     

-他们中的大多数仅检查文档或窗口内部的单个元素

对于所有这些问题,我都有一个解决方案,其优点是:

  

-您可以返回visible,当任何一侧的一个像素只出现而不是一个角时,

     

-当元素大于视口时,您仍然可以返回visible

     

-您可以选择parent element,也可以自动选择它,

     

-也可以处理动态添加的元素

如果您查看下面的代码片段,您将看到在元素容器中使用overflow-scroll的区别不会造成任何麻烦,并且即使您从屏幕上看到一个像素,也会看到在此处不喜欢其他答案 任何一侧,或者当元素大于视口时,我们会看到元素的内部像素仍然有效。

用法很简单:

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

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

// For checking elements visibility even if its bigger then viewport
isVisible(element, null, true) // Without parent choise
isVisible(element, parent, true) // With parent choise

不包含crossSearchAlgorithm的演示,对于大于视口的元素,检查元素3的内部像素很有用,以查看:

function isVisible(element, parent, crossSearchAlgorithm) {
    var rect = element.getBoundingClientRect(),
    		prect = (parent != undefined) ? parent.getBoundingClientRect() : element.parentNode.getBoundingClientRect(),
        csa = (crossSearchAlgorithm != undefined) ? crossSearchAlgorithm : false,
        efp = function (x, y) { return document.elementFromPoint(x, y) };
    // Return false if it's not in the viewport
    if (rect.right < prect.left || rect.bottom < prect.top || rect.left > prect.right || rect.top > prect.bottom) {
        return false;
    }
    var flag = false;
    // Return true if left to right any border pixel reached 
    for (var x = rect.left; x < rect.right; x++) {
    	if (element.contains(efp(rect.top, x)) || element.contains(efp(rect.bottom, x))) {
        flag = true;
        break;
      }
    }
    // Return true if top to bottom any border pixel reached 
    if (flag == false) {
      for (var y = rect.top; y < rect.bottom; y++) {
        if (element.contains(efp(rect.left, y)) || element.contains(efp(rect.right, y))) {
          flag = true;
          break;
        }
      }
    }
    if(csa) {
      // Another algorithm to check if element is centered and bigger than viewport
      if (flag == false) {
        var x = rect.left;
        var y = rect.top;
        // From top left to bottom right
        while(x < rect.right || y < rect.bottom) {
          if (element.contains(efp(x,y))) {
            flag = true;
            break;
          }
          if(x < rect.right) { x++; }
          if(y < rect.bottom) { y++; }
        }
        if (flag == false) {
          x = rect.right;
          y = rect.top;
          // From top right to bottom left
          while(x > rect.left || y < rect.bottom) {
            if (element.contains(efp(x,y))) {
              flag = true;
              break;
            }
            if(x > rect.left) { x--; }
            if(y < rect.bottom) { y++; }
          }
        }
      }
    }
    return flag;
}

// Check multiple elements visibility
document.getElementById('container').addEventListener("scroll", function() {
	var elementList = document.getElementsByClassName("element");
  var console = document.getElementById('console');
	for (var i=0; i < elementList.length; i++) {
  	// I did not define parent so it will be element's parent
    if (isVisible(elementList[i])) {
  		console.innerHTML = "Element with id[" + elementList[i].id + "] is visible!";
      break;
    } else {
    	console.innerHTML = "Element with id[" + elementList[i].id + "] is hidden!";
    }
  }
});

// Dynamically added elements
for(var i=4; i <= 6; i++) {
  var newElement = document.createElement("div");
  newElement.id = "element" + i;
  newElement.classList.add("element");
  document.getElementById('container').appendChild(newElement);
}
#console { background-color: yellow; }
#container {
  width: 300px;
  height: 100px;
  background-color: lightblue;
  overflow-y: auto;
  padding-top: 150px;
  margin: 45px;
}
.element {
  margin: 400px;
  width: 400px;
  height: 320px;
  background-color: green;
}
#element3 {
  position: relative;
  margin: 40px;
  width: 720px;
  height: 520px;
  background-color: green;
}
#element3::before {
  content: "";
  position: absolute;
  top: -10px;
  left: -10px;
  margin: 0px;
  width: 740px;
  height: 540px;
  border: 5px dotted green;
  background: transparent;
}
<div id="console"></div>
<div id="container">
    <div id="element1" class="element"></div>
    <div id="element2" class="element"></div>
    <div id="element3" class="element"></div>
</div>

您看到当您位于element3内时,它无法告诉它是否可见,因为我们仅检查元素是否从侧面 corners可见

其中包括crossSearchAlgorithm,当元素大于视口时,它仍可以返回visible

function isVisible(element, parent, crossSearchAlgorithm) {
    var rect = element.getBoundingClientRect(),
    		prect = (parent != undefined) ? parent.getBoundingClientRect() : element.parentNode.getBoundingClientRect(),
        csa = (crossSearchAlgorithm != undefined) ? crossSearchAlgorithm : false,
        efp = function (x, y) { return document.elementFromPoint(x, y) };
    // Return false if it's not in the viewport
    if (rect.right < prect.left || rect.bottom < prect.top || rect.left > prect.right || rect.top > prect.bottom) {
        return false;
    }
    var flag = false;
    // Return true if left to right any border pixel reached 
    for (var x = rect.left; x < rect.right; x++) {
    	if (element.contains(efp(rect.top, x)) || element.contains(efp(rect.bottom, x))) {
        flag = true;
        break;
      }
    }
    // Return true if top to bottom any border pixel reached 
    if (flag == false) {
      for (var y = rect.top; y < rect.bottom; y++) {
        if (element.contains(efp(rect.left, y)) || element.contains(efp(rect.right, y))) {
          flag = true;
          break;
        }
      }
    }
    if(csa) {
      // Another algorithm to check if element is centered and bigger than viewport
      if (flag == false) {
        var x = rect.left;
        var y = rect.top;
        // From top left to bottom right
        while(x < rect.right || y < rect.bottom) {
          if (element.contains(efp(x,y))) {
            flag = true;
            break;
          }
          if(x < rect.right) { x++; }
          if(y < rect.bottom) { y++; }
        }
        if (flag == false) {
          x = rect.right;
          y = rect.top;
          // From top right to bottom left
          while(x > rect.left || y < rect.bottom) {
            if (element.contains(efp(x,y))) {
              flag = true;
              break;
            }
            if(x > rect.left) { x--; }
            if(y < rect.bottom) { y++; }
          }
        }
      }
    }
    return flag;
}

// Check multiple elements visibility
document.getElementById('container').addEventListener("scroll", function() {
	var elementList = document.getElementsByClassName("element");
  var console = document.getElementById('console');
	for (var i=0; i < elementList.length; i++) {
  	// I did not define parent so it will be element's parent
    // and It will do crossSearchAlgorithm
    if (isVisible(elementList[i],null,true)) {
  		console.innerHTML = "Element with id[" + elementList[i].id + "] is visible!";
      break;
    } else {
    	console.innerHTML = "Element with id[" + elementList[i].id + "] is hidden!";
    }
  }
});
// Dynamically added elements
for(var i=4; i <= 6; i++) {
  var newElement = document.createElement("div");
  newElement.id = "element" + i;
  newElement.classList.add("element");
  document.getElementById('container').appendChild(newElement);
}
#console { background-color: yellow; }
#container {
  width: 300px;
  height: 100px;
  background-color: lightblue;
  overflow-y: auto;
  padding-top: 150px;
  margin: 45px;
}
.element {
  margin: 400px;
  width: 400px;
  height: 320px;
  background-color: green;
}
#element3 {
  position: relative;
  margin: 40px;
  width: 720px;
  height: 520px;
  background-color: green;
}
#element3::before {
  content: "";
  position: absolute;
  top: -10px;
  left: -10px;
  margin: 0px;
  width: 740px;
  height: 540px;
  border: 5px dotted green;
  background: transparent;
}
<div id="console"></div>
<div id="container">
    <div id="element1" class="element"></div>
    <div id="element2" class="element"></div>
    <div id="element3" class="element"></div>
</div>

与JSFiddle一起玩:http://jsfiddle.net/BerkerYuceer/grk5az2c/

如果我有更多时间,可以进一步改进它,为有需要的人创建一个新的库。

特别感谢 @Andy E 向我提供这种观点。

重要编辑:无论视图中是否显示元素的任何部分,均使用此代码来获取更精确的信息。对于性能选项或仅垂直幻灯片,请勿使用此功能!该代码在绘图情况下更有效。

答案 19 :(得分:0)

检查元素是否至少部分在视图中(垂直尺寸):

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

答案 20 :(得分:0)

这是一个告诉元素在元素的当前视口中是否可见的函数:

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

答案 21 :(得分:0)

更好的解决方案:

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

答案 22 :(得分:0)

最简单的解决方案,因为compatibilityElement.getBoundingClientRect()十分完善:

function inView(el) {
    let box = el.getBoundingClientRect();
    return box.top < window.innerHeight && box.bottom >= 0;
}

答案 23 :(得分:0)

在Android上放大chrome时,可接受的答案不起作用。要与Dan's answer结合使用,要说明Android上的Chrome,必须使用visualViewport。以下示例仅考虑垂直检查,并使用jQuery作为窗口高度:

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

答案 24 :(得分:0)

这是检查给定元素在其父元素中是否完全可见的片段:

export const visibleInParentViewport = (el) => {
  const elementRect = el.getBoundingClientRect();
  const parentRect = el.parentNode.getBoundingClientRect();

  return (
    elementRect.top >= parentRect.top &&
    elementRect.right >= parentRect.left &&
    elementRect.top + elementRect.height <= parentRect.bottom &&
    elementRect.left + elementRect.width <= parentRect.right
  );
}

答案 25 :(得分:-1)

我使用这个功能(它只检查y是否是屏幕,因为大部分时间不需要x)

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

}

答案 26 :(得分:-2)

对于类似的挑战,我非常喜欢this gist,它为scrollIntoViewIfNeeded()公开了一个polyfill。

所有必要的功夫都需要回答:

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指的是你想要知道的元素,如果它是overTopoverBottom - 只是应该得到漂移......