ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

snacks.nvim animate 动画库实战指南:45+ 缓动函数与全局动画调度原理

snacks.nvim animate 动画库实战指南:45+ 缓动函数与全局动画调度原理 snacks.nvim animate 动画库实战指南45 缓动函数与全局动画调度原理【免费下载链接】snacks.nvim A collection of QoL plugins for Neovim项目地址: https://gitcode.com/GitHub_Trending/sn/snacks.nvimsnacks.nvim 内置的animate是一个面向 Neovim 的高效动画库内置超过 45 个缓动easing函数被scroll平滑滚动、indent缩进线动画、dim代码聚焦变暗等模块统一复用。本文基于 docs/animate.md 并结合 lua/snacks/animate/init.lua、lua/snacks/animate/easing.lua 源码完整讲解配置项、API、缓动函数签名以及全局仅一个定时器的调度机制读完即可在自己的插件或配置中安全地驱动动画。核心设计任意时刻至多一个定时器animate库最关键的性能特性是任意时刻全局最多只有一个 timer 在运行它负责驱动所有活跃动画。所有动画的帧率统一由全局fps设置控制而不是每个动画各起一个定时器。从源码看动画的调度采用预计算 定时步进模型lua/snacks/animate/init.lua启动时一次性用缓动函数计算出每一帧的目标值存入self.steps数组创建一个uv.new_timer()以固定step_duration间隔触发self:step(cb)每步回调通过vim.schedule安全地在主循环中执行更新动画当前值。帧间隔由fps决定step_duration math.max(duration / (to - from), 1000 / fps)即单步耗时不会小于1000/fps毫秒保证帧率上限受控源码。每一步都会调用用户提供的回调cb(value, ctx)回调拿到的value就是当前帧已经插值好的数值。动画对象还具备按id去重能力创建新动画时如果active[id]已存在会先stop()旧的再覆盖源码。active表使用了弱引用__mode v动画结束后自动从表中回收避免内存泄漏。Snacks.animate.del(id)也基于这张表实现源码。全局关闭动画在任意时刻都可以一键关闭全部动画这在低配机器或纯文本工作流中非常实用vim.g.snacks_animate false -- 全局关闭 vim.b.snacks_animate false -- 仅在当前 buffer 关闭设置之后scroll、indent、dim以及所有其他基于animate的动画都会一并禁用。从源码看enabled()的判定逻辑是lua/snacks/animate/init.lualocal key snacks_animate .. (opts.name and (_ .. opts.name) or ) return Snacks.util.var(opts.buf, key, true)即若传入name如dim则还会额外检查snacks_animate_dim这类带名字的开关。Snacks.util.varlua/snacks/util/init.lua的取值优先级是buffer 变量vim.b[buf][name]→ 全局变量vim.g[name]→ 默认值true。所以任何动画模块如dim会写vim.g.snacks_animate_dim都可以通过同一套机制被独立开关。安装与配置通过 lazy.nvim 安装配置完整示例见 docs/examples/init.lua-- lazy.nvim { folke/snacks.nvim, ---type snacks.Config opts { animate { -- 你的 animate 配置写在这里 -- 留空则使用默认设置详见下方配置节 } } }配置项详解---class snacks.animate.Config ---field easing? snacks.animate.easing|snacks.animate.easing.Fn { ---type snacks.animate.Duration|number duration 20, -- 每一步的毫秒数步进时长 easing linear, -- 缓动函数名或自定义缓动函数 fps 120, -- 每秒帧数。作用于全局所有动画 }配置项默认值说明duration20可以传数字每步毫秒数也可以传{ step ..., total ... }表格同时给出时取两者中的最小值easinglinear缓动函数名称字符串或满足(t, b, c, d) - number签名的自定义函数fps120全局帧率上限控制所有动画的渲染频率各模块会覆盖自己的默认值。例如dim的默认配置lua/snacks/dim.lua是animate { enabled vim.fn.has(nvim-0.10) 1, -- 仅 Neovim 0.10 启用 easing outQuad, -- 使用二次方出缓动先快后慢 duration { step 20, -- 每步 20ms total 300, -- 动画总时长上限 300ms }, },scrolllua/snacks/scroll.lua与indentlua/snacks/indent.lua也有各自的animate子配置它们都会传给底层的Snacks.animate()。缓动函数签名、参数与 45 函数族所有缓动函数接收相同的一组参数t时间应取值从 0 到 durationbbegin属性的起始值cchange属性的结束值减去起始值dduration动画总时长---alias snacks.animate.easing.Fn fun(t: number, b: number, c: number, d: number): number部分函数支持额外修饰参数例如 elastic弹性系列还可以接收振幅a和周期p未提供时使用默认值lua/snacks/animate/easing.lua。easing.lua 中实现的完整函数清单每个函数都有in/out/inOut/outIn四个方向变体函数族名称以in为例特点线性linear匀速唯一的单一函数二次inQuad/outQuad/inOutQuad/outInQuad缓入缓出最常用outQuad是dim默认三次inCubic/outCubic/inOutCubic/outInCubic比 Quad 更陡的加速/减速四次inQuart/outQuart/inOutQuart/outInQuart更强的缓动感五次inQuint/outQuint/inOutQuint/outInQuint更强的缓动感正弦inSine/outSine/inOutSine/outInSine基于三角函数柔和指数inExpo/outExpo/inOutExpo/outInExpo指数级加速/减速圆形inCirc/outCirc/inOutCirc/outInCirc基于圆弧曲线弹性inElastic/outElastic/inOutElastic/outInElastic带振荡回弹可传a振幅、p周期回退inBack/outBack/inOutBack/outInBack先越过目标再回落默认过冲系数s 1.70158弹跳inBounce/outBounce/inOutBounce/outInBounce落地弹跳效果这些函数改编自 Penners Easing EquationsBSD 协议参考 Emmanuel Oga 的 easing 库与 kikito 的 tween.lua 的缓动函数总览覆盖了前端与动画领域最主流的缓动曲线总计 45 个以上。在 lua/snacks/animate/init.lua 中easing配置项按如下方式解析local easing self.opts.easing or linear easing type(easing) string and require(snacks.animate.easing)[easing] or easing也就是说传字符串会在easing.lua中按名查找传函数则直接作为缓动函数使用——这为自定义缓动曲线留了口子。Duration按步 vs 总时长duration可以指定为总时长或每步时长两者同时给出时取最小值---class snacks.animate.Duration ---field step? number 每步时长毫秒 ---field total? number 总时长毫秒源码中的计算逻辑lua/snacks/animate/init.lua为若duration是表格取其step字段否则把数字本身当作step若指定了step则duration step * abs(to - from)再与total若指定取最小值若只指定total则直接使用兜底默认 250ms。因此每步时长模式下动画总时长会随插值距离自动伸缩而total字段用于限制最长时间防止长距离动画过慢。API 详解Snacks.animate(from, to, cb, opts?)模块本身是 callable 的Snacks.animate()等价于Snacks.animate.add()通过setmetatable的__call实现见 lua/snacks/animate/init.lua。---type fun(from: number, to: number, cb: snacks.animate.cb, opts?: snacks.animate.Opts): snacks.animate.Animation Snacks.animate()Snacks.animate.add(from, to, cb, opts?)添加一个动画返回Animation对象---param from number ---param to number ---param cb snacks.animate.cb ---param opts? snacks.animate.Opts Snacks.animate.add(from, to, cb, opts)opts继承Config并额外支持---class snacks.animate.Opts: snacks.animate.Config ---field buf? number 可选检查该 buffer 是否启用动画 ---field int? boolean 是否把插值结果取整为整数 ---field id? number|string 动画唯一标识同 id 的新动画会顶掉旧动画回调上下文---class snacks.animate.ctx ---field anim snacks.animate.Animation -- 当前动画对象 ---field prev number -- 上一帧的值 ---field done boolean -- 是否最后一帧---alias snacks.animate.cb fun(value:number, ctx: snacks.animate.ctx)值得注意的细节当from to时动画不会启动定时器直接以done true调用一次回调源码最后一帧的值被强制设置为精确的to源码保证动画终点精确无误差若opts.int true且缓动为linear会采用整数值插值优化路径先算好每步整数增量one_step并把残余差值delta从缓动计算中扣除源码int true时每一帧都会math.floor(value 0.5)四舍五入源码步数下限为 10 步step_count math.max(math.floor(duration / step_duration 0.5), 10)即使距离很短也保证有足够帧数形成平滑过渡。Snacks.animate.del(id)删除停止指定 id 的动画---param id number|string Snacks.animate.del(id)Snacks.animate.enabled(opts?)检查动画是否启用返回false的情况包括全局snacks_animate被置为false、或 buffer 局部变量snacks_animate为false---param opts? {buf?: number, name?: string} Snacks.animate.enabled(opts)传name时会额外检查snacks_animate_name这个开关如snacks_animate_dim、snacks_animate_indent、snacks_animate_scroll因此可以做到全局关闭 按模块精细开关两级控制。实战scroll / indent / dim 如何使用动画看三个真实调用点能更直观地理解opts的用法平滑滚动lua/snacks/scroll.lua把滚动多少行从 0 插值到scrolls每帧回调里用keepjumps normal!执行滚动、垂直/水平移动光标命令实现光标与视口同步平滑移动。它传入的opts带int true行号必须是整数、id按窗口区分动画以及buf检查启用状态并且区分普通滚动与重复滚动animate_repeat由按键重复延迟阈值触发两套动画参数。缩进线动画lua/snacks/indent.lua当代码 scope 变化时把缩进高亮范围0 → scope.to - scope.from插值回调中按value与ctx.prev逐步推进 extmark 的绘制范围id indent_scope_ .. win保证同一窗口的旧动画被顶替。聚焦变暗lua/snacks/dim.lua同时启动两条动画分别把 scope 的from上边界与to下边界从旧值插值到新值每帧回调里更新 dim 的 extmark 范围并redraw。注意两条动画的id分别为snacks_dim_from_ .. win与snacks_dim_to_ .. win互不冲突。这三个模块都先通过Snacks.animate.enabled({ buf buf, name xxx })判断是否允许动画如scroll还会额外排除 terminal buffer、录制宏等场景不允许时直接跳变如indent直接redraw_range、dim直接渲染最终 scope保证任何环境下都有兜底行为。自定义动画的最小示例基于上面的 API可以这样写一个把数字从 0 平滑动画到 100 的调用Snacks.animate(0, 100, function(value, ctx) -- value 为当前帧插值结果 print((progress%d done%s prev%d):format(value, ctx.done, ctx.prev)) end, { easing outQuad, -- 先快后慢 duration { step 20, total 500 }, -- 每步 20ms总时长上限 500ms int true, -- 输出取整 id my_progress, -- 可被新动画顶替 })若希望该动画受全局/局部开关约束可在回调开头用Snacks.animate.enabled()判断也可在配置里通过opts.animate子表如dim、indent、scroll所做统一注入easing、duration等参数。小结snacks.nvim的animate库在极小的 API 表面下封装了完整的动画基础设施45 缓动函数、统一的(t, b, c, d)签名、step/total双模式时长、整数插值、按 id 去重、弱引用回收以及全局单定时器 fps 上限的性能模型。理解它之后你既可以通过vim.g.snacks_animate false一键关闭scroll/indent/dim的全部动画也可以直接调用Snacks.animate()在自有插件中复用它。进一步可阅读 docs/animate.md、lua/snacks/animate/init.lua 与 lua/snacks/animate/easing.lua 对照学习。【免费下载链接】snacks.nvim A collection of QoL plugins for Neovim项目地址: https://gitcode.com/GitHub_Trending/sn/snacks.nvim创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表