Motion · Light & emphasis

Radial ripple phone chips

Concentric rings breathe like water behind a phone mockup with an auto-scrolling feed, while white chips spring in on both sides.

1920×108016:930 fps6.1s · 183 framesremotionLicense: Apache-2.0#shotcraft#remotion#effects

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

When to use

The "this is it" freeze shot for a mobile product; an intro section with feature points listed on both sides; a product overview for a cold open or outro

Intention

Put the phone dead-center with as few moves as possible and make it feel "alive": the concentric rings handle the ambient breathing, the auto-scrolling feed inside the screen proves "there's real content running in there," and the chips on either side call out feature names. Each of the three layers of motion is extremely slow on its own — stacked together, they avoid reading as static. This is the standard recipe for a product freeze shot.

Duration & energy

  • Duration: About 5.6s (168f@30fps)
  • Energy: Low (quiet, breathing feel, sustained by the concentric rings' continuous undulation so it never goes cold)

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

意图

用最少的动作把手机放在画面正中并让它"活着":同心圆负责氛围呼吸, 屏内自动滚屏负责证明"里面有内容在跑",两侧 chip 负责点出功能名。 三层动作各自极慢,叠起来才不会显得静止——这是产品定格镜头的标准配法。

动效核心

  • 4 层同心圆(560/440/320/210px,#d7dbe1→#eef0f3 由深到浅)各自

scale 1 ± 0.06,全片走 1.5 个正弦周期,相邻层相位差 1.7 rad ——错相是"水波"而不是"整体缩放"的关键

  • 手机 mockup 132×264、22px 圆角、0 24px 50px rgba(58,64,74,.35)

的落地投影,屏内 8 张骨架卡组成 feed

  • feed translateY 0→−150px,窗口 t 0.08→0.98 匀速无缓动
  • chip pop:seg(t0, t0+0.12, outBack) 驱动 scale 0.8→1,opacity 在

前 0.08 就跳满;左 chip 起 0.22、右 chip 起 0.40

  • pop 完接 sin 悬浮 ±3px(2 周期/全片),并由 seg(t0+0.12, t0+0.3)

渐渐介入——不是一落位就开始飘

参数表

| 参数 | 典型值 | 调节手感 | |------|--------|----------| | 同心圆层数/尺寸 | 4 层 560/440/320/210px | 3 层撐不满画面,5 层以上最外层被裁掉只剩色带 | | 呼吸幅度 | scale 1±0.06,1.5 周期/全片 | 0.06 已是上限;到 0.1 就读作"脉冲动画"而非水波 | | 相位差 | 相邻层 1.7 rad | 相位全同 = 整块放大缩小;差 1.7 才有波从中心荡开的错觉 | | 手机投影 | 0 24px 50px rgba(58,64,74,.35) | 投影是手机"浮在波上"的唯一依据,去掉就贴平了 | | 自动滚屏 | translateY 0→−150px,t 0.08→0.98 匀速 | 加 ease 立刻读作"有人在滑";匀速才是自动播放 | | chip pop | outBack、0.12 时长、scale 0.8→1 | outBack 的过冲量是"弹出"的全部质感,换 outCubic 就平了 | | chip 时序 | 左 0.22 / 右 0.40(≈0.6s 差) | 同时出现读作两个标签;错开才有"一个一个说"的节奏 | | chip 悬浮 | ±3px、2 周期,pop 后 0.18 内渐入 | 幅度超过 5px 就和同心圆的呼吸抢戏 |

已知坑

  • chip 用 calc(50% + 86px) 贴着手机边缘定位;改手机宽度必须同步改

这个 86px,否则 chip 压到 mockup 上

  • 屏内 feed 是 8 张骨架卡的占位内容,落地换真实截图时总高必须

大于「150px + 屏高」,否则滚到底露白

  • 同心圆是实色叠放(不是描边圆环),层序必须由大到小 append,

顺序反了就只看得见最大那层

  • ACCENT_RGB(默认 122,134,153 中性灰蓝)只作用于屏内缩略图渐变,

换项目品牌色只改这一个变量即可换肤

参考实现

demos/effects/radial-ripple-phone-chips/ (RadialRipplePhoneChips.tsx)

Reference implementation (Remotion)

  • demos/effects/radial-ripple-phone-chips/RadialRipplePhoneChips.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/effects/radial-ripple-phone-chips/RadialRipplePhoneChips.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/radial-ripple-phone-chips.json
git clone https://github.com/Vincentwei1021/video-shotcraft
# component: demos/effects/radial-ripple-phone-chips/RadialRipplePhoneChips.tsx
npx remotion render <CompositionId> out.mp4
claude mcp add --transport http gimgs https://gimgs.net/mcp
# then: search_motion_templates / get_motion_template("radial-ripple-phone-chips")

Prompt

Use the video-shotcraft shot recipe "Radial ripple phone chips" (Remotion composition, Apache-2.0) published on gimgs.net.
Recipe JSON (prompt = timing/easing/parameter table/pitfalls, files = TSX source): https://gimgs.net/motion/radial-ripple-phone-chips.json
Raw TSX: https://gimgs.net/motion/radial-ripple-phone-chips/template.html
Repository: https://github.com/Vincentwei1021/video-shotcraft  (component: demos/effects/radial-ripple-phone-chips/RadialRipplePhoneChips.tsx; imports demos/_fixtures and assets/lib)
Spec: 1920x1080, 30 fps, 6.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

4,505 chars · Apache-2.0Open raw template ↗
// ===== demos/effects/radial-ripple-phone-chips/RadialRipplePhoneChips.tsx =====
// radial-ripple-phone-chips — Radial Ripple Chips 同心波纹手机(motion-lab 定稿转原生 Remotion)
// 浅灰底同心圆多层错相呼吸如水波,中央手机 mockup 屏内 feed 缓慢自动滚动,
// 两侧白色 chip 先后 spring pop 入场并悬浮。配色是灰阶骨架 + 单一强调色
//(ACCENT 变量),落地时按项目品牌色替换。
// 设计坐标 480×270(DesignStage 等比放大),参数表数值以此坐标系标定。
import React from 'react';
import { DesignStage, E, lerp, rand, seg, useT } from '../../_fixtures/Motion';

export const RADIAL_RIPPLE_PHONE_CHIPS_DURATION = 168; // 5600ms @30fps

// 模板强调色:实际使用时按项目品牌色替换这一个变量
const ACCENT_RGB = '122,134,153'; // = #7a8699,中性蓝灰做默认演示色
const SANS = '-apple-system,BlinkMacSystemFont,sans-serif';

// 同心圆(深灰→浅灰,从大到小叠放),phase 错相
const RCOL = ['#d7dbe1', '#e0e4e9', '#e8ebef', '#eef0f3'];
const RSZ = [560, 440, 320, 210];
const RINGS = RSZ.map((size, i) => ({ size, color: RCOL[i], phase: i * 1.7 }));

// feed 卡片骨架:渐变图占位 + 两条 rand 定宽的文字条(种子与 effect.js 一致)
const CARDS = Array.from({ length: 8 }, (_, i) => ({
  imgGrad: `linear-gradient(120deg,rgba(${ACCENT_RGB},${(0.5 - i * 0.04).toFixed(2)}),rgba(${ACCENT_RGB},${(0.8 - i * 0.05).toFixed(2)}))`,
  l1w: `${62 + rand(i) * 28}%`,
  l2w: `${34 + rand(i + 40) * 30}%`,
}));

export const RadialRipplePhoneChips: React.FC = () => {
  const t = useT();
  // chip 先后 pop:scale 0.8→1 过冲 + 淡入,随后轻浮动
  const chipStyle = (t0: number, ph: number): React.CSSProperties => {
    const p = seg(t, t0, t0 + 0.12, E.outBack);
    const fl = Math.sin(t * Math.PI * 2 * 2 + ph) * 3 * seg(t, t0 + 0.12, t0 + 0.3);
    return {
      opacity: seg(t, t0, t0 + 0.08),
      transform: `scale(${lerp(p, 0.8, 1)}) translateY(${fl}px)`,
    };
  };
  const chipBase: React.CSSProperties = {
    position: 'absolute',
    padding: '10px 18px',
    borderRadius: 999,
    background: '#fff',
    color: '#2c3038',
    font: `600 13px ${SANS}`,
    whiteSpace: 'nowrap',
    boxShadow: '0 10px 26px rgba(58,64,74,.22)',
  };
  return (
    <DesignStage bg="#f2f3f5">
      <div style={{ position: 'absolute', inset: 0, background: '#f2f3f5', overflow: 'hidden' }}>
        {/* 同心圆各层错相极缓呼吸 */}
        {RINGS.map(({ size, color, phase }, i) => (
          <div
            key={i}
            style={{
              position: 'absolute',
              left: '50%',
              top: '50%',
              width: size,
              height: size,
              margin: `${-size / 2}px 0 0 ${-size / 2}px`,
              borderRadius: '50%',
              background: color,
              transform: `scale(${1 + 0.06 * Math.sin(t * Math.PI * 2 * 1.5 + phase)})`,
            }}
          />
        ))}

        {/* 手机 mockup:屏内 feed 缓慢自动滚动 */}
        <div
          style={{
            position: 'absolute',
            left: '50%',
            top: '50%',
            width: 132,
            height: 264,
            margin: '-132px 0 0 -66px',
            borderRadius: 22,
            background: '#1b1c22',
            padding: 7,
            // 原渲染无全局 border-box:132px 是内容宽,padding 外扩(Remotion 注入
            // 了 * { box-sizing:border-box },这里显式还原 content-box 才对得上原片)
            boxSizing: 'content-box',
            boxShadow: '0 24px 50px rgba(58,64,74,.35)',
          }}
        >
          <div style={{ position: 'relative', width: '100%', height: '100%', borderRadius: 16, background: '#fff', overflow: 'hidden' }}>
            <div
              style={{
                position: 'absolute',
                left: 0,
                top: 0,
                width: '100%',
                transform: `translateY(${-seg(t, 0.08, 0.98) * 150}px)`,
              }}
            >
              {CARDS.map(({ imgGrad, l1w, l2w }, i) => (
                <div key={i} style={{ position: 'relative', margin: '8px 8px 0', padding: 7, borderRadius: 8, background: '#f4f4f7' }}>
                  <div style={{ height: 34, borderRadius: 5, background: imgGrad }} />
                  <div style={{ marginTop: 6, height: 5, width: l1w, borderRadius: 3, background: '#c9cad3' }} />
                  <div style={{ marginTop: 4, height: 5, width: l2w, borderRadius: 3, background: '#dedfe6' }} />
                </div>
              ))}
            </div>
          </div>
        </div>

        {/* 两侧 chip:左先右后 spring pop */}
        <div style={{ ...chipBase, top: '38%', right: 'calc(50% + 86px)', ...chipStyle(0.22, 0) }}>Feature one</div>
        <div style={{ ...chipBase, top: '56%', left: 'calc(50% + 86px)', ...chipStyle(0.4, 1.8) }}>Feature detail</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/effects/radial-ripple-phone-chips/RadialRipplePhoneChips.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