前言
如果你的项目没有引入 jquery,但是又想方便地控制滚动条,这时候,react-smooth-scroll-hoook可能会帮上忙。
useSmoothScroll
用法
useSmoothScroll 核心在于 scrollTo 方法,可传入目标节点或者滚动距离,以自定义的速度滚动到该节点。
1 | export const Demo = () => { |
Demo
核心调用:requestAnimationFrame
浏览器事件循环
宏任务 => 微任务 => 重绘前执行rAF callback => GUI线程渲染
概念
是一个特别的异步任务,只是注册的方法不加入异步队列,而是加入渲染这一边的队列中,它在渲染的三个步骤之前被执行。通常用来处理渲染相关的工作。
- 下次重绘之前执行,处于渲染循环的任务队列中,不属于宏任务或者微任务
- 跟随浏览器的刷新频率
- 非激活状态,动画暂停,节省性能开销。
- 由系统来决定回调函数的执行时机, 如果屏幕刷新率是60Hz,那么回调函数就每16.7ms被执行一次,防止丢帧
- 位于渲染队列,执行在微任务之后,下一个宏任务之前。
rAF的意义和作用
- 使用递归结合rAF实现transition或者animation动画。
- 弥补CSS3动画的一些缺陷,如不能做scrollTop的平滑滚动。
- CSS3只支持部分曲线方程,需要自动以的话只能使用js动画解决即rAF。
- 利用递归入渲染队列的任务,间隔时间根据重绘的频率而定,防止了定时器过快导致掉帧,过慢导致卡顿。
原理
- 初始化容器 dom 节点
- 监听滚动条的状态,如滚动条是否触达容器的两端。
- scrollTo 方法:通过 requestAnimationFrame 去设置容器的 scrollLeft/scrollTop,直到滚动到目标位置。
处理细节
传入目标节点,计算滚动距离
- 如果滚动容器是根元素 html 或者 body,那么直接取当前目标节点的上边距(getBoundingClientRect().top/left),否则,还要减去父容器的上边距。
- 使用
requestAnimationFrame
,递归执行滚动条位移,设置滚动终点,终止递归。
事件监听
- 监听滚动条的属性变化,更新滚动容器的大小。
- 监听窗口的 resize 事件,更新滚动容器的大小。
- 监听滚动容器内子元素的 dom 变化,更新滚动条位置状态。
- 监听容器的 scroll 事件,更新滚动条位置状态。
useScrollWatch
用法
useScrollWatch 用于解决类似导航栏定位的问题,可以获取当前滚动条位于传入 list 节点数组中的哪个节点。
1 | export const ScrollConatainerMode = () => { |
Demo
原理
- 初始化节点 list
- 监听 scroll 事件和子元素的变化,根据滚动条当前的位置,计算出当前滚动条位于 list 中的哪个节点。
处理细节
- 需要注意,滚动条的初始点不是父容器的顶部,而是父容器下第一个子元素的顶部(即需要考虑内边距对滚动容器起点的影响)
搭建文档
生成参数表格
使用 storybook,集成 react-docgen-typescript-loader
,轻松通过 TS 类型自动生成文档。具体配置如下见*.storybook/main.js
*
**
将 stories 文件用于 CodeSandbox
我们可以将文档展示的 demo 一起用于 codesandbox 展示,具体见 ./example
编写测试
单元测试
为工具方法编写单元测试,具体见 ./test/specs
,执行 test 命令
1 | tsdx test --passWithNoTests --config jest.unit.config.js |
e2e 测试
对于组件渲染或者强交互的组件,我们需要通过端到端测试模拟,摆脱人工测试,实现自动化。具体见 ./test/e2e
测试滚动的有效性
我们通过到列表中的某个元素后,如果该元素的上一个元素在视口中不可见,确定滚动的有效性。
经过文档的查阅,我使用了 isIntersectingViewport
这个方法,通过该方法,可以判断某个元素是否真正离开屏幕或者在视口中不可见。
isIntersectingViewport 的缺陷
在这个过程中,我重写了官方的 isIntersectingViewport
方法,让其支持传入一个误差值。
为什么呢?由于滑动的距离是通过运算得出的,无可避免地产生了精度问题,导致某元素即使表面上完全不可见了,却由于精度问题, isIntersectingViewport
返回了错误的判断(例如期望值是 0,可能返回 0.000000001)。
以下为重写代码,可以重点关注增加的 threshold
,传入后 最终的显示比例会加上这个值进行精度修正。
该问题已经向 puppetter 提交了 pr,但尚未处理。https://github.com/puppeteer/puppeteer/pull/6497
1 | async function isIntersectingViewport( |
在这里,我们在使用 puppeteer 提供的 api 的时候,需要注意,回调函数在浏览器环境下执行,而不是在 node 环境下,所以两边的环境变量不一样,是不能相互读取的。
发布
使用 github-action 完成发布,具体脚本,流程如下:
- storybook.yml:在
stories/**
和example.**
或者Reame.md
发生变更的时候,我们执行文档发布作业。 - release.yml: 在
src/**
源码发生变更的时候,我们执行 npm 发布和文档发布作业。