framesync.js 的设计思想

framesync.js 是什么?

约定:

  • 前一帧、当前帧、下一帧:指代 上一个 requestAnimationFrame,当前待执行的 requestAnimationFrame,与下一个 requestAnimationFrame。

framesync.js 是对浏览器原生 requestAnimationFrame 函数的封装,它作为 Popmotion.js 动画库的一部分,作为一个独立包分发。

它是一个很简单的库,它暴露出来的接口只有下面的几个:

js 复制代码
import sync,{flushSync,cancelSync,getFrameData} from "framesync"

这些接口本质上是一些提供订阅监听的对象:

  • sync:用来添加订阅
  • cancelSync:用来取消订阅
  • flushSync:用来发布订阅

这些对象都具有五个属性,分别是:read、update、preRender、render、postRender。它们的类型如下:

ts 复制代码
 type T = {
     read:function,
     update:function,
     preRender:function,
     render:function,
     postRender:function
 }

read、update、preRender、render、postRender,本质上是一组按顺序执行的生命周期函数,sync.read 先于 sync.update 之前执行。

这些接口实际上实现的是一个订阅发布模式,不过它与 requestAnimationFrame 关联到一起了,在当前帧的第一次调用 sync.xxx 进行订阅的时候,它会在 requestAnimationFrame 上添加发布函数的回调。由此,sync.xxx 的调用等同于 requestAnimationFrame 的调用。

目前为止,它解决了直接使用 requestAnimationFrame 的一个痛点(执行的顺序是按照其调用顺序,无法在中途插入一个优先级更高的监听),你可以通过在不同的生命周期中添加监听来处理优先级的问题,而无需关心调用顺序。

实际上,因为它也算是使用了适配器模式,sync.xxx 在内部对 requestAnimationFrame 是否存在进行了检测,如果不存在它会使用 setTimeout。也即,解决了 requestAnimationFrame 的另一个痛点:可能存在的兼容性问题。

原生的 requestAnimationFrame 如果要持续的运行下去需要使它自调用,一般会写一些类似下面的逻辑:

js 复制代码
requestAnimationFrame(() => {
    //一些逻辑
    requestAnimationFrame(//...)
})

这实在是麻烦,这种麻烦在 framesync.js 中也得到了封装:

js 复制代码
import sync,{flushSync,cancelSync,getFrameData} from "framesync"

sync.render(() => {
  //一些逻辑
},true)//注意这第二个参数,
//如果它是true,则回调会在每一帧都执行,直到使用cancelSync.render进行取消。

//如果你也希望手动控制,那么你其实也可以这样写
sync.render(() => {
  //这与上面的 requestAnimationFrame 自调用是类似的
  sync.render(//...)//为下一帧添加新订阅

这里再次解决一个痛点(命令式的自调用),将为自调用添加了声明式的语法糖。

另外,添加到 requestAnimationFrame 上的回调我有时候希望它立刻执行,这在 framesync.js中,则是 flushSync 的职责了,它本身就是发布函数,因此调用它就可以很方便的马上触发订阅。

js 复制代码
import sync,{flushSync,cancelSync,getFrameData} from "framesync"

sync.render(() => {})
//一些逻辑后
//希望马上调用sync.render(() => {})中的回调,你可以使用
flushSync.render()
//flushSync.xxx 实际上是一种"合并",
//假设你的代码是:sync.render(() => {},true),也就是它是自调用的,
//那么它本身的代码执行应该是:1 2 3 4 5 ...帧的执行
//如果你调用了 flushSync.xxx,那么 在你调用 flushSync.xxx 的那一帧里,
//(假设是一开始就调用了flushSync.xxx)代码执行会变为:12 3 4 5 ...帧的执行,
//也就是本来 1 2 用两帧处理的逻辑,合并到一帧去处理了。
//当然如果你的逻辑本身就是一帧解决的,那么flushSync.xxx 的作用就是马上处理这些逻辑(同步),
//而不是将其放到 requestAnimationFrame 的异步去处理。

一言以蔽之:flushSync.xxx的作用就是:合并加速。将两帧的逻辑合并到一帧去处理。

最后 getFrameData 是一个用于获取上一帧执行时间戳的函数,其类似于 requestAnimationFrame回调函数接收到的时间戳。

总结:

framesync.js解决了requestAnimationFrame的以下痛点:

  1. 执行的顺序是按照其调用顺序,无法在中途插入一个优先级更高的监听
  2. 可能存在的兼容性问题
  3. 命令式的自调用
  4. 无法立刻触发回调,压缩掉某一帧

有些东西用文字描述是困难的,作者能力有限只能够描述到这种程度,想要更深入的理解,还需要去阅读源码才行。

实际上库的源码并不多,但因为其大量使用了可变变量,可变变量在各函数间相互修改,导致逻辑理解困难。

但其核心其实就是一个订阅监听模式,只需要把握好"sync、cancelSync、flushSync"本质是用于订阅、取消订阅、发布的对象,那么在阅读源码的时候,就可以很轻易的找到主干,能够大大节约源码的理解时间。

相关推荐
李刚大人2 小时前
react-amap海量点优化
前端·react.js·前端框架
想自律的露西西★13 小时前
用el-scrollbar实现滚动条,拖动滚动条可以滚动,但是通过鼠标滑轮却无效
前端·javascript·css·vue.js·elementui·前端框架·html5
hummhumm16 小时前
第 28 章 - Go语言 Web 开发入门
java·开发语言·前端·python·sql·golang·前端框架
幼儿园的小霸王17 小时前
通过socket设置版本更新提示
前端·vue.js·webpack·typescript·前端框架·anti-design-vue
GISer_Jing1 天前
React核心功能详解(一)
前端·react.js·前端框架
鑫宝Code1 天前
【React】React Router:深入理解前端路由的工作原理
前端·react.js·前端框架
沉默璇年1 天前
react中useMemo的使用场景
前端·react.js·前端框架
2401_882727571 天前
BY组态-低代码web可视化组件
前端·后端·物联网·低代码·数学建模·前端框架
红绿鲤鱼2 天前
React-自定义Hook与逻辑共享
前端·react.js·前端框架
zhenryx2 天前
前端-react(class组件和Hooks)
前端·react.js·前端框架