Description
Visual timeline editor for CSS @keyframes animations. Add keyframes on a draggable timeline, set properties per keyframe, and see a live preview on a customizable element. Exports clean @keyframes CSS. The visual approach makes animation authoring intuitive — no more guessing percentages.
Features
Timeline Editor
- Visual timeline — Horizontal bar representing 0%–100% of the animation
- Keyframe markers — Draggable diamond markers on the timeline, click to select
- Add keyframe — Click on the timeline to add a new keyframe at that percentage
- Remove keyframe — Right-click or delete key to remove a keyframe
- Snap to grid — Keyframes snap to 5% increments (toggle off for freeform)
- Multi-select — Shift+click to select multiple keyframes, move together
- Copy keyframe — Duplicate a keyframe's properties to another position
Properties Panel
Per-keyframe CSS properties with visual controls:
- Transform:
- Translate X/Y/Z (sliders, px or %)
- Scale X/Y (slider, 0–3)
- Rotate (angle dial, 0–360°)
- Skew X/Y (slider, -45° to 45°)
- Opacity — Slider 0–1
- Colors:
- Dimensions:
width / height (px, %, auto)
border-radius (px or %)
padding / margin
- Filter:
blur() — slider 0–20px
brightness() — slider 0–200%
saturate() — slider 0–200%
hue-rotate() — angle dial 0–360°
grayscale() — slider 0–100%
- Clip-path — Select from presets (circle, polygon, inset)
- Custom CSS — Freeform CSS input per keyframe for properties not covered above
Animation Controls
- Name — Custom animation name (default:
my-animation)
- Duration — 0.1s–10s slider
- Timing function —
linear, ease, ease-in, ease-out, ease-in-out, cubic-bezier() with visual curve editor
- Iteration count — 1, 2, 3, ...,
infinite
- Direction —
normal, reverse, alternate, alternate-reverse
- Fill mode —
none, forwards, backwards, both
- Play state — Play/pause the preview animation
- Playback speed — 0.25x, 0.5x, 1x, 2x for slow-motion debugging
Preview
- Live element — Customizable preview target:
- Shape: square, circle, rectangle, text, custom HTML
- Size, color, border, background
- The animation plays on this element in real-time
- Ghost frames — Toggle to show all keyframe states simultaneously (onion skinning)
- Background — Checkered (transparent), white, dark, custom color
- Scrub — Drag a playhead across the timeline to manually scrub through the animation
Easing Curve Editor
- Cubic bezier — Draggable control points on a curve graph
- Preset curves — Quick-select common easing functions with visual preview
- Per-keyframe easing — Different easing between each keyframe pair (via
animation-timing-function within keyframes)
Implementation
Dependencies
Zero — native CSS animations + DOM manipulation.
Core Logic
interface KeyframeData {
id: string;
offset: number; // 0–1 (0% to 100%)
properties: CSSProperties; // Typed subset of CSS properties
easing?: string; // Easing to this keyframe
}
interface AnimationConfig {
name: string;
keyframes: KeyframeData[];
duration: number; // seconds
timingFunction: string;
iterationCount: number | 'infinite';
direction: string;
fillMode: string;
}
function generateCSS(config: AnimationConfig): string {
const sorted = [...config.keyframes].sort((a, b) => a.offset - b.offset);
let css = `@keyframes ${config.name} {\n`;
for (const kf of sorted) {
const percent = kf.offset === 0 ? 'from' : kf.offset === 1 ? 'to' : `${Math.round(kf.offset * 100)}%`;
css += ` ${percent} {\n`;
for (const [prop, value] of Object.entries(kf.properties)) {
if (value !== undefined && value !== '') {
css += ` ${camelToKebab(prop)}: ${value};\n`;
}
}
if (kf.easing) css += ` animation-timing-function: ${kf.easing};\n`;
css += ` }\n`;
}
css += `}\n\n`;
css += `.animated-element {\n`;
css += ` animation: ${config.name} ${config.duration}s ${config.timingFunction} ${config.iterationCount === 'infinite' ? 'infinite' : config.iterationCount} ${config.direction} ${config.fillMode};\n`;
css += `}\n`;
return css;
}
// Apply animation to preview element via CSSOM
function applyAnimation(element: HTMLElement, config: AnimationConfig): Animation {
const keyframes = config.keyframes
.sort((a, b) => a.offset - b.offset)
.map(kf => ({ offset: kf.offset, ...kf.properties, easing: kf.easing }));
return element.animate(keyframes, {
duration: config.duration * 1000,
iterations: config.iterationCount === 'infinite' ? Infinity : config.iterationCount,
direction: config.direction as PlaybackDirection,
fill: config.fillMode as FillMode,
easing: config.timingFunction,
});
}
// Timeline scrubbing via Web Animations API
function scrubTo(animation: Animation, progress: number): void {
animation.pause();
animation.currentTime = progress * animation.effect!.getComputedTiming().duration! as number;
}
// Cubic bezier curve editor
function cubicBezierToCSS(x1: number, y1: number, x2: number, y2: number): string {
return `cubic-bezier(${x1.toFixed(2)}, ${y1.toFixed(2)}, ${x2.toFixed(2)}, ${y2.toFixed(2)})`;
}
Ghost Frames (Onion Skinning)
function renderGhostFrames(container: HTMLElement, element: HTMLElement, keyframes: KeyframeData[]): void {
container.innerHTML = '';
for (const kf of keyframes) {
const ghost = element.cloneNode(true) as HTMLElement;
ghost.style.opacity = '0.15';
ghost.style.position = 'absolute';
Object.assign(ghost.style, kf.properties);
// Label with percentage
const label = document.createElement('span');
label.textContent = `${Math.round(kf.offset * 100)}%`;
label.style.cssText = 'position:absolute;top:-18px;font-size:10px;color:#888;';
ghost.appendChild(label);
container.appendChild(ghost);
}
}
Privacy
Pure CSS generation. No data leaves the browser. Animation presets stored in localStorage.
UI
- Timeline (top): Horizontal bar with draggable keyframe markers, playhead scrubber
- Preview (center): Live animated element on configurable background, ghost frames toggle
- Properties panel (right): Per-keyframe property controls, organized by category (Transform, Color, Filter, etc.)
- Animation controls (bottom-left): Duration, easing, iteration, direction, fill mode, play/pause, speed
- Easing curve (bottom-right): Visual cubic-bezier editor with preset buttons
- CSS output (bottom): Live-updating
@keyframes CSS with copy button
- Presets: Save/load animation presets (bounce, fade, slide, spin, pulse, shake, flip)
- Responsive layout
Files to Create/Modify
| File |
Purpose |
tools/animation-builder/package.json |
Workspace package |
tools/animation-builder/src/tool.ts |
CSS generation, Web Animations API integration |
tools/animation-builder/src/timeline.ts |
Timeline UI component, drag handling, scrubbing |
tools/animation-builder/src/easing.ts |
Cubic bezier editor, preset curves |
tools/animation-builder/src/page.ts |
Full editor layout, preview, property panel |
tools/animation-builder/src/meta.ts |
ToolMeta |
tools/animation-builder/src/index.ts |
Exports |
tools/animation-builder/tests/tool.unit.test.ts |
CSS generation, keyframe sorting, easing output |
tools/animation-builder/tests/page.unit.test.ts |
DOM rendering tests |
src/index.ts |
Register route |
src/index.html |
Add to tool grid |
Description
Visual timeline editor for CSS
@keyframesanimations. Add keyframes on a draggable timeline, set properties per keyframe, and see a live preview on a customizable element. Exports clean@keyframesCSS. The visual approach makes animation authoring intuitive — no more guessing percentages.Features
Timeline Editor
Properties Panel
Per-keyframe CSS properties with visual controls:
color— Color pickerbackground-color— Color pickerborder-color— Color pickerbox-shadow— Reuse shadow editor logic from [Feat] Box Shadow & CSS Gradient Generator #99width/height(px, %, auto)border-radius(px or %)padding/marginblur()— slider 0–20pxbrightness()— slider 0–200%saturate()— slider 0–200%hue-rotate()— angle dial 0–360°grayscale()— slider 0–100%Animation Controls
my-animation)linear,ease,ease-in,ease-out,ease-in-out,cubic-bezier()with visual curve editorinfinitenormal,reverse,alternate,alternate-reversenone,forwards,backwards,bothPreview
Easing Curve Editor
animation-timing-functionwithin keyframes)Implementation
Dependencies
Zero — native CSS animations + DOM manipulation.
Core Logic
Ghost Frames (Onion Skinning)
Privacy
Pure CSS generation. No data leaves the browser. Animation presets stored in localStorage.
UI
@keyframesCSS with copy buttonFiles to Create/Modify
tools/animation-builder/package.jsontools/animation-builder/src/tool.tstools/animation-builder/src/timeline.tstools/animation-builder/src/easing.tstools/animation-builder/src/page.tstools/animation-builder/src/meta.tstools/animation-builder/src/index.tstools/animation-builder/tests/tool.unit.test.tstools/animation-builder/tests/page.unit.test.tssrc/index.tssrc/index.html