$npx -y skills add ibelick/ui-skills --skill fixing-motion-performanceAudit and fix animation performance issues including layout thrashing, compositor properties, scroll-linked motion, and blur effects. Use when animations stutter, transitions jank, or reviewing CSS/JS animation performance.
| 1 | # fixing-motion-performance |
| 2 | |
| 3 | Fix animation performance issues. |
| 4 | |
| 5 | ## how to use |
| 6 | |
| 7 | - `/fixing-motion-performance` |
| 8 | Apply these constraints to any UI animation work in this conversation. |
| 9 | |
| 10 | - `/fixing-motion-performance <file>` |
| 11 | Review the file against all rules below and report: |
| 12 | - violations (quote the exact line or snippet) |
| 13 | - why it matters (one short sentence) |
| 14 | - a concrete fix (code-level suggestion) |
| 15 | |
| 16 | Do not migrate animation libraries unless explicitly requested. Apply rules within the existing stack. |
| 17 | |
| 18 | ## when to apply |
| 19 | |
| 20 | Reference these guidelines when: |
| 21 | - adding or changing UI animations (CSS, WAAPI, Motion, rAF, GSAP) |
| 22 | - refactoring janky interactions or transitions |
| 23 | - implementing scroll-linked motion or reveal-on-scroll |
| 24 | - animating layout, filters, masks, gradients, or CSS variables |
| 25 | - reviewing components that use will-change, transforms, or measurement |
| 26 | |
| 27 | ## rendering steps glossary |
| 28 | |
| 29 | - composite: transform, opacity |
| 30 | - paint: color, borders, gradients, masks, images, filters |
| 31 | - layout: size, position, flow, grid, flex |
| 32 | |
| 33 | ## rule categories by priority |
| 34 | |
| 35 | | priority | category | impact | |
| 36 | |----------|----------|--------| |
| 37 | | 1 | never patterns | critical | |
| 38 | | 2 | choose the mechanism | critical | |
| 39 | | 3 | measurement | high | |
| 40 | | 4 | scroll | high | |
| 41 | | 5 | paint | medium-high | |
| 42 | | 6 | layers | medium | |
| 43 | | 7 | blur and filters | medium | |
| 44 | | 8 | view transitions | low | |
| 45 | | 9 | tool boundaries | critical | |
| 46 | |
| 47 | ## quick reference |
| 48 | |
| 49 | ### 1. never patterns (critical) |
| 50 | |
| 51 | - do not interleave layout reads and writes in the same frame |
| 52 | - do not animate layout continuously on large or meaningful surfaces |
| 53 | - do not drive animation from scrollTop, scrollY, or scroll events |
| 54 | - no requestAnimationFrame loops without a stop condition |
| 55 | - do not mix multiple animation systems that each measure or mutate layout |
| 56 | |
| 57 | ### 2. choose the mechanism (critical) |
| 58 | |
| 59 | - default to transform and opacity for motion |
| 60 | - use JS-driven animation only when interaction requires it |
| 61 | - paint or layout animation is acceptable only on small, isolated surfaces |
| 62 | - one-shot effects are acceptable more often than continuous motion |
| 63 | - prefer downgrading technique over removing motion entirely |
| 64 | |
| 65 | ### 3. measurement (high) |
| 66 | |
| 67 | - measure once, then animate via transform or opacity |
| 68 | - batch all DOM reads before writes |
| 69 | - do not read layout repeatedly during an animation |
| 70 | - prefer FLIP-style transitions for layout-like effects |
| 71 | - prefer approaches that batch measurement and writes |
| 72 | |
| 73 | ### 4. scroll (high) |
| 74 | |
| 75 | - prefer Scroll or View Timelines for scroll-linked motion when available |
| 76 | - use IntersectionObserver for visibility and pausing |
| 77 | - do not poll scroll position for animation |
| 78 | - pause or stop animations when off-screen |
| 79 | - scroll-linked motion must not trigger continuous layout or paint on large surfaces |
| 80 | |
| 81 | ### 5. paint (medium-high) |
| 82 | |
| 83 | - paint-triggering animation is allowed only on small, isolated elements |
| 84 | - do not animate paint-heavy properties on large containers |
| 85 | - do not animate CSS variables for transform, opacity, or position |
| 86 | - do not animate inherited CSS variables |
| 87 | - scope animated CSS variables locally and avoid inheritance |
| 88 | |
| 89 | ### 6. layers (medium) |
| 90 | |
| 91 | - compositor motion requires layer promotion, never assume it |
| 92 | - use will-change temporarily and surgically |
| 93 | - avoid many or large promoted layers |
| 94 | - validate layer behavior with tooling when performance matters |
| 95 | |
| 96 | ### 7. blur and filters (medium) |
| 97 | |
| 98 | - keep blur animation small (<=8px) |
| 99 | - use blur only for short, one-time effects |
| 100 | - never animate blur continuously |
| 101 | - never animate blur on large surfaces |
| 102 | - prefer opacity and translate before blur |
| 103 | |
| 104 | ### 8. view transitions (low) |
| 105 | |
| 106 | - use view transitions only for navigation-level changes |
| 107 | - avoid view transitions for interaction-heavy UI |
| 108 | - avoid view transitions when interruption or cancellation is required |
| 109 | - treat size changes as potentially layout-triggering |
| 110 | |
| 111 | ### 9. tool boundaries (critical) |
| 112 | |
| 113 | - do not migrate or rewrite animation libraries unless explicitly requested |
| 114 | - apply these rules within the existing animation system |
| 115 | - never partially migrate APIs or mix styles within the same component |
| 116 | |
| 117 | ## common fixes |
| 118 | |
| 119 | ```css |
| 120 | /* layout thrashing: animate transform instead of width */ |
| 121 | /* before */ .panel { transition: width 0.3s; } |
| 122 | /* after */ .panel { transition: transform 0.3s; } |
| 123 | |
| 124 | /* scroll-linked: use scroll-timeline instead of JS */ |
| 125 | /* before */ window.addEventListener('scroll', () => el.style.opacity = scrollY / 500) |
| 126 | /* after */ .reveal { animation: fade-in linear; animation-timeline: view(); } |
| 127 | ``` |
| 128 | |
| 129 | ```js |
| 130 | // measurement: batch reads before writes (FLIP) |
| 131 | // before — layout thrash |
| 132 | el.style.left = el.getBoundingClientRect().left + 10 + 'px'; |
| 133 | // after — measure once, animate via transform |
| 134 | const first = el.getBoundingClientRect(); |
| 135 | el.classList.add('moved'); |
| 136 | con |