IntersectionObserver API 详解篇(6)

除了通过在元素身上添加新属性来记录上次回调触发时是进还是出外,我还想到另外一个办法,那就是给 threshold 选项设置一个很小的接近 0 的临界值,比如 0.000001,然后再用 entry.intersectionRatio > 0 判断,这样就不会受贴边的情况影响了,也就不会受滚动速度影响了:

<div>不可见,以任意速度向下滚动</div> <div></div> <style> #info { position: fixed; } #target { position: absolute; top: 200%; width: 100px; height: 100px; background: red; } </style> <script> let observer = new IntersectionObserver(([entry]) => { if (entry.intersectionRatio > 0) { info.textContent = "可见了" } else { info.textContent = "不可见,以任意速度向下滚动" } }, { threshold: [0.000001] }) observer.observe(target) </script>

目标元素不是根元素的后代元素的话会怎样?

如果在执行 observe() 时,目标元素不是根元素的后代元素,浏览器也并不会报错,Chrome 从 53 开始会对这种用法发出警告(是我提议的),从而提醒开发者这种用法有可能是不对的。为什么不更严格点,直接报错?因为元素的层级关系是可以变化的,可能有人会写出这样的代码:

<div></div> <div></div> <style> #target { width: 100px; height: 100px; background: red; } </style> <script> let observer = new IntersectionObserver(() => alert("看见我了"), {root: root}) observer.observe(target) // target 此时并不是 root 的后代元素,Chrome 控制台会发出警告:target element is not a descendant of root. root.appendChild(target) // 现在是了,触发回调 </script>

又或者被 observe 的元素此时还未添加到 DOM 树里:

<div></div> <style> #target { width: 100px; height: 100px; background: red; } </style> <script> let observer = new IntersectionObserver(() => alert("看见我了"), {root: root}) let target = document.createElement("div") // 还不在 DOM 树里 observer.observe(target) // target 此时并不是 root 的后代元素,Chrome 控制台会发出警告:target element is not a descendant of root. root.appendChild(target) // 现在是了,触发回调 </script>

也就是说,只要在相交发生时,目标元素是根元素的后代元素,就可以了,执行 observe() 的时候可以不是。

是后代元素还不够,根元素必须是目标元素的祖先包含块

要求目标元素是根元素的后代元素只是从 DOM 结构上说的,一个较容易理解的限制,另外一个不那么容易理解的限制是从 CSS 上面说的,那就是:根元素矩形必须是目标元素矩形的祖先包含块(包含块也是链式的,就像原型链)。比如下面这个 demo 所演示的,两个做随机移动的元素 a 和 b,a 是 b 的父元素,但它俩的 position 都是 fixed,导致 a 不是 b 的包含块,所以这是个无效的观察操作,尝试把 fixed 改成 relative 就发现回调触发了:

<div> <div></div> </div> <div>0%</div> <style> #a, #b { position: fixed; /* 尝试改成 relative */ width: 200px; height: 200px; opacity: 0.8; } #a { background: red } #b { background: blue } #info { width: 200px; margin: 0 auto; } #info::before { content: "Intersection Ratio: "; } </style> <script> let animate = (element, oldCoordinate = {x: 0, y: 0}) => { let newCoordinate = { x: Math.random() * (innerWidth - element.clientWidth), y: Math.random() * (innerHeight - element.clientHeight) } let keyframes = [oldCoordinate, newCoordinate].map(coordinateToLeftTop) let duration = calcDuration(oldCoordinate, newCoordinate) element.animate(keyframes, duration).onfinish = () => animate(element, newCoordinate) } let coordinateToLeftTop = coordinate => ({ left: coordinate.x + "px", top: coordinate.y + "px" }) let calcDuration = (oldCoordinate, newCoordinate) => { // 移动速度为 0.3 px/ms return Math.hypot(oldCoordinate.x - newCoordinate.x, oldCoordinate.y - newCoordinate.y) / 0.3 } animate(a) animate(b) </script> <script> let thresholds = Array.from({ length: 200 }, (k, v) => v / 200) // 200 个临界值对应 200px new IntersectionObserver(([entry]) => { info.textContent = (entry.intersectionRatio * 100).toFixed(2) + "%" }, { root: a, threshold: thresholds }).observe(b) </script>

从 DOM 树中删除目标元素会怎么样?

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wwfjsd.html