是否有一种有效的方法来判断DOM元素(在HTML文档中)当前是否可见(出现在视口中)?
(这个问题指的是Firefox。)
是否有一种有效的方法来判断DOM元素(在HTML文档中)当前是否可见(出现在视口中)?
(这个问题指的是Firefox。)
当前回答
对于类似的挑战,我非常喜欢这个要点,它为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 -你只需要得到漂移…
其他回答
我发现这里公认的答案对于大多数用例来说过于复杂。这段代码很好地完成了工作(使用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");
}
});
Domysee的答案https://stackoverflow.com/a/37998526接近正确。
许多示例使用“完全包含在视口中”,他的代码使用百分比来允许部分可见。他的代码还解决了“是否是父视图剪切”的问题,大多数示例都忽略了这个问题。
一个缺失的元素是父对象滚动条的影响——getBoundingClientRect返回父对象的外部矩形(包含滚动条),而不是内部矩形(不包含滚动条)。子滚动条可以隐藏在父滚动条后面,当它不可见时,它被认为是可见的。
推荐的观察者模式不适合我的用例:使用方向键更改表中当前选择的行,并确保新选择是可见的。使用观察器进行此操作将过于复杂。
这是一些代码
它包括一个额外的hack (fudgeY),因为我的表有一个粘头,不是通过直接的方式检测(自动处理这个将是相当乏味的)。此外,对于所需的可见分数,它使用十进制(0到1)而不是百分比。(对于我的例子,我需要完整的y, x是不相关的)。
function intersectRect(r1, r2) {
var r = {};
r.left = r1.left < r2.left ? r2.left : r1.left;
r.top = r1.top < r2.top ? r2.top : r1.top;
r.right = r1.right < r2.right ? r1.right : r2.right;
r.bottom = r1.bottom < r2.bottom ? r1.bottom : r2.bottom;
if (r.left < r.right && r.top < r.bottom)
return r;
return null;
}
function innerRect(e) {
var b,r;
b = e.getBoundingClientRect();
r = {};
r.left = b.left;
r.top = b.top;
r.right = b.left + e.clientWidth;
r.bottom = b.top + e.clientHeight;
return r;
}
function isViewable(e, fracX, fracY, fudgeY) {
// ref https://stackoverflow.com/a/37998526
// intersect all the rects and then check the result once
// innerRect: mind the scroll bars
// fudgeY: handle "sticky" thead in parent table. Ugh.
var r, pr, er;
er = e.getBoundingClientRect();
r = er;
for (;;) {
e = e.parentElement;
if (!e)
break;
pr = innerRect(e);
if (fudgeY)
pr.top += fudgeY;
r = intersectRect(r, pr);
if (!r)
return false;
}
if (fracX && ((r.right-r.left) / (er.right-er.left)) < (fracX-0.001))
return false;
if (fracY && ((r.bottom-r.top) / (er.bottom-er.top)) < (fracY-0.001))
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>
在Android上放大谷歌Chrome浏览器时,最被接受的答案是不工作的。结合Dan的回答,要考虑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);
新的交集观察者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>