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

(这个问题指的是Firefox。)


当前回答

新的交集观察者API非常直接地解决了这个问题。

这个解决方案将需要一个polyfill,因为Safari, Opera和Internet Explorer还不支持这个(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>

其他回答

一个更好的解决方案:

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

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

顺便说一下,窗户和窗户的区别。innerWidth和document.documentElement.clientWidth是clientWidth/clientHeight不包括滚动条,while窗口。innerWidth /高。

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>

更新:时间在流逝,我们的浏览器也是如此。这种方法不再被推荐,如果你不需要支持ie7之前的版本,你应该使用Dan的解决方案。

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

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

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

对于类似的挑战,我非常喜欢这个要点,它为scrollIntoViewIfNeeded()暴露了一个填充。

所有必要的功夫都需要回答这个问题:

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;

这指的是你想知道的元素,例如,overTop或overBottom -你只需要得到漂移…

查看verge的源代码,它使用getBoundingClientRect。这就像:

function inViewport (element) {
  if (!element) return false;
  if (1 !== element.nodeType) return false;

  var html = document.documentElement;
  var rect = element.getBoundingClientRect();

  return !!rect &&
    rect.bottom >= 0 &&
    rect.right >= 0 && 
    rect.left <= html.clientWidth &&
    rect.top <= html.clientHeight;
}

如果元素的任何部分在视口中,则返回true。