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

(这个问题指的是Firefox。)


当前回答

一个更好的解决方案:

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的答案,然而,用于确定边界的代数意味着元素必须既≤视口大小,又完全在视口内才能为真,很容易导致假否定。如果你想确定一个元素是否在视口中,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() */;
}

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

-当一个元素的任何像素都是可见的,但不是“一个角落”, -当一个元素大于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/

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

这检查元素是否至少部分在视图中(垂直维度):

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

我使用这个函数(它只检查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;
    }

}

我的更短更快的版本:

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/