Audit and fix animation performance issues in Angular 21.x — layout thrashing, compositor properties, scroll-linked motion, will-change, blur effects, FLIP technique, and View Transitions API. Use when Angular animations stutter, transitions jank, scroll-linked motion is slow, or reviewing CSS/WAAPI animation code for performance. Works as a file auditor: /fixing-motion-performance <file> reports violations with exact line quotes and concrete fixes.
Instrucciones de origen · Vista previa de solo lectura
name
fixing-motion-performance
description
Audit and fix animation performance issues in Angular 21.x — layout thrashing, compositor properties, scroll-linked motion, will-change, blur effects, FLIP technique, and View Transitions API. Use when Angular animations stutter, transitions jank, scroll-linked motion is slow, or reviewing CSS/WAAPI animation code for performance. Works as a file auditor: /fixing-motion-performance <file> reports violations with exact line quotes and concrete fixes.
NEVER ANIMATE LAYOUT PROPERTIES (width, height, top, left, margin) ON LARGE SURFACES. Default to transform and opacity only. Resolve compositor vs paint vs layout tier BEFORE writing any animation CSS.
Fixing Motion Performance — Angular 21.x
When to Use
Load this skill when:
Angular CSS transitions or animations stutter or drop below 60fps
Implementing scroll-linked motion or reveal-on-scroll in Angular templates
Animating layout, filters, masks, gradients, or CSS variables
Reviewing Angular component styles that use will-change, transforms, or DOM measurement
Refactoring janky interactions in existing Angular components
How to Use as File Auditor
/fixing-motion-performance <file>
Review the file against all rules below and report:
violations — quote the exact line or snippet
why it matters — one short sentence
concrete fix — code-level suggestion with before/after
Do not migrate animation libraries unless explicitly requested. Apply rules within the existing Angular/TailwindCSS/daisyUI stack.
Rendering Steps Glossary
Understanding which browser rendering step an animation triggers determines its performance cost:
Step
Triggered By
Cost
composite
transform, opacity
Cheapest — runs on compositor thread, never blocks main thread
Most expensive — recalculates entire document flow
Rule: Always animate at the cheapest tier that achieves the design intent.
Rule Categories by Priority
Priority
Category
Impact
1
Never patterns
Critical
2
Choose the mechanism
Critical
3
Measurement
High
4
Scroll
High
5
Paint
Medium-High
6
Layers
Medium
7
Blur and filters
Medium
8
View transitions
Low
9
Tool boundaries
Critical
1. Never Patterns (Critical)
These always cause jank. Zero exceptions.
Do not interleave layout reads and writes in the same frame
Do not animate layout properties continuously on large or meaningful surfaces
Do not drive animation from scrollTop, scrollY, or scroll events
No requestAnimationFrame loops without a stop condition
Do not mix multiple animation systems that each measure or mutate layout
2. Choose the Mechanism (Critical)
Default to transform and opacity for all motion
Use JS-driven animation only when interaction requires it
Paint or layout animation is acceptable only on small, isolated surfaces
One-shot effects are acceptable more often than continuous motion
Prefer downgrading technique over removing motion entirely
Angular + Tailwind mapping
Intent
Wrong (causes layout/paint)
Correct (compositor)
Slide open panel
transition: width 0.3s
transition: transform 0.3s + scaleX()
Fade in element
transition: display
transition: opacity 0.2s + visibility
Card hover lift
transition: margin-top
transition: transform 0.2s + translateY(-4px)
Alert shake
transition: left
@keyframes with translateX
Expand accordion
transition: height 0.3s
FLIP technique (see §3) or grid-template-rows
3. Measurement (High)
Measure once, then animate via transform or opacity
Batch all DOM reads before writes — never interleave
Do not read layout (getBoundingClientRect, offsetHeight, scrollTop) during an animation
Prefer FLIP-style transitions for layout-like effects
FLIP Technique — Angular example
// Angular component: FLIP for expanding cardflipExpand(el: HTMLElement): void {
// FIRST — measure before state changeconst first = el.getBoundingClientRect();
// Apply the state change (add class, toggle signal)this.isExpanded.set(true);
// Force style recalc synchronously
el.offsetHeight; // force reflow once, intentionally// LAST — measure after state changeconst last = el.getBoundingClientRect();
// INVERT — animate from first to last positionconst deltaY = first.top - last.top;
const deltaScale = first.height / last.height;
el.style.transform = `translateY(${deltaY}px) scaleY(${deltaScale})`;
el.style.transition = 'none';
// PLAY — release to natural positionrequestAnimationFrame(() => {
el.style.transition = 'transform 0.3s ease-out';
el.style.transform = '';
});
}
4. Scroll (High)
Prefer CSS animation-timeline: view() or scroll() for scroll-linked motion
Use IntersectionObserver for visibility-triggered animations and pausing
Do not poll scrollTop / scrollY for animation in Angular components
Pause or stop animations when off-screen (use IntersectionObserver)
Scroll-linked motion must not trigger continuous layout or paint on large surfaces
Angular scroll-reveal pattern
// BAD — scroll event listener causes layout thrash@HostListener('window:scroll')
onScroll() {
const scrollY = window.scrollY; // forces layout readthis.opacity = Math.min(scrollY / 300, 1); // mutation in same frame
}
/* GOOD — CSS View Timeline, runs on compositor thread */.reveal-card {
animation: slideUp linear both;
animation-timeline: view();
animation-range: entry 0% entry 40%;
}
@keyframes slideUp {
from { opacity: 0; transform: translateY(24px); }
to { opacity: 1; transform: translateY(0); }
}
Paint-triggering animation is allowed only on small, isolated elements
Do not animate paint-heavy properties on large containers
Do not animate CSS variables that control transform, opacity, or position
Do not animate inherited CSS variables (the inheritance causes full repaint)
Scope animated CSS variables locally and avoid inheritance
Angular + daisyUI context
/* BAD — animating background triggers paint on full container */.hero { transition: background-color 1s; }
/* OK — small indicator badge, paint cost is acceptable */.badge { transition: background-color 0.2s; }
/* BAD — animating inherited CSS variable causes subtree repaint */:root { --card-bg: oklch(0.900); }
.card { transition: --card-bg 0.3s; } /* inherits, repaints all children *//* GOOD — scope locally, no inheritance */.card { transition: opacity 0.2s, transform 0.2s; }
6. Layers (Medium)
Compositor motion requires layer promotion — never assume it happens automatically
Use will-change temporarily and surgically (add before animation, remove after)
Avoid many or large promoted layers — each costs GPU memory
Validate layer behavior with Chrome DevTools → Layers panel when performance matters
/* BAD — permanent will-change on all cards wastes GPU memory */.card { will-change: transform; }
/* GOOD — add surgically before animation, remove after *//* In Angular component: */
Do not migrate or rewrite animation libraries unless explicitly requested
Apply these rules within the existing Angular/TailwindCSS/daisyUI animation system
Never partially migrate APIs or mix Tailwind transition-* and Angular Animations API in the same component
Do not introduce framer-motion, GSAP, or other JS animation libraries — workspace uses CSS/WAAPI
Common Fixes (Angular Context)
/* layout thrashing: animate transform instead of width *//* BEFORE */.panel { transition: width 0.3s; }
/* AFTER */.panel { transition: transform 0.3s; transform-origin: left; }
/* Use scaleX() to simulate width expansion without layout *//* scroll-linked: use CSS View Timeline instead of JS scroll listener *//* BEFORE (Angular @HostListener scroll) *//* AFTER */.reveal {
animation: fadeUp linear both;
animation-timeline: view();
animation-range: entry 0% entry 30%;
}
/* Tailwind: prefer translate over top/left *//* BEFORE */.dropdown { transition: top 0.2s; }
/* AFTER */.dropdown { transition: translate 0.2s; }
/* Tailwind v4: translate-y-2 → translate-y-0 with transition-transform */
// FLIP measurement pattern — batch reads before writesflipTransition(el: HTMLElement): void {
const first = el.getBoundingClientRect(); // readthis.expanded.set(true); // write (state change)
el.offsetHeight; // force single reflowconst last = el.getBoundingClientRect(); // read after writeconst dy = first.top - last.top;
el.style.transform = `translateY(${dy}px)`;
el.style.transition = 'none';
requestAnimationFrame(() => {
el.style.transition = 'transform 0.25s ease-out';
el.style.transform = 'none';
});
}
Review Guidance
Enforce critical rules first (never patterns, tool boundaries)
Choose the least expensive rendering work that matches the design intent
For any non-default choice, state the constraint that justifies it (surface size, duration, or interaction requirement)
When reviewing, prefer actionable notes and concrete alternatives over theory
Angular-specific: check whether @HostListener('window:scroll') is being used for animation — always flag for replacement with CSS View Timeline or IntersectionObserver