Motion · UI xuất hiện

Cột nhập theo bậc thang giá trị

16 cột vào hình với delay là bậc thang thời gian, đồng thời chiều cao/sắc màu/dịch chuyển/độ mờ mỗi thuộc tính tự trải thành bậc thang giá trị từ đầu đến cuối; nhịp hai đổi gốc lệch nhịp về tâm, biên độ xung lấy tâm làm đỉnh trải lại.

1920×108016:930 fps5.5s · 165 frameremotionGiấy phép: Apache-2.0#shotcraft#remotion#ui-entrance

Nguồn: video-shotcraft (Vincent Wei) ↗ · Apache-2.0 · Composition Remotion (React/TSX) — có video preview, không có player trong trình duyệt

Khi nào dùng

Cảnh demo kỹ thuật và năng lực tham số hóa; cũng dùng trực tiếp làm màn vào hình cho giao diện dữ liệu/phổ tần/equalizer

Ý đồ

Stagger thường chỉ được dùng để lệch nhịp thời gian. Thẻ này muốn nói nó còn lệch nhịp được cả giá trị — cùng một chỉ số vừa quyết định "khi nào động" vừa quyết định "động tới mức nào". Hai nhịp tạo thành một cặp thí nghiệm đối chiếu: nhịp một gốc ở vị trí đầu (bậc thang trải đơn điệu từ trái sang phải), nhịp hai gốc đổi về tâm (bậc thang trải đối xứng qua trục giữa). Khán giả thấy không phải hai hoạt ảnh, mà là cùng một công thức đổi một tham số. Phụ đề code trên màn hình đổi theo tương ứng lời gọi stagger, nói thẳng ra điều này.

Thời lượng & năng lượng

  • Thời lượng: khoảng 5.0s (150f@30fps)
  • Năng lượng: Trung bình cao (nhịp một là trải liên tục, nhịp hai xung tâm là một đỉnh rõ ràng)

Công thức cảnh quay (bản gốc tiếng Trung — timing, easing, bảng tham số, lỗi hay gặp)

意图

stagger 通常只被用来错开时间。这张卡要说的是它还能错开值——同一个索引既决定 "什么时候动",也决定"动到多少"。两拍是一组对照实验:第一拍原点在首位(梯度从左到 右单调铺开),第二拍原点换到中心(梯度以中轴对称铺开)。观众看到的不是两个动画, 而是同一个配方换了一个参数。屏幕上的代码字幕同步换成对应的 stagger 调用,明说这件事。

动效核心

  • 核心工具是一个 8 行的 staggerVal(i, n, a, b, ease):把索引归一化成 k = i/(n-1)

再 lerp(k, a, b)——等价 anime.js 的 stagger([a, b]) 值模式,可选 ease 让梯度非线性

  • 静态梯度(写在 setup 里,一次算完):色相 staggerVal(i,N,200,320)、

最大高度 staggerVal(i,N,92,32)——16 根柱天生是一道从蓝到紫、从高到矮的斜坡

  • 动态梯度(每帧用):位移量 staggerVal(i,N,46,14)、模糊量 staggerVal(i,N,8,2)

——首位柱要走 46px、糊 8px,末位只走 14px、糊 2px。同一次入场里每根柱的行程都不同

  • 第一拍时间错峰:d = i*0.02,e = seg(t, 0.06+d, 0.28+d, E.outCubic)——从首位起,

16 根摊在 0.3 的窗上;opacity = e、filter = blur((1-e)*b0)、 transform = translateY((1-e)*y0) scaleY(e),三者都被同一个 e 驱动

  • 第二拍换原点:distC = |i - 7.5| / 7.5 是到中心的归一化距离,

w = seg(t, 0.56 + distC*0.13, +0.18)——中心柱先动,波向两端扩,这就是 from:'center'

  • 第二拍的幅度也是梯度:amp = lerp(1-distC, 0.06, 0.42),中心柱脉冲 42%、末端只有 6%;

pulse = sin(w*π) 是纯脉冲,同时叠进 scaleY(e*(1+pulse*amp)) 与 brightness(1+pulse*0.55)

  • 字幕在 t=0.52 硬切文案,从 scale: stagger([1, 0.35]) hue: stagger([200, 320])

换成 pulse: stagger([.06, .42], { from: 'center' })——正好卡在两拍之间

参数表

| 参数 | 典型值 | 调节手感 | |------|--------|----------| | 柱数 | N=16(left: 8+i*5.4%,宽 3.4%) | 16 根让梯度有足够分档;<8 根梯度读作"每根都不一样"而非一道斜坡,>24 根柱间距要重算 | | 时间错峰 | d = i*0.02(≈3f/根,总 0.3) | 3f 是连绵的铺开;>0.05 会变成逐根点名,梯度的"整体感"丢失 | | 单柱行程 | 窗 0.22(≈33f,outCubic) | 行程是总错峰的 0.73 倍 → 大部分时间十来根同时在动;行程 < 错峰总量时变成一根接一根 | | 高度梯度 | stagger([92, 32])px | 首末 2.9 倍差是明确斜坡;比值 <1.5 读不出梯度,>4 末端柱矮到看不见 | | 色相梯度 | stagger([200, 320]) | 120° 跨度(蓝→紫);跨度 <40° 时色相白给,>200° 会绕回红色让斜坡读作环形 | | 位移/模糊梯度 | stagger([46,14])px / stagger([8,2])px | 这两条是本卡的"值 stagger"主证据——把它们改成常量,动效立刻退化成普通的 stagger 入场 | | 第二拍原点 | from:'center',distC*0.13 | 0.13 是从中心到端点的传播延迟(≈4f);归零则整排同时脉冲,from 参数就没有意义了 | | 脉冲幅度梯度 | lerp(1-distC, 0.06, 0.42) | 中心 42% / 端点 6% 是 7 倍差,波形明显;两端也给大幅度会读作整排一起鼓,中心原点看不出 | | 亮度脉冲 | brightness(1 + pulse*0.55) | 55% 提亮让脉冲在暗底上可读;这一项没做梯度(全柱同幅),是有意的——太多梯度叠一起观众读不清哪个是主角 |

已知坑

  • 第一拍与第二拍首尾相接:末柱入场结束于 0.28+15*0.02 = 0.58,而中心柱的脉冲在 0.56

就起。加柱数或加大时间错峰会让入场吃进脉冲段,两拍糊成一件事——加柱必须把 0.56 后推

  • 端点柱的第二拍窗口是 0.69→0.87,之后到 t=1 有约 0.13(20f)的完全静止。这是给

观众看清"终态梯度"的时间,不是空白

  • distC 的分母 C=(N-1)/2 在 N=1 时会除零;staggerVal 里已用 n<=1 ? 0 兜住,

但 distC 没有——柱数改成 1 会 NaN

  • 柱位 left: 8+i*5.4% 与宽 3.4% 是为 N=16 手配的(末柱右缘 8+15*5.4+3.4 = 92.4%)。

改柱数必须重算这两个百分比,否则柱会溢出或挤在左半边

  • 字幕是 anime.js 的 API 调用字符串,本质是技法说明而不是内容。用在真实成片里

要么换成有语义的标题,要么去掉——否则观众读到的是代码

  • 底部的 16 个小圆点(bottom:17%,opacity .35)是静态装饰,不参与任何动画;它们的

left 偏移 +1.2% 是与柱宽 3.4% 手对的居中量,改柱宽要一起改

  • 深底(#0a0b10)+ 高饱和柱(85%/66%)+ 12px 光晕是一套;浅色主题下光晕与

brightness 脉冲都失效,需要改用 saturate 或阴影表达脉冲

参考实现

demos/ui-entrance/value-stagger-gradient/ (ValueStaggerGradient.tsx)

Mã nguồn tham chiếu (Remotion)

  • demos/ui-entrance/value-stagger-gradient/ValueStaggerGradient.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/ui-entrance/value-stagger-gradient/ValueStaggerGradient.tsx

Component import fixture chung từ demos/_fixtures và helper từ assets/lib của repo; xem SKILL.md trong repo để biết quy trình render.

Mọi thứ trên trang này đều có bản máy đọc. Đưa agent link JSON, hoặc thêm MCP server gimgs rồi bảo nó tìm motion template.

  1. Tải JSON — trong đó có công thức cảnh quay (timing, easing, bảng tham số, lỗi hay gặp) và toàn bộ source TSX.
  2. Clone repo video-shotcraft; component import fixture chung từ demos/_fixtures và helper từ assets/lib.
  3. Đăng ký component thành <Composition> trong project Remotion rồi render bằng `npx remotion render`, hoặc dựng lại công thức bằng engine của bạn.
curl -s https://gimgs.net/motion/value-stagger-gradient.json
git clone https://github.com/Vincentwei1021/video-shotcraft
# component: demos/ui-entrance/value-stagger-gradient/ValueStaggerGradient.tsx
npx remotion render <CompositionId> out.mp4
claude mcp add --transport http gimgs https://gimgs.net/mcp
# then: search_motion_templates / get_motion_template("value-stagger-gradient")

Prompt

Use the video-shotcraft shot recipe "Cột nhập theo bậc thang giá trị" (Remotion composition, Apache-2.0) published on gimgs.net.
Recipe JSON (prompt = timing/easing/parameter table/pitfalls, files = TSX source): https://gimgs.net/motion/value-stagger-gradient.json
Raw TSX: https://gimgs.net/motion/value-stagger-gradient/template.html
Repository: https://github.com/Vincentwei1021/video-shotcraft  (component: demos/ui-entrance/value-stagger-gradient/ValueStaggerGradient.tsx; imports demos/_fixtures and assets/lib)
Spec: 1920x1080, 30 fps, 5.5s

1. Read the JSON and follow its "prompt" section (when to use, intention, parameter table, known pitfalls).
2. Adapt it for: <describe what you want>.
3. Either clone the repo, register the component as a <Composition> in a Remotion project and run npx remotion render <CompositionId> out.mp4 — or re-implement the recipe in your own engine (HTML/WAAPI works: keep the same timing and easing).

JSON · Markdown · llms.txt

3,633 chars · Apache-2.0Mở template gốc ↗
// ===== demos/ui-entrance/value-stagger-gradient/ValueStaggerGradient.tsx =====
// value-stagger-gradient — Value Stagger 数值梯度(motion-lab 定稿转原生 Remotion)
// stagger 不只错开时间,还把属性值在 N 个元素上铺成梯度:16 根柱入场时
// delay=stagger(时间),同时高度/色相/模糊都是 stagger([from,to]) 的数值梯度;
// 第二拍换 from:'center',脉冲幅度以中心为原点重新铺开。
// 设计坐标 480×270(DesignStage 等比放大),参数表数值以此坐标系标定。
import React from 'react';
import { DesignStage, E, lerp, seg, useT } from '../../_fixtures/Motion';

export const VALUE_STAGGER_GRADIENT_DURATION = 150; // 5000ms @30fps

// 数值梯度 util(等价 stagger([a,b]) 的 value 模式)
const staggerVal = (i: number, n: number, a: number, b: number, ease?: (x: number) => number) => {
  let k = n <= 1 ? 0 : i / (n - 1);
  if (ease) k = ease(k);
  return lerp(k, a, b);
};

const N = 16;
const C = (N - 1) / 2;
const BARS = Array.from({ length: N }, (_, i) => ({
  i,
  hue: staggerVal(i, N, 200, 320), // 数值梯度:色相铺开
  hMax: staggerVal(i, N, 92, 32), // 数值梯度:高度 1→0.35
  distC: Math.abs(i - C) / C,
}));

export const ValueStaggerGradient: React.FC = () => {
  const t = useT();
  return (
    <DesignStage bg="#0a0b10">
      <div
        style={{
          position: 'absolute',
          inset: 0,
          background: '#0a0b10',
          overflow: 'hidden',
          fontFamily: '"SF Mono",Menlo,monospace',
        }}
      >
        {BARS.map(({ i, hue, hMax, distC }) => {
          // 拍一:时间 stagger(linear from first)× 数值梯度(y/blur 同时铺开)
          const d = i * 0.02;
          const e = seg(t, 0.06 + d, 0.28 + d, E.outCubic);
          const y0 = staggerVal(i, N, 46, 14); // 位移量本身也是梯度
          const b0 = staggerVal(i, N, 8, 2); // 模糊量梯度
          // 拍二:from:'center' —— 波与幅度都以中心为原点铺开
          const w = seg(t, 0.56 + distC * 0.13, 0.74 + distC * 0.13);
          const pulse = Math.sin(w * Math.PI);
          const amp = lerp(1 - distC, 0.06, 0.42); // 幅度梯度:中心最大
          return (
            <React.Fragment key={i}>
              <div
                style={{
                  position: 'absolute',
                  bottom: '22%',
                  left: `${8 + i * 5.4}%`,
                  width: '3.4%',
                  height: hMax,
                  borderRadius: 5,
                  transformOrigin: '50% 100%',
                  background: `linear-gradient(180deg,hsl(${hue},85%,66%),hsl(${hue},70%,42%))`,
                  boxShadow: `0 0 12px hsla(${hue},85%,58%,.25)`,
                  opacity: e,
                  filter: `blur(${(1 - e) * b0}px) brightness(${1 + pulse * 0.55})`,
                  transform: `translateY(${(1 - e) * y0}px) scaleY(${e * (1 + pulse * amp)})`,
                }}
              />
              <div
                style={{
                  position: 'absolute',
                  bottom: '17%',
                  left: `${8 + i * 5.4 + 1.2}%`,
                  width: 4,
                  height: 4,
                  borderRadius: '50%',
                  background: `hsl(${hue},70%,55%)`,
                  opacity: 0.35,
                }}
              />
            </React.Fragment>
          );
        })}
        <div
          style={{
            position: 'absolute',
            left: '50%',
            top: '9%',
            transform: 'translateX(-50%)',
            color: '#5b6480',
            fontSize: 10,
            letterSpacing: '1.5px',
            whiteSpace: 'nowrap',
            opacity: 0.5 + 0.5 * seg(t, 0.04, 0.12),
          }}
        >
          {t < 0.52
            ? 'scale: stagger([1, 0.35])  hue: stagger([200, 320])'
            : "pulse: stagger([.06, .42], { from: 'center' })"}
        </div>
      </div>
    </DesignStage>
  );
};


  1. Read the recipe
    Prompt = when to use, intention, parameter table with typical values and how each one feels, known pitfalls. Treat values as calibrated starting points.
  2. Get the code
    tool: git
    git clone https://github.com/Vincentwei1021/video-shotcraft && cd video-shotcraft && npm install
  3. Register + render
    tool: remotion
    npx remotion render <CompositionId> out.mp4 # component: demos/ui-entrance/value-stagger-gradient/ValueStaggerGradient.tsx
  4. Or re-implement
    Port the timing/easing from the recipe to HTML + WAAPI (gimgs motion contract) or any engine; keep the same segment windows and easing.
paramtypedefaultdescription
—

Cùng danh mục