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

Check if element is partially in viewport

本文关键字:视口 元素 是否部 检查      更新时间:2023-09-26

我正在尝试确定元素是部分还是全部在视口中。

我发现这将确定一个元素是否完全可见,但在尝试确定部分可见性时一直感到困惑。我不想使用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)
    );
}

任何帮助将不胜感激!

回答晚了,但大约一个月前我写了一个函数来做到这一点,它确定一个元素在视口中以百分比为单位的可见量。我在iPhone/iPad上的chrome,Firefox,ie11,ios中对其进行了测试。当元素的 X 百分比(作为 0 到 100 之间的数字)可见时,该函数返回 true。仅确定元素的测量值是否可见,而不确定元素是否隐藏,具有不透明度、可见性等。

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
  )
};
你需要

一个基于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>

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/

如何处理这个问题的现代方法是交叉观察者(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>

你的代码说的是:

    元素的顶部
  • 必须位于窗口的顶部下方,
  • 元素的左侧
  • 必须位于窗口左侧的右侧,
  • 元素的底部必须位于窗口底部的顶部,并且
  • 元素的右侧必须位于窗口右侧的左侧

你想要什么:

    元素的顶部必须低于窗口的
  • 顶部,或者元素的底部必须高于窗口的底部,并且
  • 元素的左侧必须位于窗口左侧的右侧,
  • 或者元素的右侧必须位于窗口右侧的左侧

中获取您将要的内容,代码应该从这里足够简单。

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

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;