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

相关推荐
火星思想4 小时前
都2025年了,还在问构建工具是干嘛的?
前端·前端框架·设计
vvilkim9 小时前
React 与 Vue:两大前端框架的深度对比
vue.js·react.js·前端框架
猿究院_xyz10 小时前
跟着尚硅谷学vue-day5
前端·javascript·vue.js·前端框架·html
前端大白话12 小时前
前端工程师必看!手把手教你用CSS实现超丝滑的自适应视频嵌入
前端·css·前端框架
喝拿铁写前端1 天前
从圣经Babel到现代编译器:没开玩笑,普通程序员也能写出自己的编译器!
前端·架构·前端框架
进取星辰1 天前
13、性能优化:魔法的流畅之道——React 19 memo/lazy
前端·react.js·前端框架
zwjapple1 天前
React 的 useEffect 清理函数详解
前端·react.js·前端框架
Thomas游戏开发1 天前
Unity3D Audio Mixer自定义调制教程
前端框架·unity3d·游戏开发
旅行者1024号2 天前
vue3--手写手机屏组件
javascript·前端框架
知识分享小能手2 天前
JavaScript学习教程,从入门到精通,XMLHttpRequest 与 Ajax 请求详解(25)
开发语言·javascript·学习·ajax·前端框架·css3·html5