Jsonz bug-log

后续更新会放在 github.com/jsonz1993/blog

0%

记录第一个vue组件 Backtop

Backtop 作为一个不起眼的小组件,api也不会很多,最适合拿来当上手的小组件,这里是基于vue写的组件。

在写一个组件之前,我们要先考虑这个组件的api都有哪些,先把 props 列好,再规划组件怎么写就事半功倍

在做之前也参考了一些业界比较有名的组件库,比如 ant-designelement-ui

  • target 触发滚动的对象 默认是 document.documentElement
  • visible 是否显示,如果有传值则组件为受控组件,否则根据其他条件来展示(滚动的高度)
  • visiblityHeight 滚动高度的阈值,当监听的对象滚动超过这个值时才出现回到顶部的按钮
  • right, bottom, className 按钮的样式配置
  • slot 自定义回到顶部的dom结构

上面这些props都很好实现,这个组件的灵魂其实就只有 scrollTo 这个功能

ScrollTo

我们可以单独实现一个scrollTo的公共函数,后面其他组件可以直接调用

scrollBehavior

在一般的情况下,如果只是默认回到顶部,没有滚动时间需求的话,用系统的api是最优选择MDN scroll-behavior

demo
1
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的绑定问题就可以了

scrollTo
1
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'); // 这里可以直接项目的规范不用Symbol

// 缓动函数
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'
});
}
}