@lovince/motion

中文文档
0.4.0发布于 2026/11/02MITTypeScriptES Module

与框架无关的运动系统:原生CSS显示,管理GSAP时间线和滚动场景,以及Lenis平滑滚动-强制清理和减少运动处理。

中文使用文档
文档目录23 节
  1. 条目
  2. 原则
  3. 令牌
  4. 模式
  5. 原语
  6. 页面转换
  7. 适配器
  8. 谁拥有什么
  9. 快速启动
  10. 该模型
  11. 减少运动
  12. 渐进增强
  13. CSS层
  14. GSAP层
  15. 平滑滚动
  16. 预设
  17. 框架
  18. 表演
  19. 测试
  20. 版本控制
  21. 贡献
  22. 故障排除
  23. 许可证

@lovince /运动

与框架无关的运动系统。 CSS 处理轻的界面运动, GSAP 编排时间线和滚动场景,以及 微弱的 提供了 平滑滚动。每个动画都有一个所有者,一个清理和一个 reduced-motion政策。

状态: 0.4.0、实验。 API之前可能会更改 1.0.

bun add @lovince/motion
bun add gsap lenis      # only for the entries that use them

我们的目标不是让所有东西都有动画效果。它是让一个网站做的动作 具有一致性、可中断性和可访问性。

条目

每个引擎都位于自己的条目后面,因此应用程序只需为其导入的内容付费 一个缺失的可选节点不会破坏其他任何东西。

进口需要给了
@lovince/motion- - - - - -createMotion的偏好, revealOnScroll, fadeIn, splitText, focusTarget, whenMotionAllowed,动作配置和令牌
@lovince/motion/gsapgsapcreateTimeline, createStagger, createScrollReveal, createParallax, createHorizontalScroll, gsapScrollDriver
@lovince/motion/scrollleniscreateSmoothScroll
@lovince/motion/presetsgsapheroEntrance, revealText, revealImage, followState, createPageTransition
@lovince/motion/vuevueuseMotion, useMotionEffect, v-reveal
@lovince/motion/primitivesgsapinitMotion 以及每个数据属性原语
@lovince/motion/primitives/<name>gsap (scroll 也 lenis)一个原始的: reveal, split, scrub, parallax, magnetic, marquee, liquid, ripple, progress, headroom, transition, scroll
@lovince/motion/adapters/vue · react · svelte · astrogsap +框架vMotion, useMotion, motion 行动, bindAstro
@lovince/motion/styles.css- - - - - -令牌+实用程序+预设+基本样式(或 tokens.css, utilities.css, presets.css, primitives.css 他们自己)

gsap, lenis, vue 和 react 是可选的同伴,所以你的应用程序控制他们 版本,并且在运行时永远不会有第二个副本。

原则

  • 克制。 运动解释了变化;它从不为自己而装饰。12-48px旅行。
  • 进入时要放松,离开时要放松。 出口运行于 exitRatio (0.65×)的入口。
  • 可中断。 吐温类使用 overwrite: 'auto' 和 quickTo所以一个新的手势永远不会等着旧的手势。
  • 只有变换和不透明度,再加上 clip-path 用于显示和液体指示。
  • 符号,而不是原始数字。 持续时间,轻松度,距离和错开来自 @lovince/tokens.
  • 简化运动是一级模式,而不是事后的想法:内容立即显示,循环保持不变。
  • 每个原语都返回一个清理操作。

令牌

集团值CSS
持续时间Xs 0.2·sm 0.4·md 0.7·lg 1.1·xl 1.6 (s)--motion-duration-{xs…xl}
缓解出 expo.out ·inOut power3.inOut ·在 power2.in ·软 power2.out ·液体(弹簧)--motion-ease-{out,in-out,in,soft,glide,liquid}
距离Sm 12·md 24·lg 48(在16px根处,按间距比例)--motion-distance-{sm,md,lg}
错开紧固0.04·底座0.06·松动0.1 (s)--motion-stagger-{tight,base,loose}
exitRatio0.65--motion-exit-ratio
import { motionTokens, exitDuration } from '@lovince/motion'
gsap.to(el, { y: 0, duration: motionTokens.duration.md, ease: motionTokens.ease.out })
gsap.to(el, { autoAlpha: 0, duration: exitDuration(motionTokens.duration.md), ease: motionTokens.ease.in })

测试让CSS镜像和TypeScript的值保持同步。

模式

Lovince的界面已经以几种可识别的方式移动。 motionPatterns 将它们命名为新的工作匹配:

模式使用时机
state颜色,边框,背景变化micro,标准ease
move指示器,拇指,药片的更换位置morph,强调轻松
enter弹窗,祝酒,物品到货enter,强调轻松
exit什么离开0.65× enter,放松
liquid分节拇指,TAB药丸,选择overlay,春沉降
scene卷轴显示,英雄scene.md, expo.out
loop遮罩,旋转器,微光线性,当离开屏幕或被询问时暂停

原语

标记页面,然后初始化一次:

<script>document.documentElement.classList.add('js')</script>
<link rel="stylesheet" href="@lovince/motion/styles.css" />

<header data-headroom>…</header>
<div data-scroll-progress></div>
<h1 data-split="lines">Big things, gently</h1>
<p data-scrub>Words light up as you scroll past them</p>
<h2 data-kinetic="scramble">Signal acquired</h2>
<div data-reveal="lg" data-delay="0.1">…</div>
<li data-reveal="pop">…</li>
<img data-parallax="0.2" src="…" alt="" />
<a data-magnetic="0.3" href="#">Book now</a>
<button data-ripple>Pick seat</button>
<div data-marquee="40" data-marquee-velocity>
  <ul data-marquee-track>…</ul>
  <button data-marquee-toggle>Pause</button>
  <button data-marquee-nudge="4">Faster</button>
</div>
<div data-liquid role="tablist">
  <span data-liquid-indicator></span>
  <button data-liquid-item role="tab" aria-selected="true">Day</button>
  <button data-liquid-item role="tab" aria-selected="false">Week</button>
</div>
import { initMotion } from '@lovince/motion/primitives'
import { createScroll } from '@lovince/motion/primitives/scroll'

const motion = initMotion({ scroll: createScroll() })
motion.destroy()
属性价值做
data-revealsm · md · lg · pop随着画面滚动而逐渐淡出; pop 弹簧在
data-splitlines · words字体加载后分割;被蒙面的线条从自己的边缘升起
data-scrub调暗不透明度,默认0.18点亮文字,擦洗卷轴
data-kinetictilt · letters · flip · blur · scramble · wipe · fill · slide · marker动态类型;填充和滑动跟随滚动,其余播放一次; <mark> 获得绘制的高亮(--kinetic-marker)
data-parallax速度,默认为0.15飘浮在卷轴上
data-magnetic强度,默认0.3拉向指针,仅悬停设备
data-marqueePx /s,默认为40,反向为负无缝循环;暂停悬停,聚焦,离开屏幕和打开 data-marquee-toggle (2.2.2 WCAG)
data-liquid- - - - - -一颗药丸横跨两种物质,挤压、过度并落在活性物质上
data-ripple- - - - - -一个环从压点展开
data-scroll-progress- - - - - -scaleX 跟踪页面进度
data-headroom偏移量px,默认值420隐藏向下滚动,显示向上滚动或聚焦
data-delay秒添加到显露或分裂中
data-lenis-prevent- - - - - -让内部滚动条本地滚动

每个原语也可以单独调用,如 (root?, options?) => Cleanup:

import { liquid } from '@lovince/motion/primitives/liquid'
const stop = liquid('#filters')

液体指示器跟随组件已经报告的内容: aria-selected, aria-pressed, aria-checked, aria-current 或者一个 data-state 的 on, active 或者 checked因此,它可以与Lovince标签、切换组和分段控件一起使用,而无需连接。

FOUC警卫。 与一个 js 课上 <html>,显示和分割目标开始隐藏在CSS中,所以它们不会在脚本运行之前闪烁。没有JavaScript,在减少运动和清理后,它们是可见的。 initMotion 如果缺少类,则添加该类。

页面转换

transition() 遵循一个顺序,所以滚动,触发器和焦点永远不会不一致:

停止滚动→退出→销毁旧的原语→交换→滚动到顶部→init→刷新→开始滚动→进入

import { transition } from '@lovince/motion/primitives/transition'
const flow = transition({ scroll, init: (container) => initMotion({ root: container }).destroy, focus: 'h1' })
await flow.navigate(async () => { document.querySelector('main')!.replaceWith(await fetchPage(url)) })

适配器

<section v-motion="['reveal', 'liquid']">…</section>
const ref = useMotion<HTMLDivElement>({ primitives: ['reveal'] })
return <div ref={ref}>…</div>
<section use:motion={{ primitives: ['reveal'] }}>…</section>
<script>import { bindAstro } from '@lovince/motion/adapters/astro'; bindAstro()</script>

谁拥有什么

引擎拥有
CSS悬停,焦点和按压反馈,下划线,小过渡,装饰循环,简单的滚动显示
GSAP英雄序列,交错和分割排版,滚动链接的场景,固定,水平画廊,页面过渡
微弱的当两者同时使用时,在GSAP的时钟上平滑滚动,作为每个滚动条的一个服务

给每个动画一个所有者。永远不要用CSS和 马上GSAP。

快速启动

import '@lovince/motion/styles.css'
import { createMotion, revealOnScroll } from '@lovince/motion'
import { gsapScrollDriver } from '@lovince/motion/gsap'
import { heroEntrance } from '@lovince/motion/presets'
import { createSmoothScroll } from '@lovince/motion/scroll'

const motion = createMotion({ root: '#app' })

heroEntrance({ label: '.eyebrow', heading: 'h1', body: '.lede', actions: '.ctas', media: '.hero-image' }, { context: motion })
revealOnScroll('[data-reveal]', { context: motion })
const scroll = createSmoothScroll({ driver: gsapScrollDriver() })

// When this view goes away:
motion.destroy()   // reverts every tween, trigger, observer and split it owns
scroll.destroy()

该模型

  • createMotion() 是所有者。 它将选择器限定为根,保存每个 效果和清理,和 destroy() 将它们全部还原,首先是最新的。每个API 需要 { context }. 如果没有上下文,调用将获得自己的上下文并返回 清理。
  • 当首选项改变时,效果会重新运行。 效果设置并返回 其撤销。如果用户打开或关闭减少运动,而页面是 打开,效果撤消,再次运行 scope.reduced 更新。 进程会继续,所以一个完成的入口永远不会重播。
  • 结果,而不是暂停。 任何结束的东西都会返回一个解决的承诺 有一次, 'finished' 或者 'cancelled',从不拒绝。摧毁了 拥有上下文将取消它。没有什么是等待计时器的。
  • 一个词汇。 入口 fade, fade-up, fade-down, slide-left, slide-right, scale 和 clip 在CSS中是一样的 显示, fadeIn (Web Animations)和每个GSAP API。
  • 令牌决定。 持续时间,曲线和行进距离来自 @lovince/tokens. defaultMotionConfig 使用 duration.travel 和 ease.out (power3.out),以及CSS --motion-* 属性是别名 的 --lovince-* 所以CSS和GSAP的运行方式是一样的。
const config: MotionConfig = { duration: 0.72, ease: 'power3.out', stagger: 0.08, respectReducedMotion: true }

减少运动

一个核心政策是:under prefers-reduced-motion: reduce,运动被取代 它的最终状态,不仅仅是缩短。

API在减速运动下
revealOnScroll, createScrollReveal显示内容;没有什么是隐藏的
fadeIn, createTimeline,预设跳到最终状态;承诺解决 finished
createParallax, createHorizontalScroll不创建;静态布局为
revealText文本永远不会被分割
followState各州立即适用
createSmoothScroll微弱的;本机滚动,实时切换

样式表有相同的规则,所以只有css的动作也遵循它。集 respectReducedMotion: false 只为了有意义的动作。

渐进增强

内容永远不会被单独的CSS隐藏。显示只隐藏一个元素 设置 data-motion-state="hidden" 从脚本中删除它。如果 JavaScript、GSAP或样式表无法加载,页面只是静态的。 在服务器上没有效果运行,并且每个条目都安全导入。

CSS层

<a class="motion-link" href="/docs">Docs</a>
<section data-motion-reveal="fade-up">…</section>
<h1 class="motion-fade-up" style="--motion-delay: 0.1s">Hello</h1>

revealOnScroll(targets, { effect, once, threshold, rootMargin, stagger }) 使用 IntersectionObserver,没有GSAP。一起进入的元素得到a --motion-delay 错开。 fadeIn(targets, { effect, distance, duration, stagger }) 运行Web动画入口 fill: backwards,所以没有 之后徘徊。

公用事业: .motion-link, .motion-lift, .motion-press, .motion-fade-up, .motion-fade-in, .motion-scale-in, .motion-spin, .motion-pulse. 所有的 它们都是可选类,不会触及没有请求它的元素。

GSAP层

const intro = createTimeline((tl, scope) => {
  tl.from(scope.select('h1'), { autoAlpha: 0, y: 28 })
    .from(scope.select('.lede'), { autoAlpha: 0, y: 18, duration: 0.5 }, '-=0.4')
}, { context: motion })

await intro.finished          // 'finished' | 'cancelled'
  • createTimeline(build, { context, vars, scrollTrigger }):青少年是 在毁灭时恢复。一个 scrollTrigger 在减速运动下下降。
  • createStagger(targets, { effect, stagger, onScroll })收藏,收藏,收藏 文档顺序。
  • createScrollReveal(targets, { effect, start, repeat }):批次 一起进入。
  • createParallax(targets, { speed, axis }):线性,擦洗,测量一次。 保持 speed 小。
  • createHorizontalScroll(section, track, { minWidth }):针在 minWidth 和更广泛;较窄的屏幕保持了简单的布局。选择屏幕外的项目可将其滚动到视图中。
  • splitText(element, { by, mask, onResplit }):单词、字符或行。它 保持内联标记,通过处理任何脚本 Intl.Segmenter,阅读全文 文字到屏幕阅读器,并在宽度改变时重新拆分行。 revert() 恢复原节点。

ScrollTrigger在第一次使用时注册,而不是在导入时注册。

平滑滚动

const scroll = createSmoothScroll({ driver: gsapScrollDriver(), lenis: { lerp: 0.1 } })
scroll.scrollTo('#pricing', { offset: -64 })
scroll.stop() // e.g. while a modal is open

每个卷轴有一个服务。第二个呼叫相同的卷轴 共享实例和最后一个实例 destroy() 把它拆了,所以是组件 每个人都可以安全地要求它。与 gsapScrollDriver()Lenis在GSAP上运行 并保持ScrollTrigger同步:一个帧循环,而不是两个。没有一个 驱动程序,Lenis运行自己的循环。更喜欢原生滚动应用程序,长表单 以及内容丰富的页面。

预设

  • heroEntrance({ label, heading, body, actions, media }):阅读顺序; 重叠成一个入口。 actions 孩子蹒跚。
  • revealText(target, { by: 'words' | 'lines' | 'chars', onScroll }):行 揭示从面具后面升起,并在调整大小的地方重建。
  • revealImage(frames, { scale }):画面向上打开 从一个轻微的变焦。只有剪辑路径和变换改变。
  • followState(target, { attribute, states }):动画到 组件报告(例如: data-state 从 @lovince/ui-vue),所以菜单可以 门关着的时候,不要往外看。
  • createPageTransition({ leave, enter, focus }): await leave(old)交换, enter(next). 每次通话结束,新的通话取消最后一个,然后集中注意力 移动到新的页面。

框架

香草。 在DOM存在后创建上下文,并调用 destroy() 当视图被移除时。

Vue / next。

<script setup lang="ts">
import { useMotion, useMotionEffect, vReveal } from '@lovince/motion/vue'
import { createSmoothScroll } from '@lovince/motion/scroll'
import { gsapScrollDriver } from '@lovince/motion/gsap'
import { heroEntrance } from '@lovince/motion/presets'

const root = ref<HTMLElement | null>(null)
useMotion((motion) => heroEntrance({ heading: 'h1', body: 'p' }, { context: motion }), { root })
const scroll = useMotionEffect(() => createSmoothScroll({ driver: gsapScrollDriver() }))
</script>

<template>
  <section ref="root"><h1>Hello</h1><p v-reveal="'fade-up'">…</p></section>
</template>

一切从mount开始,在unmount时恢复。期间没有任何运行 服务器呈现。

反应。

useLayoutEffect(() => {
  const motion = createMotion({ root: ref.current })
  heroEntrance({ heading: 'h1' }, { context: motion })
  return motion.destroy // Strict Mode's double run is safe: destroy reverts fully
}, [])

苗条的。

<script>
  import { onMount } from 'svelte'
  import { createMotion, revealOnScroll } from '@lovince/motion'
  let root
  onMount(() => {
    const motion = createMotion({ root })
    revealOnScroll('[data-reveal]', { context: motion })
    return motion.destroy
  })
</script>

表演

  • 只有 transform, opacity 而且,对于揭示, clip-path 动画。
  • 视差和固定测量通过ScrollTrigger的刷新,从来没有 滚动事件。只有当宽度改变时,才重新测量线分割。
  • 没有永久的 will-change. GSAP每间隔设置一次。
  • 一个时钟:当两个人都在场时,莱尼斯就会使用GSAP的计时器。
  • 主条目小于6kb gzip,没有引擎。GSAP和Lenis只能加载 通过他们的条目。
  • 每个原语小于1.5 KB gzip;他们都有 initMotion 保持在6 KB以下。

测试

  • 泄漏: 后 destroy(), ScrollTrigger.getAll().length 回到原来的位置。
  • 减少运动: matchMedia 是嘲笑;内容显示,没有分裂,循环保持不变,首选项更改重新运行。
  • 令牌同步: CSS镜像会解析出与TypeScript令牌相同的值。

版本控制

Semver。更改令牌值是一个 主要的 改变,因为它改变了每个网站的感觉。新的标记、原语和属性是次要的。修复是补丁。在1.0之前,小版本可能会改变行为,发行说明也会这样说。

贡献

  • 使用令牌,而不是原始的持续时间、时间或距离
  • 动画转换和不透明度(或说明为什么不)
  • 返回使内容可见的清理操作
  • 处理减少的运动,并有一个测试
  • 有泄漏测试
  • 没有目标时从不扔
  • 保持在它的规模预算之内

故障排除

  • 内容保持隐藏: 这个 js 类设置,但脚本失败。加载动作脚本之前,任何可能抛出,或放弃类。
  • 在错误的地方引发火灾: 有什么东西在装载后改变了高度。呼叫 motion.refresh(); createScroll 一旦字体加载就会刷新。
  • 平滑滚动与内部滚动对抗: 添加 data-lenis-prevent 到它。
  • 什么都不能动: 检查操作系统的运动减少设置。这是预期的行为。
  • Marquee仍然是: 轨道还没有宽度,或者速度为0。给它内容 data-marquee-track.

许可证

麻省理工学院

版本与发布记录

@lovince/motion 共发布 5 个版本,首次发布于 2026/10/16;过去 12 个月发布了 5 个版本。

版本发布日期距今
0.4.0latest2026/11/021 天前
0.3.02026/11/021 天前
0.2.02026/11/011 天前
0.1.02026/10/1618 天前
0.0.0-stage2026/10/1618 天前

关于 @lovince/motion 的常见问题

@lovince/motion 是什么?

与框架无关的运动系统:原生CSS显示,管理GSAP时间线和滚动场景,以及Lenis平滑滚动-强制清理和减少运动处理。

如何安装 @lovince/motion?

在项目目录运行 npm install @lovince/motion,或使用 pnpm add / yarn add / bun add。

@lovince/motion 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@lovince/motion 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@lovince/motion 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=18。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@lovince/motion 可以商用吗?

@lovince/motion 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@lovince/motion 还在维护吗?

最新版本 0.4.0 发布于 2026/11/02(1 天前);至今共发布 5 个版本,其中过去 12 个月发布了 5 个;首次发布于 2026/10/16。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。