确定元素(IntersectionObserver)覆盖了多少视口

时间:2019-09-04 10:03:25

标签: javascript intersection-observer

我正在使用IntersectionObserver在元素进入视口时向其添加和删除类。

我不是说“当元素的X%可见时-添加此类”,而是说“当元素的X%可见时,当X%的视口被覆盖时按元素-添加此类”。

我假设这是不可能的?如果是这样,我认为IntersectionObserver有点缺陷,因为如果元素的高度是视口的10倍,则除非将阈值设置为10%或更小,否则它将永远不会被视为可见。而且,当您具有可变高度的元素时,尤其是在自适应设计中,必须将阈值设置为0.1%左右,以“确保”该元素将获得该类(尽管您永远无法真正确定)。

编辑:响应Mose的回复。

Edit2:更新了几个阈值,以强制其更频繁地计算percentOfViewport。还是不理想。

var observer = new IntersectionObserver(function (entries) {
	entries.forEach(function (entry) {
		var entryBCR = entry.target.getBoundingClientRect();
		var percentOfViewport = ((entryBCR.width * entryBCR.height) * entry.intersectionRatio) / ((window.innerWidth * window.innerHeight) / 100);

		console.log(entry.target.id + ' covers ' + percentOfViewport + '% of the viewport and is ' + (entry.intersectionRatio * 100) + '% visible');

		if (entry.intersectionRatio > 0.25) {
			entry.target.style.background = 'red';
		}
		else if (percentOfViewport > 50) {
			entry.target.style.background = 'green';
		}
		else {
			entry.target.style.background = 'lightgray';
		}
	});
}, {threshold: [0.025, 0.05, 0.075, 0.1, 0.25]});

document.querySelectorAll('#header, #tall-content').forEach(function (el) {
	observer.observe(el);
});
#header {background: lightgray; min-height: 200px}
#tall-content {background: lightgray; min-height: 2000px}
<header id="header"><h1>Site header</h1></header>
<section id="tall-content">I'm a super tall section. Depending on your resolution the IntersectionObserver will never consider this element visible and thus the percentOfViewport isn't re-calculated.</section>

2 个答案:

答案 0 :(得分:0)

let optionsViewPort = {
  root: document.querySelector('#viewport'), // assuming the viewport has an id "viewport"
  rootMargin: '0px',
  threshold: 1.0
}

let observerViewport = new IntersectionObserver(callback, optionsViewPort);
observerViewPort.observe(target);

在回调中,给定视口的大小,给定元素的大小,给定重叠的百分比,您可以计算视口中的重叠百分比:

  const percentViewPort = viewPortSquarePixel/100;
  const percentOverlapped = (targetSquarePixel * percent ) / percentViewPort;

示例:

const target = document.querySelector('#target');
const viewport = document.querySelector('#viewport');
const optionsViewPort = {
  root: viewport, // assuming the viewport has an id "viewport"
  rootMargin: '0px',
  threshold: 1.0
}

let callback = (entries, observer) => { 
  entries.forEach(entry => {  
    const percentViewPort = (parseInt(getComputedStyle(viewport).width) * parseInt(getComputedStyle(viewport).height))/100;    
    const percentOverlapped = ((parseInt(getComputedStyle(target).width) * parseInt(getComputedStyle(viewport).height)) * entry.intersectionRatio) / percentViewPort;
    console.log("% viewport overlapped", percentOverlapped);
    console.log("% of element in viewport", entry.intersectionRatio*100);
    // Put here the code to evaluate percentOverlapped and target visibility to apply the desired class
  });
    
};

let observerViewport = new IntersectionObserver(callback, optionsViewPort);
observerViewport.observe(target);
#viewport {
  width: 900px;
  height: 900px;
  background: yellow;
  position: relative;
}

#target {
  position: absolute;
  left: 860px;
  width: 100px;
  height: 100px;
  z-index: 99;
  background-color: red;
}
<div id="viewport">
  <div id="target" />
</div>

使用getBoundingClientRect()计算目标的重叠面积/百分比的替代数学

const target = document.querySelector('#target');
const viewport = document.querySelector('#viewport');

const rect1 = viewport.getBoundingClientRect();
const rect2 = target.getBoundingClientRect();

const rect1Area = rect1.width * rect1.height;
const rect2Area = rect2.width * rect2.height;

const x_overlap = Math.max(0, Math.min(rect1.right, rect2.right) - Math.max(rect1.left, rect2.left));
const y_overlap = Math.max(0, Math.min(rect1.bottom, rect2.bottom) - Math.max(rect1.top, rect2.top));

const overlapArea = x_overlap * y_overlap;
const overlapPercentOfTarget = overlapArea/(rect2Area/100);

console.log("OVERLAP AREA", overlapArea);
console.log("TARGET VISIBILITY %", overlapPercentOfTarget);
#viewport {
  width: 900px;
  height: 900px;
  background: yellow;
  position: relative;
}

#target {
  position: absolute;
  left: 860px;
  width: 100px;
  height: 100px;
  z-index: 99;
  background-color: red;
}
<div id="viewport">
  <div id="target" />
</div>

答案 1 :(得分:0)

您需要做的是给每个元素一个不同的阈值。如果元素短于默认阈值(相对于窗口),则默认阈值可以正常工作,但是如果阈值较高,则需要该元素的唯一阈值。

假设您要触发以下任一元素:

  1. 50%可见或
  2. 覆盖屏幕的50%

然后您需要检查:

  1. 如果元素短于窗口的50%,则可以使用选项1
  2. 如果元素的高度大于窗口的50%,则需要为其设置阈值,即窗口的高度除以元素的高度乘以阈值(50%):
function doTheThing (el) {
    el.classList.add('in-view');
}

const threshold = 0.5;

document.querySelectorAll('section').forEach(el => {
    const elHeight = el.getBoundingClientRect().height;
    var th = threshold;

    // The element is too tall to ever hit the threshold - change threshold
    if (elHeight > (window.innerHeight * threshold)) {
        th = ((window.innerHeight * threshold) / elHeight) * threshold;
    }

    new IntersectionObserver(iEls => iEls.forEach(iEl => doTheThing(iEl)), {threshold: th}).observe(el);
});