The interface is localized. Skill descriptions and documents are machine-translated by AI into your language; code, commands, paths and technical terms stay in the original, and the author's document governs.
Explore skills
Development · SKILL.md

fixing-motion-performance

fixing-motion-performance

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: /…

Independent GitHub snapshot · Automated classification · Original documentation governs

Skill documentation

GitHub

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 reports violations with exact line quotes and concrete fixes." risk: low source: community (adapted for Angular 21.x) date_added: "2026-03-15" allowed-tools: Read, Grep, Bash metadata: triggers: animation jank, stutter, transition performance, layout thrashing, will-change, compositor, scroll-linked animation, scroll reveal, FLIP, CSS animation performance, frame drop, 60fps, blur performance, view transitions, janky scroll related-skills: angular-spa, web-performance-optimization, tailwind-patterns, accessibility-audit domain: frontend role: specialist scope: performance output-format: document last-reviewed: "2026-03-15"

Iron Law

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:

StepTriggered ByCost
compositetransform, opacityCheapest — runs on compositor thread, never blocks main thread
paintcolor, background, border, box-shadow, filter, gradientsMedium — rerasters pixels
layoutwidth, height, top, left, margin, padding, flex, gridMost expensive — recalculates entire document flow

Rule: Always animate at the cheapest tier that achieves the design intent.


Rule Categories by Priority

PriorityCategoryImpact
1Never patternsCritical
2Choose the mechanismCritical
3MeasurementHigh
4ScrollHigh
5PaintMedium-High
6LayersMedium
7Blur and filtersMedium
8View transitionsLow
9Tool boundariesCritical

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

IntentWrong (causes layout/paint)Correct (compositor)
Slide open paneltransition: width 0.3stransition: transform 0.3s + scaleX()
Fade in elementtransition: displaytransition: opacity 0.2s + visibility
Card hover lifttransition: margin-toptransition: transform 0.2s + translateY(-4px)
Alert shaketransition: left@keyframes with translateX
Expand accordiontransition: height 0.3sFLIP 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 card
flipExpand(el: HTMLElement): void {
  // FIRST — measure before state change
  const 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 change
  const last = el.getBoundingClientRect();

  // INVERT — animate from first to last position
  const 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 position
  requestAnimationFrame(() => {
    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 read
  this.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); }
}
// GOOD — IntersectionObserver for class-based reveal
ngAfterViewInit(): void {
  const observer = new IntersectionObserver(
    (entries) => entries.forEach(e => {
      if (e.isIntersecting) {
        e.target.classList.add('animate-slide-up');
        observer.unobserve(e.target); // one-shot
      }
    }),
    { threshold: 0.15 }
  );
  this.cards().forEach(card => observer.observe(card.nativeElement));
}

5. Paint (Medium-High)

  • 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.9 0 0); }
.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: */
onHoverStart(el: HTMLElement): void {
  el.style.willChange = 'transform';
}
onHoverEnd(el: HTMLElement): void {
  el.style.willChange = 'auto'; // release layer after animation
}

7. Blur and Filters (Medium)

  • Keep blur animation radius small (backdrop-blur changes ≤ 8px delta)
  • Use blur only for short, one-time effects (modal open, not continuous hover)
  • Never animate blur() or backdrop-blur continuously
  • Never animate blur on large surfaces (full-page overlays)
  • Prefer opacity and translate over blur for reveal effects
/* BAD — continuous blur on large surface */
.hero-overlay { transition: backdrop-filter 0.5s; }
.hero-overlay:hover { backdrop-filter: blur(20px); } /* GPU-expensive on large area */

/* OK — one-shot modal open, small delta */
.modal-backdrop {
  animation: blurIn 200ms ease-out forwards;
}
@keyframes blurIn {
  from { opacity: 0; backdrop-filter: blur(0px); }
  to   { opacity: 1; backdrop-filter: blur(4px); } /* <= 8px delta */
}

8. View Transitions (Low)

Angular 21.x has native View Transition support via the router.

  • Use view transitions only for navigation-level page changes
  • Avoid view transitions for interaction-heavy UI (tabs, toggles, accordions)
  • Avoid view transitions when interruption or cancellation is required
  • Treat size changes in view transitions as potentially layout-triggering
// angular router — enable view transitions
bootstrapApplication(AppComponent, {
  providers: [
    provideRouter(routes, withViewTransitions({
      onViewTransitionCreated: ({ transition }) => {
        // Cancel if interrupted by rapid navigation
        if (transition.skipTransition) return;
      }
    }))
  ]
});

9. Tool Boundaries (Critical)

  • 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 writes
flipTransition(el: HTMLElement): void {
  const first = el.getBoundingClientRect(); // read
  this.expanded.set(true);                  // write (state change)
  el.offsetHeight;                          // force single reflow
  const last = el.getBoundingClientRect();  // read after write
  const 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

Related Skills

  • angular-spa — Angular component patterns; reference/animations.md covers timing standards and keyframes
  • web-performance-optimization — Core Web Vitals, bundle size, lazy loading (loading performance, not animation runtime)
  • tailwind-patterns — Tailwind v4 @theme config and utility patterns
  • accessibility-audit — WCAG 2.1 prefers-reduced-motion compliance (complements this skill's performance rules)
Saved skills