Skip to content
Back to skills

Fixing Motion Performance

ASecurity

Fix animation jank and layout thrashing — compositor-only properties, will-change hints, avoiding layout-triggering CSS, GPU layer promotion, requestAnimationFrame usage, reduced-motion support, and auditing with DevTools Performance panel. Use when asked to "animation is janky", "fix animation performance", "layout thrashing", "GPU acceleration", "will-change", "compositor layer", "60fps animations", "CSS animation vs JS animation", "prefers-reduced-motion", "scroll performance", or "why is ...

  • 10 stars
  • 0 votes
  • 0 copies
  • 7 views
  • Added September 9, 2026
ai-agentsjavascriptjavareactperformance

Security analysis

A100/100

Scanned September 9, 2026

npx -y skills add yanacuti1121/Yana-AI --skill fixing-motion-performance --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Fixing Motion Performance?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Fixing Motion Performance
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/yanacuti1121-fixing-motion-performance/badge)](https://www.skillsdirectory.com/skills/yanacuti1121-fixing-motion-performance)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: fixing-motion-performance
description: >
  Fix animation jank and layout thrashing — compositor-only properties,
  will-change hints, avoiding layout-triggering CSS, GPU layer promotion,
  requestAnimationFrame usage, reduced-motion support, and auditing with
  DevTools Performance panel. Use when asked to "animation is janky",
  "fix animation performance", "layout thrashing", "GPU acceleration",
  "will-change", "compositor layer", "60fps animations", "CSS animation
  vs JS animation", "prefers-reduced-motion", "scroll performance", or
  "why is my transition slow". Do NOT use for: accessibility of motion —
  see fixing-accessibility. Do NOT use for: designing animation curves
  and timing — see motion-design.
origin: adapted:MIT © Ibelick
license: MIT © 2026 Vũ Văn Tâm
version: 1.0.0
compatibility: "All browsers. Framer Motion v11, GSAP v3 specific sections noted."
---

## When to Use

- Use when: animations drop below 60fps or feel "sticky"
- Use when: scrolling is janky on mobile
- Use when: DevTools shows long paint/layout frames during animation
- Use when: CSS transitions animate `width`, `height`, `top`, `left` (causes layout)
- Do NOT use for: choosing easing curves — that's motion-design
- Do NOT use for: reduced-motion accessibility only — see fixing-accessibility

---

## The Two Layers

```
Main thread          Compositor thread
───────────────      ─────────────────────────────────────────
Layout               transform: translate / scale / rotate
Paint                opacity
JavaScript           filter (some)
                     (runs on GPU — never blocks main thread)

Animating non-compositor properties → forces main thread work → jank
```

**Rule: animate ONLY `transform` and `opacity` for 60fps.**

---

## Compositor-Only Animations (Fast)

```css
/* ✅ Fast — runs on compositor, never touches layout */
.box {
  transition: transform 300ms ease, opacity 200ms ease;
}
.box:hover {
  transform: translateY(-4px) scale(1.02);
  opacity: 0.9;
}

/* ✅ Move with transform, not top/left */
.modal-enter { transform: translateY(20px); opacity: 0; }
.modal-open  { transform: translateY(0);    opacity: 1; }
```

---

## Layout-Triggering Properties (Slow)

```css
/* ❌ These trigger layout on every frame — avoid in animations */
.bad-animation {
  transition: width 300ms, height 300ms, top 300ms, left 300ms,
              margin 300ms, padding 300ms, font-size 300ms,
              border-width 300ms;
}

/* ✅ Replace with equivalent compositor animations */
/* Instead of width 0→100%: */
.reveal { transform: scaleX(0); transform-origin: left; }
.reveal.open { transform: scaleX(1); }

/* Instead of height auto: */
/* Use max-height trick or CSS Grid row expansion */
.collapsible { display: grid; grid-template-rows: 0fr; transition: grid-template-rows 300ms; }
.collapsible.open { grid-template-rows: 1fr; }
.collapsible > div { overflow: hidden; }
```

---

## will-change — Use Sparingly

```css
/* ✅ Only on elements that WILL animate, applied just before animation starts */
.card:hover {
  will-change: transform;   /* promote to own GPU layer before hover animates */
}

/* ❌ Don't apply globally — every will-change uses GPU memory */
* { will-change: transform; }  /* exhausts GPU memory */
.static-element { will-change: transform; }  /* no benefit, wastes memory */

/* Remove after animation completes if applied dynamically */
element.addEventListener('transitionend', () => {
  element.style.willChange = 'auto';
});
```

---

## Scroll Performance

```css
/* Prevent scroll-linked animations from blocking — use passive listeners */
/* In CSS: prevent scrolling from triggering paint */
.scroll-container {
  overflow-y: auto;
  overscroll-behavior: contain;   /* prevents scroll chaining */
  -webkit-overflow-scrolling: touch;
}

/* For sticky headers: use position:sticky (compositor) not scroll JS */
.header { position: sticky; top: 0; }
```

```js
// ❌ Blocking scroll handler
window.addEventListener('scroll', onScroll);

// ✅ Passive — can't call preventDefault, runs on compositor thread
window.addEventListener('scroll', onScroll, { passive: true });
```

---

## requestAnimationFrame

```js
// ❌ setTimeout-based animation — fires off-cadence, causes jank
let pos = 0;
function animate() {
  pos += 2;
  el.style.transform = `translateX(${pos}px)`;
  setTimeout(animate, 16);   // not synced with display refresh
}

// ✅ rAF — fires in sync with browser paint cycle
function animate() {
  pos += 2;
  el.style.transform = `translateX(${pos}px)`;
  requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
```

---

## Framer Motion — Avoiding Layout Animation Cost

```tsx
import { motion, LayoutGroup } from 'framer-motion';

// ❌ layout prop without LayoutGroup causes full-page layout measurement
<motion.div layout>

// ✅ Scope layout animations with LayoutGroup — limits measurement area
<LayoutGroup>
  <motion.div layout>
    {items.map(i => <motion.div key={i.id} layout />)}
  </motion.div>
</LayoutGroup>

// For list reordering, use layoutId for shared element transitions
<motion.div layoutId={`card-${id}`} />
```

---

## prefers-reduced-motion

```css
/* Always respect user system preference */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
```

```tsx
// In JS/React
const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const duration = prefersReduced ? 0 : 300;
```

---

## DevTools Audit

```
Chrome DevTools → Performance tab:
  1. Record 3–5s while animation plays
  2. Look at "Frames" row — red = dropped frame (< 60fps)
  3. Expand frame → look for "Layout" or "Paint" tasks during animation
  4. "Rendering" panel → enable "Paint flashing" — green overlay = repaint
  5. "Layers" panel — each will-change element should be its own layer

Target: all animation frames < 16ms (60fps) on mid-range mobile
```

---

## Anti-Fake-Pass Rules

Before claiming animation performance is fixed, you MUST show:
- [ ] All animated properties are `transform` or `opacity` — no `width/height/top/left`
- [ ] `will-change` used only on actually-animated elements, not globally
- [ ] Scroll handlers use `{ passive: true }` option
- [ ] JS animations use `requestAnimationFrame`, not `setTimeout`
- [ ] `prefers-reduced-motion` media query applied globally
- [ ] No layout-triggering properties in `transition:` declarations
- [ ] DevTools Performance shows no dropped frames during animation (or documented acceptable tradeoff)

Reference: `gates/anti-fake-pass-gate.md`

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.