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"本质是用于订阅、取消订阅、发布的对象,那么在阅读源码的时候,就可以很轻易的找到主干,能够大大节约源码的理解时间。

相关推荐
前端三叶草5 小时前
静态分析:现代前端脚手架路由梳理
前端·前端框架
GISer_Jing16 小时前
React-Markdown详解
前端·react.js·前端框架
harry235day1 天前
Flutter getx 状态管理
flutter·前端框架
程序员ys1 天前
微前端是什么?
微服务·架构·前端框架
2501_906801202 天前
BY组态-低代码web可视化组件
前端·物联网·低代码·数学建模·前端框架
sma2mmm2 天前
微前端实现方案对比Qiankun VS npm组件
前端·前端框架·npm
hac13222 天前
利用GitHub Pages快速部署前端框架静态网页
前端框架·github
Shi_haoliu2 天前
各种网址整理-vue,前端,linux,ai前端开发,各种开发能用到的网址和一些有用的博客
linux·前端·javascript·vue.js·nginx·前端框架·pdf
百锦再3 天前
React编程的核心概念:发布-订阅模型、背压与异步非阻塞
前端·javascript·react.js·前端框架·json·ecmascript·html5
Book_熬夜!3 天前
Vue2——组件的注册与通信方式、默认插槽、具名插槽、插槽的作用域
前端·javascript·vue.js·前端框架·ecmascript