在 web 的开发过程中,有时我们需要实现当点击一个按钮或超链接时,立刻滚动跳转到本页面中指定的位置。对此有很多的实现方式,但大体可以分为以下两类:
- 通过 html 锚点实现
- 通过 js 实现
- 通过 scrollTo() 实现
- 通过 Element.scrollIntoView()实现
今天的这篇文章,将讨论上述两类的实现原理与各自的优缺点。鉴于各种实现的 html 结构与 css 样式是共有的,所以我先将实现示例的共有的 html 结构与 css 样式列出来。
html 结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <html> <ul class="list"> <a name="topAnchor"></a> <li class="list__item" id="top">我是顶部</li> <li class="list__item"></li> <li class="list__item"></li> <li class="list__item"></li> <li class="list__item"></li> <li class="list__item"></li> </ul> <div class="operation">
<button class="operation__back-top" id="backTopBtn2"> 回到顶部 </button> <button class="operation__back-top" id="backTopBtn3"> 回到顶部 </button> </div> </html>
|
css 样式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <style> .list li { display: flex; align-items: center; justify-content: center; height: 12vh; margin-bottom: 10px; background-color: aqua; } .operation { line-height: 120px; text-align: center; } </style>
|
通过锚点实现
对于这种实现方式,我们只需要在 html 结构的”代码片段 1”注释处插入以下代码:
1
| <a href="#topAnchor">回到顶部</a>
|
或者采用 js 生成 html 的方式:
先在”代码片段 1”注释处插入以下代码:
1 2 3
| <button class="operation__back-top" id="backTopBtn1"> 回到顶部 </button>
|
再添加下面的 javascript 代码:
1 2 3 4 5 6 7 8 9 10 11
| const backTopBtn1 = document.getElementById('backTopBtn1'); backTopBtn1.addEventListener('click', function(e) { const aNode = document.createElement('a'); aNode.href = '#topAnchor'; e.target.appendChild(aNode); aNode.onclick = function(e) { e.stopPropagation(); }; aNode.click(); e.target.removeChild(aNode); });
|
上述方法,在点击 a 标签或者按钮后,我们会看到页面立马跑到顶部。对于交互性要求不高的项目,这种做法没什么问题,同时不同浏览器之间的兼容性问题也不大。但如果我们的项目交互性要求高了之后就不行了,这种一下子就跑到页面顶部的交互方式会让用户觉得有些突兀。通过查询资料,可以在 style 中设置 html, body { scroll-behavior:smooth; }
让过度变得比较平滑一些。但更好的解决方式还是通过 js 来实现。下面我将着重介绍通过 js 的实现方式。
此 api 需要传递 DOM 元素相对于 window 的 left 和 top 的距离,当然它还有一个 behavior 参数,将其设置为 smooth 后,过度就会比较平滑一些。步骤如下:
- 计算目标元素距离顶部的距离
- 通过事件触发
实现代码如下:
1 2 3 4 5 6
| const backTopBtn2 = document.getElementById('backTopBtn2'); const TOP = document.getElementById('top'); const y = TOP.offsetTop; backTopBtn2.addEventListener('click', function(e) { window.scrollTo({ top: y, left: 0, behavior: 'smooth' }); });
|
相较于第一种实现方式,scrollTo
支持动画,使得页面跳转会更丝滑,但它对 iframe 的支持度不够。
该 api 相较于上一个,节点信息更加的明确,操作方法也更加的简洁,更利于后续的维护。实现代码如下:
1 2 3 4 5
| const backTopBtn3 = document.getElementById('backTopBtn3'); const TOP = document.getElementById('top'); backTopBtn3.addEventListener('click', function(e) { TOP.scrollIntoView({ behavior: 'smooth' }); });
|
从效果上来看,该 api 和 scrollTo 的作用是一致的,但是从代码结构上来说,scrollIntoView 会更加的简洁且在 iframe 中表现也很优秀,基本上被用到的频率更高。
然后,参考 eleUI 中的 backTop 组件源码,我又做了下面这个版本:
1 2 3 4 5 6 7 8 9 10 11 12
| const distance = 70; function up() { let start = document.documentElement.scrollTop; function gotop() { scrollTo(0, start - distance); start = start - distance; if (start > 0) { window.requestAnimationFrame(gotop); } } window.requestAnimationFrame(gotop); }
|
eleUI 中的 backTop 组件源码的实现方式是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const cubic = value => Math.pow(value, 3); const easeInOutCubic = value => value < 0.5 ? cubic(value * 2) / 2 : 1 - cubic((1 - value) * 2) / 2;
scrollToTop() { const el = this.el; const beginTime = Date.now(); const beginValue = el.scrollTop; const rAF = window.requestAnimationFrame || (func => setTimeout(func, 16)); const frameFunc = () => { const progress = (Date.now() - beginTime) / 500; if (progress < 1) { el.scrollTop = beginValue * (1 - easeInOutCubic(progress)); rAF(frameFunc); } else { el.scrollTop = 0; } }; rAF(frameFunc); }
|
上面的源码中,用了一个数学曲线来搞的滚动,这样可以让无论页面内容多高,回到顶部都是差不多的时间。而我的却是写死的 70,这样界面越长,回到顶部就会花越多的时间。
参考资料: