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

(这个问题指的是Firefox。)


当前回答

我发现这里公认的答案对于大多数用例来说过于复杂。这段代码很好地完成了工作(使用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");
    }
});

其他回答

在之前的回答中,大多数用法都没有做到这一点:

-当一个元素的任何像素都是可见的,但不是“一个角落”, -当一个元素大于viewport并且居中时, -大多数只检查文档或窗口内的单个元素。

好吧,对于所有这些问题,我都有一个解决方案,好的方面是:

-你可以返回可见时,只有一个像素从任何一方显示,而不是一个角落, -你仍然可以返回大于viewport的visible while元素, -你可以选择你的父元素,或者你可以自动让它选择。 -也适用于动态添加的元素

如果你检查下面的片段,你会发现在元素的容器中使用溢出滚动的区别不会造成任何麻烦,并且看到与其他答案不同的是,即使一个像素从任何一侧显示,或者当一个元素大于视口时,我们看到的是元素的内部像素,它仍然有效。

用法很简单:

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

一个没有crossSearchAlgorithm的演示,对于大于viewport的元素,检查element3内部像素,可以看到:

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内部时,它无法判断它是否可见,因为我们只检查元素是否从侧面或角落可见。

这个包含了crossSearchAlgorithm,它允许你在元素大于viewport时仍然返回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/

这段代码是为了获得更精确的信息,无论元素的任何部分是否显示在视图中。对于性能选项或仅垂直幻灯片,不要使用此选项!这段代码在绘图情况下更有效。

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

这里是一个演示(尝试调整窗口大小为)

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

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

更新:时间在流逝,我们的浏览器也是如此。这种方法不再被推荐,如果你不需要支持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
  );
}

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

此函数通过递归测试HTML标记之前的任何级别,并在第一个false处停止,解决了当您的元素位于其他可滚动div中的问题。

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

这是一个简单而简单的解决方法,对我来说很有效。

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

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