背景
我们的网站有个「点击加载更多」的功能,就像这样
![5de34fe0158bbd3bc7c0d9bb1643244f.gif](https://i-blog.csdnimg.cn/blog_migrate/69376a55d1fee61680cad32f7dd7b040.gif)
❝点击按钮,拉取数据填充列表,用户自行滚动到下方,继续点击加载更多……
❞
这种场景是不是很常见??我浏览了几个网站,都有这种场景,国内最流行的 Ant Design 组件库更是直接封装了这个功能[1]
本来只是没啥问题的,直到 Chrome V84 的出现…
❝确切的说,是 chromium 84 出了问题,因为最新的 edge 用的 chromium 内核,也出了相同的问题
❞
有一天,收到用户反馈:在点击加载更多后,列表内容像是原地刷新,体验实在不在,就像这样
![728921d02febca86df7cae1772d48b39.gif](https://i-blog.csdnimg.cn/blog_migrate/9c6ef56c1822937e5936ec44381f93bb.gif)
在线体验链接,需要 Chrome 84 哦>[2]
❝点击加载更多,按钮位置始终不变,列表填充后自行向上滚动 ?
❞
看这个 gif 你可能觉得还行,真实场景是会加载更多内容的,这种自行滚动会让用户突然找不到刚刚浏览项的位置,极大破坏用户体验。
或者说,这种做法有一定场景,但是行为控制应该交给前端开发者来定不是?
(废话好像有点多,想看解决方案的直接拉到文末~
怎么发现是 chromium 的 bug/feature ?
收到的反馈,说的是偶现,然后部分用户高频出现。所以我一开始并没有往浏览器层面想,而是自己的代码有没有逻辑漏洞。
在几个浏览器上跑了一遍,发现确实有些浏览器能复现。在确认自己代码天衣无缝之后,我怀疑起了 react ?
为了验证和框架无关,我关闭了 JavaScript ,手动复制列表元素到父节点,还是能够稳定复现。。为了严谨,自己又用原生代码写了一个 demo[3] ,还是能够复现。那么问题就出在这些浏览器身上了。。
这一晚搞到了 11 点多,先回去睡个觉。。
第二天醒来,脑子清醒多了。
先确定复现浏览器的版本,同事装的 Chrome 83 没问题,而自己的 84 出了问题,看来是这次 Chrome 更新的锅。
接着去网络上搜搜有没有人遇到类似的问题。恰好,前一天也有个网友遇到了同样的问题,见 给你代码:chrome84 追加元素的问题[4]
最后去看下更新文档(在此之前我只知道 Chrome 84 调整了 same-site 策略
在 Chrome 84 新特性[5] 文中,并没有提到这个功能。
看来对于官方来说,这种功能改动是很小的,不足以放到 feature 列表中 ?,更多细节提示需要到 commit log 里查看
看来只能去版本提交日志[6]里查下了,在输入了 scroll 关键字后,跳出来的结果有数千条,着实劝退,我还是去提 bug[7] 等待官方解答吧。
影响范围
目前使用 chromium 84 内核的浏览器都受到了影响,包括:
- Chrome 84
- Edge 84
- Android Chrome 84
- Android Webview (默认跟随本地 Chrome 升级而升级,也可以独立维护版本)
啥,没有 iOS ? 因为 iOS 的 Chrome 用的不是 chromium 内核 ?
滚动偏移重置的解决方案
既然浏览器做了滚动,那我们「记住上次滚动位置,加载完后滚回去」不就行了?
试了一下,还真的有效。
完整代码:
html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
<style>.container {
width: 100%;display: flex;flex-direction: column;align-items: center;
}.items {
width: 100%;
}.item {
margin-top: 10px;height: 100px;width: 100%;background-color: #FF142B;
}.btn {
width: 200px;height: 44px;margin-bottom: 40px;background: #BCCFFF;box-shadow: 0 0 3px 0 rgba(0, 0, 0, 0.05);border: none;border-radius: 22px;font-size: 15px;font-weight: 500;color: #FF142B;-webkit-transition: 150ms all;transition: 150ms all;
}style>
<script>function genRandomColor() {
const fn = () => parseInt(Math.random() * (255 + 1), 10)return `rgb(${fn()},${fn()},${fn()})`
}function showMore() {
let items = document.querySelector('.items')let tmp = document.createElement('div')// 记住当前位置const currentScrollTop = document.documentElement.scrollTop || document.body.scrollTop
tmp.className = "item"
tmp.style = `background-color: ${genRandomColor()}`;
items.appendChild(tmp)// 滚回到之前位置window.scrollTo({
top: currentScrollTop
})
}function showMoreWithTimeout(){
setTimeout(showMore,10)
}script>
head>
<body>
<div class="container">
<div class="items">
<div class="item">div>
<div class="item">div>
<div class="item">div>
<div class="item">div>
<div class="item">div>
<div class="item">div>
<div class="item">div>
<div class="item">div>
div>
<button class="btn" onclick="showMore()">点击展开更多button>
div>
body>
html>
虽然可以了,但还是会有以下几个疑问:
- Chrome 84 内部滚动的时机是什么时候?
- 一次事件循环中多次执行 scrollTo ,会发生什么情况?
- 已经执行了 scrollTo ,在 setTimeout 里执行 scrollTo 和在 rAF 里执行 scrollTo ,有什么区别?
- scrollTo 和 scrollBy 同时执行,会发生什么情况?
问题 1 比较复杂,先看其他几个问题