// ===== demos/outro/ui-strip-away-outro/UiStripAwayOutro.tsx ===== // ui-strip-away-outro —— framer-ai 33–36.5s // 满屏灰阶"编辑器"里光标点击高亮 Publish 按钮 → UI 层层错峰蒸发退场 // (每层 fade + 轻微位移,从外围到中心)→ 黑场只剩按钮 → 按钮淡出交棒字标。 import React from 'react'; import { AbsoluteFill, useCurrentFrame, interpolate, spring, useVideoConfig, Easing } from 'remotion'; import { G, Card } from '../../_fixtures/Fixtures'; const CLICK = 34; // 点击时刻 // 蒸发层级(点击后延迟,外围先走) const STRIP = { sidebar: CLICK + 4, leftPanel: CLICK + 8, canvasCards: CLICK + 12, topbarEnds: CLICK + 16, canvasBg: CLICK + 20, toolbarShell: CLICK + 24, }; const STRIP_DUR = 14; const BTN_FADE = CLICK + 52; const LOGO_IN = CLICK + 62; // 某层的蒸发进度 → {opacity, offset} const useStrip = (frame: number, start: number, dx: number, dy: number) => { const p = interpolate(frame, [start, start + STRIP_DUR], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: Easing.in(Easing.quad), // 离场加速 }); return { opacity: 1 - p, transform: `translate(${dx * p}px, ${dy * p}px)`, }; }; export const UiStripAwayOutro: React.FC = () => { const frame = useCurrentFrame(); const { fps } = useVideoConfig(); // 背景:编辑器灰底 → 黑场(随 canvasBg 层蒸发压黑) const bgDark = interpolate(frame, [STRIP.canvasBg, STRIP.canvasBg + STRIP_DUR + 6], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: Easing.inOut(Easing.quad), }); const sidebar = useStrip(frame, STRIP.sidebar, -140, 0); const leftPanel = useStrip(frame, STRIP.leftPanel, -90, 20); const topLeft = useStrip(frame, STRIP.topbarEnds, -80, -60); const topRight = useStrip(frame, STRIP.topbarEnds, 80, -60); const toolbarShell = useStrip(frame, STRIP.toolbarShell, 0, -50); const canvasFrame = useStrip(frame, STRIP.canvasBg, 0, 40); // 画布卡片错峰蒸发 const cardStrip = (i: number) => useStripStatic(frame, STRIP.canvasCards + i * 3, (i % 2 ? 70 : -70), 50 + i * 10); // 按钮:点击脉冲 + 最后淡出 const press = spring({ frame: frame - CLICK, fps, config: { damping: 12, stiffness: 220 } }); const pressScale = frame < CLICK ? 1 : 1 - 0.12 * Math.sin(Math.min(1, press) * Math.PI); const btnOp = interpolate(frame, [BTN_FADE, BTN_FADE + 12], [1, 0], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', }); // 蒸发期间按钮从工具条位置滑向屏幕中心,独占黑场 const btnCenter = interpolate(frame, [STRIP.toolbarShell, STRIP.toolbarShell + 18], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: Easing.inOut(Easing.cubic), }); const btnX = 1560 + (960 - 88 - 1560) * btnCenter; const btnY = 30 + (540 - 30 - 30) * btnCenter; const btnScale = 1 + 0.5 * btnCenter; // 字标接棒 const logoP = spring({ frame: frame - LOGO_IN, fps, config: { damping: 14, stiffness: 90 } }); // 光标移向按钮 const curX = interpolate(frame, [4, CLICK - 2], [820, 1636], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: Easing.inOut(Easing.quad), }); const curY = interpolate(frame, [4, CLICK - 2], [640, 64], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: Easing.inOut(Easing.quad), }); const curOp = interpolate(frame, [CLICK + 6, CLICK + 16], [1, 0], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', }); return ( {/* 编辑器灰底(作为一层可蒸发的背景) */} {/* 左侧栏(图层面板) */}
{Array.from({ length: 9 }).map((_, i) => (
))}
{/* 右侧属性面板 */}
{Array.from({ length: 4 }).map((_, i) => (
))}
{/* 顶部工具条左半(logo + 工具 chips) */}
{Array.from({ length: 5 }).map((_, i) => (
))}
{/* 顶部工具条中段(标题) */}
{/* 顶部工具条右段底板(Invite 假按钮;Publish 单独渲染在最上层) */}
{/* 画布区:一张浏览器式大卡 + 两张小卡 */}
{[0, 1, 2].map((i) => (
))}
{[0, 1, 2, 3].map((i) => { const s = cardStrip(i); return (
); })}
{/* Publish 按钮(高亮层,最后退场) */}
Publish
{/* 字标接棒 */} {frame >= LOGO_IN && (
WORDMARK
)} {/* 光标 */} ); }; // hook 规则外的静态版本(供 map 内调用) const useStripStatic = (frame: number, start: number, dx: number, dy: number) => { const p = interpolate(frame, [start, start + STRIP_DUR], [0, 1], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: Easing.in(Easing.quad), }); return { opacity: 1 - p, transform: `translate(${dx * p}px, ${dy * p}px)`, }; };