Motion · Data

Counter Confetti Celebration

A big number counts up with an easeOutQuart sprint and a scale overshoot; a beat before it lands, 52 confetti pieces burst in from both sides on parabolic arcs, an impact ring expands, and the label's letter-spacing tightens to close.

1920×108016:930 fps5.1s · 153 framesremotionLicense: Apache-2.0#shotcraft#remotion#data

Source: video-shotcraft (Vincent Wei) ↗ · Apache-2.0 · Remotion composition (React/TSX) — preview video, no in-browser player

When to use

The celebration beat for milestone/achievement numbers: user count, revenue, downloads — any "pop the champagne" metric reveal.

Intention

The number isn't "displayed," it "crosses the finish line": the counting curve is fast-then-slow, like a 100m sprint slowing into a finish-line replay, and the confetti bursts a beat before the number lands — the celebration arrives half a beat ahead of the result, emotion outrunning information, so viewers feel excited before they even read the number clearly.

Duration & energy

  • Duration: About 4.6s (138f@30fps; counting 0.3–2.6s · confetti from 2.4s · settles at 3.3s).
  • Energy: High (a counting wind-up plus a burst payoff — a textbook emotional-peak shot).

Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)

意图

数字不是"显示"出来的,是"冲线"的:计数曲线前快后慢像百米冲刺进入慢镜,纸屑在数字到位之前抢拍爆开——庆祝比结果先到半拍,情绪压过信息,观众先兴奋再看清数字。

动效核心

  • 计数走 easeOutQuart(t=0.06–0.56):前 1/3 时间跑完 2/3 数值,符合"冲刺-减速-压线"的体感;toLocaleString 千分位格式
  • 数字 scale 两段:0.2→1.3 冲入(outCubic),计数到位后 1.3→1 回落(outBack 轻弹)——大一号入场、落定收正
  • 抢拍:纸屑 BURST=0.52 早于计数到位 0.56——庆祝先于结果 0.04(约 5 帧),这半拍是这张卡的灵魂
  • 52 片纸屑真物理:从画面两侧 ±250px 出发,初速 vx 150–450 向中冲 / vy −230~−450 上抛,重力 g 900–1420 拉回,自转 ±750°,每片错峰 ≤0.06
  • 到位冲击环:0.545 起 scale 0.35→2.95 扩散淡出(outQuart);标签 0.62 淡入且字距 11px→5px 收紧——"落章"感

参数表

| 参数 | 典型值 | 调节手感 | |------|--------|----------| | 计数曲线 | easeOutQuart,0.06–0.56 | 换 outCubic 冲刺感减弱;线性计数是最常见的"没有情绪"写法 | | 抢拍量 | BURST 提前 0.04 | 提前 0–0.02 读作同拍;>0.08 纸屑像"别人的庆祝" | | 纸屑数量 | 52 片(8 色调色板) | <30 稀疏不成雨;>80 遮数字。PAL 是"演出彩纸",一般不随品牌换色 | | 重力/初速比 | g≈2.5×|vy| | 决定抛物线顶点在画面上 1/3 处;g 太小纸屑飘出画顶 | | scale 过冲 | 1.3 峰值 + outBack 回落 | 数字是主角,过冲 >1.5 会撞到标签排版 | | 终值 | p*1000(演示值) | 换真实数字:保持"从 0 滚到终值",跳变起点会丢冲刺感 |

已知坑

  • 纸屑物理用 life = u*1.1 的归一化秒——改 dur 后重力体感会变(同 u 对应不同真实时长),需重调 g/vy
  • 抢拍差 0.04 是硬编码在 BURST 与计数终点两处的常量,改计数窗口时同步平移 BURST 保持提前量
  • 纸屑 opacity 尾段 0.74 起衰减到 0.05——别让纸屑活到 t=1 还在飞,落定段(标签收字距)需要干净背景
  • 配 SFX:计数段 tick 渐密 + BURST 拍 pop/confetti + 到位拍 impact——三层缺一情绪都塌(对照 sound-design.md counter 类)

参考实现

demos/data/counter-confetti/ (CounterConfetti.tsx)

Reference implementation (Remotion)

  • demos/data/counter-confetti/CounterConfetti.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/data/counter-confetti/CounterConfetti.tsx

The component imports shared fixtures from demos/_fixtures and helpers from assets/lib in the repo; SKILL.md there documents the render workflow.

Everything on this page is also machine-readable. Point your agent at the JSON, or add the gimgs MCP server and ask it to search motion templates.

  1. Fetch the JSON — it holds the shot recipe (timing, easing, parameter table, pitfalls) and the full TSX source.
  2. Clone the video-shotcraft repo; the component imports shared fixtures from demos/_fixtures and helpers from assets/lib.
  3. Register the component as a <Composition> in a Remotion project and render with `npx remotion render`, or re-implement the recipe in your own engine.
curl -s https://gimgs.net/motion/counter-confetti.json
git clone https://github.com/Vincentwei1021/video-shotcraft
# component: demos/data/counter-confetti/CounterConfetti.tsx
npx remotion render <CompositionId> out.mp4
claude mcp add --transport http gimgs https://gimgs.net/mcp
# then: search_motion_templates / get_motion_template("counter-confetti")

Prompt

Use the video-shotcraft shot recipe "Counter Confetti Celebration" (Remotion composition, Apache-2.0) published on gimgs.net.
Recipe JSON (prompt = timing/easing/parameter table/pitfalls, files = TSX source): https://gimgs.net/motion/counter-confetti.json
Raw TSX: https://gimgs.net/motion/counter-confetti/template.html
Repository: https://github.com/Vincentwei1021/video-shotcraft  (component: demos/data/counter-confetti/CounterConfetti.tsx; imports demos/_fixtures and assets/lib)
Spec: 1920x1080, 30 fps, 5.1s

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

5,144 chars · Apache-2.0Open raw template ↗
// ===== demos/data/counter-confetti/CounterConfetti.tsx =====
// counter-confetti — Counter Confetti 数字冲刺纸屑(motion-lab 定稿转原生 Remotion)
// 大数字 0 → 1000 用 easeOutQuart 冲刺,scale 走 [0.2,1.3,1.3,1] 过冲;数字到位前
// 5 帧就从两侧 burst 出 8 色纸屑,带重力下落与自转飘散。
// 设计坐标 480×270(DesignStage 等比放大),参数表数值以此坐标系标定。
import React from 'react';
import { DesignStage, E, lerp, rand, seg, useT } from '../../_fixtures/Motion';

export const COUNTER_CONFETTI_DURATION = 138; // 4600ms @30fps

const PAL = ['#ff6b8b', '#ffb347', '#ffe86b', '#7bff9e', '#5fd8ff', '#6c8cff', '#c86cff', '#ff6cf0'];
const BURST = 0.52; // 计数到位(0.56)前"抢拍"

// 52 片纸屑的静态参数(种子与 effect.js 完全一致,确定性可复现)
const BITS = Array.from({ length: 52 }, (_, i) => {
  const isRect = rand(i * 3) > 0.4;
  const w = 5 + rand(i + 2) * 6;
  const h = isRect ? 8 + rand(i + 5) * 7 : w;
  const side = i % 2 ? 1 : -1;
  return {
    w,
    h,
    isRect,
    color: PAL[i % 8],
    x0: side * 250, // 从画面两侧出发(px,相对中心)
    y0: (rand(i + 30) - 0.5) * 60,
    vx: -side * (150 + rand(i * 5 + 1) * 300), // 向画面中间冲
    vy: -(230 + rand(i * 7 + 3) * 220),
    g: 900 + rand(i + 60) * 520,
    spin: (rand(i + 90) - 0.5) * 1500,
    d: rand(i + 120) * 0.06, // 每片略微错峰
  };
});

export const CounterConfetti: React.FC = () => {
  const t = useT();
  // 计数:easeOutQuart 冲刺
  const p = seg(t, 0.06, 0.56, E.outQuart);
  const val = Math.round(p * 1000);
  // scale 过冲 [0.2,1.3,1.3,1]
  const s1 = seg(t, 0.06, 0.3, E.outCubic);
  const s2 = seg(t, 0.56, 0.72, E.outBack);
  const sc = lerp(s1, 0.2, 1.3) + s2 * (1 - 1.3);
  // 到位冲击环
  const rp = seg(t, 0.545, 0.75, E.outQuart);
  return (
    <DesignStage bg="radial-gradient(80% 80% at 50% 45%,#161a2b,#07080e 75%)">
      {/* 中心辉光 */}
      <div
        style={{
          position: 'absolute',
          left: '50%',
          top: '47%',
          width: 340,
          height: 340,
          margin: -170,
          borderRadius: '50%',
          background: 'radial-gradient(circle,rgba(120,160,255,.28),transparent 66%)',
          opacity: 0.35 + seg(t, 0.5, 0.62, E.outCubic) * 0.65 - seg(t, 0.72, 1) * 0.5,
        }}
      />
      {/* 大数字(渐变填充文字,外层 scale 过冲) */}
      <div style={{ position: 'absolute', left: '50%', top: '47%', transformOrigin: '50% 50%', transform: `scale(${sc})` }}>
        <div
          style={{
            position: 'absolute',
            left: 0,
            top: 0,
            transform: 'translate(-50%,-50%)',
            whiteSpace: 'nowrap',
            fontWeight: 800,
            fontSize: 74,
            lineHeight: 1,
            // 原栈 -apple-system 在 Remotion 无头浏览器解析不到(落到 Arial),
            // 补 Helvetica 兜底——度量与原片的 SF Pro 逐字形吻合
            fontFamily: "-apple-system,Helvetica,'Segoe UI',sans-serif",
            letterSpacing: -2,
            background: 'linear-gradient(180deg,#ffffff,#a9bcff)',
            WebkitBackgroundClip: 'text',
            backgroundClip: 'text',
            color: 'transparent',
            textShadow: '0 0 34px rgba(130,160,255,.28)',
            opacity: Math.min(1, seg(t, 0.02, 0.12) * 1.2),
          }}
        >
          {val.toLocaleString('en-US')}
        </div>
      </div>
      {/* 底部标签:淡入 + letter-spacing 收拢 */}
      <div
        style={{
          position: 'absolute',
          left: '50%',
          top: '70%',
          transform: 'translate(-50%,0)',
          fontWeight: 700,
          fontSize: 10,
          lineHeight: 1,
          fontFamily: '-apple-system,Helvetica,sans-serif', // 同上:Helvetica 兜底对齐原片

          color: '#7d88a8',
          opacity: seg(t, 0.62, 0.78, E.outCubic),
          letterSpacing: lerp(seg(t, 0.62, 0.82, E.outCubic), 11, 5),
        }}
      >
        METRIC THIS WEEK
      </div>
      {/* 到位冲击环 */}
      <div
        style={{
          position: 'absolute',
          left: '50%',
          top: '47%',
          width: 120,
          height: 120,
          margin: -60,
          borderRadius: '50%',
          border: '2px solid rgba(160,190,255,.8)',
          // 原渲染无全局 border-box:120px 是内容宽,2px 边框外扩到 124px
          // (Remotion 注入了 * { box-sizing:border-box },显式还原才对得上原片)
          boxSizing: 'content-box',
          opacity: rp > 0 ? (1 - rp) * 0.9 : 0,
          transform: `scale(${0.35 + rp * 2.6})`,
        }}
      />
      {/* 纸屑:抢拍 burst + 重力下落 + 自转 */}
      {BITS.map((b, i) => {
        const u = seg(t, BURST + b.d, 1);
        const life = u * 1.1; // 秒级时间尺度
        const x = b.x0 + b.vx * life;
        const y = b.y0 + b.vy * life + 0.5 * b.g * life * life;
        return (
          <div
            key={i}
            style={{
              position: 'absolute',
              left: '50%',
              top: '50%',
              width: b.w,
              height: b.h,
              background: b.color,
              borderRadius: b.isRect ? 2 : '50%',
              willChange: 'transform',
              opacity: u <= 0 ? 0 : Math.min(1, u * 8) * (1 - seg(u, 0.74, 1) * 0.95),
              transform: `translate(calc(-50% + ${x}px),calc(-50% + ${y}px)) rotate(${b.spin * life}deg) scale(${0.8 + (1 - u) * 0.35})`,
            }}
          />
        );
      })}
    </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/data/counter-confetti/CounterConfetti.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
—

More in this category