Backtop 作为一个不起眼的小组件,api也不会很多,最适合拿来当上手的小组件,这里是基于vue写的组件。
在写一个组件之前,我们要先考虑这个组件的api都有哪些,先把 props 列好,再规划组件怎么写就事半功倍
在做之前也参考了一些业界比较有名的组件库,比如 ant-design、element-ui
- target 触发滚动的对象 默认是 document.documentElement
- visible 是否显示,如果有传值则组件为受控组件,否则根据其他条件来展示(滚动的高度)
- visiblityHeight 滚动高度的阈值,当监听的对象滚动超过这个值时才出现回到顶部的按钮
- right, bottom, className 按钮的样式配置
- slot 自定义回到顶部的dom结构
上面这些props都很好实现,这个组件的灵魂其实就只有 scrollTo
这个功能
我们可以单独实现一个scrollTo
的公共函数,后面其他组件可以直接调用
在一般的情况下,如果只是默认回到顶部,没有滚动时间需求的话,用系统的api是最优选择MDN scroll-behavior
demo1 2 3 4 5 6
| const isSuperScrollBehavior = 'scrollBehavior' in target.style isSuperScrollBehavior && target.scrollTo({ top, behavior: 'smooth' })
|
requestAnimationFrame
如果浏览器不支持(safari)或者我们需要控制滚动的时间怎么办呢?
这时候我们就得手动计算当前滚动值,然后赋值给 target.scrollTop 以达到滚动的效果
一般这种模拟动画执行我们都会用 rAF(requestAnimationFrame),对于不支持rAF的浏览器直接简单粗暴用 setTimeout(cb, 16)
来代替。
滚动的动画我选的是 easeInOutCubic
,关于其他的贝塞尔曲线实现可以看这个仓库bezier-easing
最后再注意一下重复调用scrollTo的绑定问题就可以了
scrollTo1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| const sign = Symbol('elementScrollToEvent');
const easeInOutCubic = t => t < .5 ? (t ** 3) * 4 : (t - 1) * ( 2 * t - 2) ** 2 + 1
const getScroll = (target, isTop) => { const isWindow = target === window; const props = isTop ? 'pageYOffset' : 'PageXOffset'; const methods = isTop ? 'scrollTop' : 'scrollLeft';
return target[isWindow ? props : methods]; };
function scrollTo(target, { top = 0, time } = {}) { const superScrollBehavior = 'scrollBehavior' in target.style;
if (time || !superScrollBehavior) { const startTime = Date.now(); const startScrollTop = getScroll(target, true); const totalOffset = startScrollTop - top;
if (target[sign]) { window.cancelAnimationFrame(target[sign]); }
const frameFunc = () => { const progress = (Date.now() - startTime) / (time || 450);
if (progress < 1) { target.scrollTop = startScrollTop - totalOffset * easeInOutCubic(progress); window.requestAnimationFrame(frameFunc); } else { target.scrollTop = top; } };
target[sign] = window.requestAnimationFrame(frameFunc); } else { target.scrollTo({ top, behavior: 'smooth' }); } }
|