有时,网站希望根据用户是否见过它们来不同地对待事物,这是有意义的。例如,我喜欢突出显示新评论的网站(例如:EA Forum 和 LessWrong),如果评论在我没有将其滚动到视图中时不会失去“突出显示”状态,我会更喜欢它们。在编写我的 Mastodon 客户端Shrubgrazer时,我想要一个这样的版本,这样它就会向我显示我以前从未见过的帖子。实现有点繁琐,所以我想写一些关于我采取的方法的文章。
该代码 位于 github 上,如果帖子的顶部和底部都在屏幕上至少出现了半秒,它就会对查看的帖子进行计数。具体来说,每当帖子的顶部或底部进入视口时,它都会设置一个 500 毫秒的计时器,如果计时器触发时它仍在视口内,它会在客户端保留记录。如果这现在意味着顶部和底部都满足标准,它会发送回一个信标,以便服务器可以跟踪所查看的条目。
回到 4-7 年前,这需要一个滚动监听器,使用大量的 CPU,但现代浏览器现在支持IntersectionObserver API。这让我们可以在条目进入或离开视口时获得回调。
我首先创建一个IntersectionObserver
:
常量观察者 = 新的路口观察者( 句柄_相交,{ 根:空, 阈值: [0, 1], });
我们还没有告诉它要观察哪些元素,但是一旦我们这样做了,它就会在这些元素完全进入或完全退出视口时调用handle_intersect
回调。
每个条目的最顶部和最底部都有一个元素,为了开始跟踪该条目,我们告诉观察者它们:
观察者.observe(entry_top); 观察者.observe(entry_bottom);
我们的handle_intersect
回调做了什么?我们维护两组元素 ID: onscreen_top
和onscreen_bottom
,用于跟踪当前屏幕上的内容。该回调使这些设置保持最新状态,并启动 500 毫秒计时器:
函数handle_intersect(条目,观察者){ for(让条目进入){ const 目标=entry.target; const id = target.getAttribute("id"); 常量 is_bottom = target.classList.contains("底部"); 常量屏幕设置 = 是_底部?屏幕底部:屏幕顶部; if (entry.intersectionRatio > 0.99) { onscreen_set.add(id); window.setTimeout(函数() { 屏幕超时( 目标、post_id、is_bottom、onscreen_set); }, 500); start_observation_timer(目标); } else if (entry.intersectionRatio < 0.01) { onscreen_set.delete(id); } } }
onscreen_timeout
有什么作用?它检查该元素是否仍在屏幕上,如果不在屏幕上,则不执行任何操作。这涵盖了像快速滚动这样的事情,其中某些内容在屏幕上出现的时间很短,以至于实际上还没有被看到。否则,如果该元素仍在屏幕上,则会将该元素标记为已查看并停止跟踪它。如果现在条目的顶部和底部都已被查看,它会告诉服务器:
函数屏幕超时( 目标,post_id,is_bottom,onscreen_set){ if (!onscreen_set.has(post_id)) { // 元素离开屏幕太快, // 不要将其视为在屏幕上。 返回; } 观察者.unobserve(目标); 如果(是_底部){ view_bottom.add(post_id); } 别的 { view_top.add(post_id); } if (viewed_top.has(post_id) && view_bottom.has(post_id)) { send_view_ping(post_id); view_top.delete(post_id); view_bottom.delete(post_id); } }
虽然 Shrubgrazer 尚未得到广泛使用(我怀疑我是唯一的用户,因为托管需要一些工作,而且我不为其他任何人托管),但这对我来说效果很好。它使浏览器完成几乎所有的工作,因此速度非常快。