scroll
事件允许对页面或元素滚动作出反应。我们可以在这里做一些有用的事情。
例如:
- 根据用户在文档中的位置显示/隐藏其他控件或信息。
- 当用户向下滚动到页面末端时加载更多数据。
这是一个显示当前滚动的小函数:
window.addEventListener('scroll', function() {
document.getElementById('showScroll').innerHTML = window.pageYOffset + 'px';
});
在运行中:
Current scroll = scroll the window
scroll
事件在 window
和可滚动元素上都可以运行。
防止滚动
我们如何使某些东西变成不可滚动?
我们不能通过在 onscroll
监听器中使用 event.preventDefault()
来阻止滚动,因为它会在滚动发生 之后 才触发。
但是我们可以在导致滚动的事件上,例如在 pageUp 和 pageDown 的 keydown
事件上,使用 event.preventDefault()
来阻止滚动。
如果我们向这些事件中添加事件处理程序,并向其中添加 event.preventDefault()
,那么滚动就不会开始。
启动滚动的方式有很多,使用 CSS 的 overflow
属性更加可靠。
有几个练习题,你可以解决或者浏览以下几个任务来看一下 onscroll
的应用。
任务
无限的页面
重要程度: 5
创建一个无限的页面。当访问者滚动到页面末端时,它会自动将当期日期时间附加到文本中(以便访问者可以滚动更多内容)。
像这样:
请注意滚动的两个重要特性:
- 滚动是“弹性的”。在某些浏览器/设备中,我们可以在文档的顶端或末端稍微多滚动出一点(超出部分显示的是空白区域,然后文档将自动“弹回”到正常状态)。
- 滚动并不精确。当我们滚动到页面末端时,实际上我们可能距真实的文档末端约 0-50px。
因此,“滚动到末端”应该意味着访问者离文档末端的距离不超过 100px。
P.S. 在现实生活中,我们可能希望显示“更多信息”或“更多商品”。
解决方案
解决方案的核心是一个函数,当我们在页面末端时,该函数可以向页面添加更多日期(或者在实际开发中是加载更多内容)。
我们可以立即调用它,并将其添加为 window.onscroll
处理程序。
最重要的问题是:“如何检测页面滚动到了末端?”
让我们使用相对于窗口的坐标。
文档(document)在 <html>
标签中被表示(被包含)为 document.documentElement
。
我们可以通过 document.documentElement.getBoundingClientRect()
来获取整个文档相对于窗口的坐标。bottom
属性将是文档末端的相对于窗口的坐标。
例如,如果整个 HTML 文档的高度是 2000px
,那么:
// 当我们在页面顶端时
// 相对于窗口 top = 0
document.documentElement.getBoundingClientRect().top = 0
// 相对于窗口 bottom = 2000
// 如果文档太长,那么可能会远远超出窗口底部
document.documentElement.getBoundingClientRect().bottom = 2000
如果我们向下滚动 500px
,那么:
// 文档顶端在窗口之方 500px
document.documentElement.getBoundingClientRect().top = -500
// 文档末端相对于窗口近了 500px
document.documentElement.getBoundingClientRect().bottom = 1500
当我们滚动到文档末端时,假设窗口高度为 600px
:
// 文档顶端在窗口上方 -1400px
document.documentElement.getBoundingClientRect().top = -1400
// 文档末端相对于窗口坐标为 600px
document.documentElement.getBoundingClientRect().bottom = 600
请注意,bottom
不能为 0
,因为它永远不会到达窗口顶部。bottom
坐标的最低限度是窗口高度(我们假设其为 600
),我们无法再向上滚动了。
我们可以获得窗口的高度为 document.documentElement.clientHeight
。
对于本任务,我们需要知道何时文档末端距窗口底部不超过 100px
(即,如果窗口高度为 600px
,则为 600-700px
)。
所以,函数如下:
function populate() {
while(true) {
// 文档末端
let windowRelativeBottom = document.documentElement.getBoundingClientRect().bottom;
// 如果用户将页面滚动了足够远(文档末端距窗口底部 <100px)
if (windowRelativeBottom < document.documentElement.clientHeight + 100) {
// 让我们添加更多数据
document.body.insertAdjacentHTML("beforeend", `<p>Date: ${new Date()}</p>`);
}
}
}
Up/down 按钮
重要程度: 5
创建一个“到顶部”按钮来帮助页面滚动。
它应该像这样运行:
- 页面向下滚动的距离没有超过窗口高度时 —— 按钮不可见。
- 当页面向下滚动距离超过窗口高度时 —— 在左上角出现一个“向上”的箭头。如果页面回滚回去,箭头就会消失。
- 单击箭头时,页面将滚动到顶部。
像这样(左上角,滚动查看):
解决方案
加载可视化图像
重要程度: 4
假设我们有一个速度较慢的客户端,并且希望节省它们在移动端的流量。
为此,我们决定不立即显示图像,而是将其替换为占位符,如下所示:
<img src="placeholder.svg" width="128" height="128" data-src="real.jpg">
因此,最初所有图像均为 placeholder.svg
。当页面滚动到用户可以看到图像位置时 —— 我们就会将 src
更改为 data-src
的 src
,从而加载图像。
这是在 iframe
中的一个示例:
滚动它可以看到图像是“按需”加载的。
要求:
- 加载页面时,屏幕上的那些图像应该在滚动之前立即加载。
- 有些图像可能是常规图像,没有
data-src
。代码不应该改动它们。 - 一旦图像被加载,它就不应该在滚动进/出时被重新加载。
P.S. 如果你有能力,可以创建一个更高级的解决方案,以“预加载”当前位置下方/之后一页的图像。
P.P.S. 仅处理垂直滚动,不处理水平滚动。
解决方案
onscroll
处理程序应该检查哪些图像是可见的,并显示它们。
我们还希望在页面加载时运行它,以检测即将可见的图像并加载它们。
该代码应该在文档加载完成时执行,以便可以访问文档内容。
或者将该代码放在 <body>
底部:
// ...页面内容在上面...
function isVisible(elem) {
let coords = elem.getBoundingClientRect();
let windowHeight = document.documentElement.clientHeight;
// 顶部元素边缘可见吗?
let topVisible = coords.top > 0 && coords.top < windowHeight;
// 底部元素边缘可见吗?
let bottomVisible = coords.bottom < windowHeight && coords.bottom > 0;
return topVisible || bottomVisible;
}
showVisible()
函数使用通过 isVisible()
实现的可见性检查,来加载可见图像:
function showVisible() {
for (let img of document.querySelectorAll('img')) {
let realSrc = img.dataset.src;
if (!realSrc) continue;
if (isVisible(img)) {
img.src = realSrc;
img.dataset.src = '';
}
}
}
showVisible();
window.onscroll = showVisible;
P.S. 此解决方案还有一个 isVisible
的变体,可以“预加载”当前文档滚动上方/下方 1 页内的图像