检查元素是否部分在视口中

17

我正在尝试确定一个元素是否部分完全在视口中。

我找到了这个代码,可以确定一个元素是否完全在视图中,但当我尝试确定部分可见性时,经常感到困惑。我不想使用jQuery。

基本上,我的想法是页面上会有一个可能不在视图中的元素。一旦用户将该元素滚动到视图中,即使只是部分地,它也应该触发一个事件。我将通过绑定一个onscroll事件来处理事件触发。我只需要检测正常工作。

function isInViewport(element) {
    var rect = element.getBoundingClientRect();
    var html = document.documentElement;
    return (
        rect.top >= 0 &&
        rect.left >= 0 &&
        rect.bottom <= (window.innerHeight || html.clientHeight) &&
        rect.right <= (window.innerWidth || html.clientWidth)
    );
}

非常感谢任何帮助!


1
不必检查 top >= 0bottom <= html.clientHeight,你可以翻转它们来获取部分可见性,即 bottom <= 0 && top >= html.clientHeight - Alacritas
1
这里有一个很棒的解决方案,可以让您选择完全可见性还是部分可见性:https://github.com/libshin/inViewport/blob/master/index.js - Alacritas
使用Intersection Observer API检测元素是否在视口中,详情请查看此链接 - https://frontendguruji.com/blog/how-to-detect-if-element-is-in-viewport-intersection-observer-api/ - Mandeep Pasbola
6个回答

36
晚来的回答,但大约一个月前,我编写了一个函数,可以准确地确定元素在视口中可见的百分比。我已经在Chrome、Firefox、IE11、iOS上测试过,在iPhone/iPad上也同样适用。该函数返回真,当元素可见的面积占其总面积的X%(作为0到100的数字)时。它仅确定元素的测量是否可见,而不是元素是否隐藏在不透明度、可见性等方面。
const isElementXPercentInViewport = function(el, percentVisible) {
  let
    rect = el.getBoundingClientRect(),
    windowHeight = (window.innerHeight || document.documentElement.clientHeight);

  return !(
    Math.floor(100 - (((rect.top >= 0 ? 0 : rect.top) / +-rect.height) * 100)) < percentVisible ||
    Math.floor(100 - ((rect.bottom - windowHeight) / rect.height) * 100) < percentVisible
  )
};

1
这很酷。有人能解释一下 +-(rect.height / 1) 应该做什么吗?如果它应该翻转高度的符号,那为什么不只是 -rect.height - noblerare
你说得很对,noblerera。这个部分显然是多余的,所以我在帖子中更新了我的答案。 - folo
棒极了,@folo! - insivika
@folo 这非常适合在我需要为没有开发团队的客户抓取产品印象并将其发送到 GA/GA4 时使用。 - johnanish
所以为了得到元素可见部分的百分比,我只需要返回 Math.floor(100 - ((rect.bottom - windowHeight) / rect.height) * 100)。但是你的解决方案中返回语句的第一部分表示什么意思? - Priyanker Rao
显示剩余4条评论

4
你需要基于element.offsetTopelement.offsetLeftelement.offsetHeightelement.offsetWidthwindow.innerWidthwindow.innerHeight的解决方案(根据情况,还可能需要考虑滚动位置)。

function isInViewport(element){
  if(element.offsetTop<window.innerHeight && 
       element.offsetTop>-element.offsetHeight
     && element.offsetLeft>-element.offsetWidth
     && element.offsetLeft<window.innerWidth){
      return true;
    } else {
      
      return false;
    }
}



function test(){
  alert(isInViewport(document.getElementById("elem"))?"Yes":"No"); 
}
#elem{width: 20px; height: 20px; background: red; }
#elem{position: absolute;top: -9px;left: 600px;}
    <div id="elem"></div>
    <button onclick="test()">Check</button>


我该如何将滚动位置因素考虑进去呢?基本上,这个想法是页面上会有一个可能不在视野范围内的元素。一旦用户将该元素滚动到视野范围内,即使只有部分可见,它也应该触发一个事件。我将通过绑定一个onscroll事件来处理事件触发。我只需要检测能够正常工作。感谢您迄今为止的帮助! - Bill Riess
1
https://dev59.com/CHE95IYBdhLWcg3wDpgG - Henrique Barcelos
它说是的,并且它的位置是-9px,这意味着它不完全可见。 - Karue Benson Karue

2

function partInViewport(elem) {
    let x = elem.getBoundingClientRect().left;
    let y = elem.getBoundingClientRect().top;
    let ww = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
    let hw = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
    let w = elem.clientWidth;
    let h = elem.clientHeight;
    return (
        (y < hw &&
         y + h > 0) &&
        (x < ww &&
         x + w > 0)
    );
}

document.addEventListener("scroll", ()=>{
 let el = document.getElementById("test");
 if (partInViewport(el)) {
   document.getElementById("container").style.backgroundColor = "green";
  } else {
   document.getElementById("container").style.backgroundColor = "red";
  }
});
#test {
  height: 200px;
  width: 145px;
  background-color: grey;
}
#container {
  height: 400px;
  width: 345px;
  transform: translate(400px, 360px);
  background-color: red;
  display: grid;
  align-items: center;
  justify-items: center;
}
body {
  height: 1500px;
  width: 1500px;
}
<div id="container">
  <div id="test"></div>
</div>

以下是我对这段代码的示例: https://jsfiddle.net/xqpebwtv/27/


这不考虑父容器的可能偏移量。 - Edwin

2
现代处理此问题的方法是使用Intersection Observer(IO)。使用IO,您可以观察(正如名称所示)元素,并在元素进入视图时触发操作。您可以设置观察器触发的百分比(例如,10%可见,90%可见...)。
我真的很喜欢链接页面上的这个例子,其中有4个不同的元素。每个元素都有不同的触发百分比。

let observers = [];

startup = () => {
  let wrapper = document.querySelector(".wrapper");

  // Options for the observers

  let observerOptions = {
    root: null,
    rootMargin: "0px",
    threshold: []
  };

  // An array of threshold sets for each of the boxes. The
  // first box's thresholds are set programmatically
  // since there will be so many of them (for each percentage
  // point).

  let thresholdSets = [
    [],
    [0.5],
    [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0],
    [0, 0.25, 0.5, 0.75, 1.0]
  ];

  for (let i = 0; i <= 1.0; i += 0.01) {
    thresholdSets[0].push(i);
  }

  // Add each box, creating a new observer for each

  for (let i = 0; i < 4; i++) {
    let template = document.querySelector("#boxTemplate").content.cloneNode(true);
    let boxID = "box" + (i + 1);
    template.querySelector(".sampleBox").id = boxID;
    wrapper.appendChild(document.importNode(template, true));

    // Set up the observer for this box

    observerOptions.threshold = thresholdSets[i];
    observers[i] = new IntersectionObserver(intersectionCallback, observerOptions);
    observers[i].observe(document.querySelector("#" + boxID));
  }

  // Scroll to the starting position

  document.scrollingElement.scrollTop = wrapper.firstElementChild.getBoundingClientRect().top + window.scrollY;
  document.scrollingElement.scrollLeft = 750;
}

intersectionCallback = (entries) => {
  entries.forEach((entry) => {
    let box = entry.target;
    let visiblePct = (Math.floor(entry.intersectionRatio * 100)) + "%";

    box.querySelector(".topLeft").innerHTML = visiblePct;
    box.querySelector(".topRight").innerHTML = visiblePct;
    box.querySelector(".bottomLeft").innerHTML = visiblePct;
    box.querySelector(".bottomRight").innerHTML = visiblePct;
  });
}

startup();
body {
  padding: 0;
  margin: 0;
}

svg:not(:root) {
  display: block;
}

.playable-code {
  background-color: #f4f7f8;
  border: none;
  border-left: 6px solid #558abb;
  border-width: medium medium medium 6px;
  color: #4d4e53;
  height: 100px;
  width: 90%;
  padding: 10px 10px 0;
}

.playable-canvas {
  border: 1px solid #4d4e53;
  border-radius: 2px;
}

.playable-buttons {
  text-align: right;
  width: 90%;
  padding: 5px 10px 5px 26px;
}

.contents {
  position: absolute;
  width: 700px;
  height: 1725px;
}

.wrapper {
  position: relative;
  top: 600px;
}

.sampleBox {
  position: relative;
  left: 175px;
  width: 150px;
  background-color: rgb(245, 170, 140);
  border: 2px solid rgb(201, 126, 17);
  padding: 4px;
  margin-bottom: 6px;
}

#box1 {
  height: 300px;
}

#box2 {
  height: 175px;
}

#box3 {
  height: 350px;
}

#box4 {
  height: 100px;
}

.label {
  font: 14px "Open Sans", "Arial", sans-serif;
  position: absolute;
  margin: 0;
  background-color: rgba(255, 255, 255, 0.7);
  border: 1px solid rgba(0, 0, 0, 0.7);
  width: 3em;
  height: 18px;
  padding: 2px;
  text-align: center;
}

.topLeft {
  left: 2px;
  top: 2px;
}

.topRight {
  right: 2px;
  top: 2px;
}

.bottomLeft {
  bottom: 2px;
  left: 2px;
}

.bottomRight {
  bottom: 2px;
  right: 2px;
}
<template id="boxTemplate">
  <div class="sampleBox">
    <div class="label topLeft"></div>
    <div class="label topRight"></div>
    <div class="label bottomLeft"></div>
    <div class="label bottomRight"></div>
  </div>
</template>

<main>
  <div class="contents">
    <div class="wrapper">
    </div>
  </div>
</main>


0

你的代码所表达的意思是:

  • 元素的顶部必须在窗口的顶部以下,
  • 元素的左侧必须在窗口的左侧右侧,
  • 元素的底部必须在窗口的底部上方,且
  • 元素的右侧必须在窗口的右侧左侧。

你想要的是:

  • 元素的顶部必须在窗口的顶部以下或者元素的底部必须在窗口的底部以上,且
  • 元素的左侧必须在窗口的左侧右侧或者元素的右侧必须在窗口的右侧左侧。

从中获取你需要的内容,代码应该很简单。


0

应该可以了,不需要偏移量,因为我们在比较客户端矩形。

function isPartiallyVisibleInViewport(element, viewport) {
  var bound = element.getBoundingClientRect();
  var bound2 = viewport.getBoundingClientRect();
  return bound.bottom > bound2.top && bound.top < bound2.bottom;
}

此函数仅检查垂直方向,如果您还想检查水平方向,则必须进行扩展:

return bound.bottom > bound2.top && bound.top < bound2.bottom && bound.right > bound2.left && bound.left < bound2.right;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接