From 195e504b7b56cc7f4011774354ad76a4603ad1b7 Mon Sep 17 00:00:00 2001 From: dfty Date: Fri, 6 Feb 2026 00:17:55 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=96=87=E4=BB=B6=E8=87=B3?= =?UTF-8?q?=E3=80=8Crules/assets=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rules/assets/charts-bar-chart.tsx | 173 ++++++++++++++++++ rules/assets/text-animations-typewriter.tsx | 100 ++++++++++ .../assets/text-animations-word-highlight.tsx | 108 +++++++++++ 3 files changed, 381 insertions(+) create mode 100644 rules/assets/charts-bar-chart.tsx create mode 100644 rules/assets/text-animations-typewriter.tsx create mode 100644 rules/assets/text-animations-word-highlight.tsx diff --git a/rules/assets/charts-bar-chart.tsx b/rules/assets/charts-bar-chart.tsx new file mode 100644 index 0000000..1313ebf --- /dev/null +++ b/rules/assets/charts-bar-chart.tsx @@ -0,0 +1,173 @@ +import {loadFont} from '@remotion/google-fonts/Inter'; +import {AbsoluteFill, spring, useCurrentFrame, useVideoConfig} from 'remotion'; + +const {fontFamily} = loadFont(); + +const COLOR_BAR = '#D4AF37'; +const COLOR_TEXT = '#ffffff'; +const COLOR_MUTED = '#888888'; +const COLOR_BG = '#0a0a0a'; +const COLOR_AXIS = '#333333'; + +// Ideal composition size: 1280x720 + +const Title: React.FC<{children: React.ReactNode}> = ({children}) => ( +
+
+ {children} +
+
+); + +const YAxis: React.FC<{steps: number[]; height: number}> = ({ + steps, + height, +}) => ( +
+ {steps + .slice() + .reverse() + .map((step) => ( +
+ {step.toLocaleString()} +
+ ))} +
+); + +const Bar: React.FC<{ + height: number; + progress: number; +}> = ({height, progress}) => ( +
+
+
+); + +const XAxis: React.FC<{ + children: React.ReactNode; + labels: string[]; + height: number; +}> = ({children, labels, height}) => ( +
+
+ {children} +
+
+ {labels.map((label) => ( +
+ {label} +
+ ))} +
+
+); + +export const MyAnimation = () => { + const frame = useCurrentFrame(); + const {fps, height} = useVideoConfig(); + + const data = [ + {month: 'Jan', price: 2039}, + {month: 'Mar', price: 2160}, + {month: 'May', price: 2327}, + {month: 'Jul', price: 2426}, + {month: 'Sep', price: 2634}, + {month: 'Nov', price: 2672}, + ]; + + const minPrice = 2000; + const maxPrice = 2800; + const priceRange = maxPrice - minPrice; + const chartHeight = height - 280; + const yAxisSteps = [2000, 2400, 2800]; + + return ( + + Gold Price 2024 + +
+ + d.month)}> + {data.map((item, i) => { + const progress = spring({ + frame: frame - i * 5 - 10, + fps, + config: {damping: 18, stiffness: 80}, + }); + + const barHeight = + ((item.price - minPrice) / priceRange) * chartHeight * progress; + + return ( + + ); + })} + +
+
+ ); +}; diff --git a/rules/assets/text-animations-typewriter.tsx b/rules/assets/text-animations-typewriter.tsx new file mode 100644 index 0000000..89f62ea --- /dev/null +++ b/rules/assets/text-animations-typewriter.tsx @@ -0,0 +1,100 @@ +import { + AbsoluteFill, + interpolate, + useCurrentFrame, + useVideoConfig, +} from 'remotion'; + +const COLOR_BG = '#ffffff'; +const COLOR_TEXT = '#000000'; +const FULL_TEXT = 'From prompt to motion graphics. This is Remotion.'; +const PAUSE_AFTER = 'From prompt to motion graphics.'; +const FONT_SIZE = 72; +const FONT_WEIGHT = 700; +const CHAR_FRAMES = 2; +const CURSOR_BLINK_FRAMES = 16; +const PAUSE_SECONDS = 1; + +// Ideal composition size: 1280x720 + +const getTypedText = ({ + frame, + fullText, + pauseAfter, + charFrames, + pauseFrames, +}: { + frame: number; + fullText: string; + pauseAfter: string; + charFrames: number; + pauseFrames: number; +}): string => { + const pauseIndex = fullText.indexOf(pauseAfter); + const preLen = + pauseIndex >= 0 ? pauseIndex + pauseAfter.length : fullText.length; + + let typedChars = 0; + if (frame < preLen * charFrames) { + typedChars = Math.floor(frame / charFrames); + } else if (frame < preLen * charFrames + pauseFrames) { + typedChars = preLen; + } else { + const postPhase = frame - preLen * charFrames - pauseFrames; + typedChars = Math.min( + fullText.length, + preLen + Math.floor(postPhase / charFrames), + ); + } + return fullText.slice(0, typedChars); +}; + +const Cursor: React.FC<{ + frame: number; + blinkFrames: number; + symbol?: string; +}> = ({frame, blinkFrames, symbol = '\u258C'}) => { + const opacity = interpolate( + frame % blinkFrames, + [0, blinkFrames / 2, blinkFrames], + [1, 0, 1], + {extrapolateLeft: 'clamp', extrapolateRight: 'clamp'}, + ); + + return {symbol}; +}; + +export const MyAnimation = () => { + const frame = useCurrentFrame(); + const {fps} = useVideoConfig(); + + const pauseFrames = Math.round(fps * PAUSE_SECONDS); + + const typedText = getTypedText({ + frame, + fullText: FULL_TEXT, + pauseAfter: PAUSE_AFTER, + charFrames: CHAR_FRAMES, + pauseFrames, + }); + + return ( + +
+ {typedText} + +
+
+ ); +}; diff --git a/rules/assets/text-animations-word-highlight.tsx b/rules/assets/text-animations-word-highlight.tsx new file mode 100644 index 0000000..e3929c5 --- /dev/null +++ b/rules/assets/text-animations-word-highlight.tsx @@ -0,0 +1,108 @@ +import {loadFont} from '@remotion/google-fonts/Inter'; +import React from 'react'; +import { + AbsoluteFill, + spring, + useCurrentFrame, + useVideoConfig, +} from 'remotion'; + +/* + * Highlight a word in a sentence with a spring-animated wipe effect. + */ + +// Ideal composition size: 1280x720 + +const COLOR_BG = '#ffffff'; +const COLOR_TEXT = '#000000'; +const COLOR_HIGHLIGHT = '#A7C7E7'; +const FULL_TEXT = 'This is Remotion.'; +const HIGHLIGHT_WORD = 'Remotion'; +const FONT_SIZE = 72; +const FONT_WEIGHT = 700; +const HIGHLIGHT_START_FRAME = 30; +const HIGHLIGHT_WIPE_DURATION = 18; + +const {fontFamily} = loadFont(); + +const Highlight: React.FC<{ + word: string; + color: string; + delay: number; + durationInFrames: number; +}> = ({word, color, delay, durationInFrames}) => { + const frame = useCurrentFrame(); + const {fps} = useVideoConfig(); + + const highlightProgress = spring({ + fps, + frame, + config: {damping: 200}, + delay, + durationInFrames, + }); + const scaleX = Math.max(0, Math.min(1, highlightProgress)); + + return ( + + + {word} + + ); +}; + +export const MyAnimation = () => { + const highlightIndex = FULL_TEXT.indexOf(HIGHLIGHT_WORD); + const hasHighlight = highlightIndex >= 0; + const preText = hasHighlight ? FULL_TEXT.slice(0, highlightIndex) : FULL_TEXT; + const postText = hasHighlight + ? FULL_TEXT.slice(highlightIndex + HIGHLIGHT_WORD.length) + : ''; + + return ( + +
+ {hasHighlight ? ( + <> + {preText} + + {postText} + + ) : ( + {FULL_TEXT} + )} +
+
+ ); +};