brainstorming
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or planning anything new, in software or out of it (a talk, a business, a renovation).
fixing-motion-performanceAudit 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
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.
Load this skill when:
will-change, transforms, or DOM measurement/fixing-motion-performance <file>
Review the file against all rules below and report:
Do not migrate animation libraries unless explicitly requested. Apply rules within the existing Angular/TailwindCSS/daisyUI stack.
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 |
| paint | color, background, border, box-shadow, filter, gradients | Medium — rerasters pixels |
| layout | width, height, top, left, margin, padding, flex, grid | Most expensive — recalculates entire document flow |
Rule: Always animate at the cheapest tier that achieves the design intent.
| 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 |
These always cause jank. Zero exceptions.
scrollTop, scrollY, or scroll eventsrequestAnimationFrame loops without a stop conditiontransform and opacity for all motion| 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 |
transform or opacitygetBoundingClientRect, offsetHeight, scrollTop) during an animation// 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 = '';
});
}
animation-timeline: view() or scroll() for scroll-linked motionIntersectionObserver for visibility-triggered animations and pausingscrollTop / scrollY for animation in Angular componentsIntersectionObserver)// 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));
}
transform, opacity, or position/* 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; }
will-change temporarily and surgically (add before animation, remove after)/* 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
}
backdrop-blur changes ≤ 8px delta)blur() or backdrop-blur continuouslyopacity 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 */
}
Angular 21.x has native View Transition support via the router.
// angular router — enable view transitions
bootstrapApplication(AppComponent, {
providers: [
provideRouter(routes, withViewTransitions({
onViewTransitionCreated: ({ transition }) => {
// Cancel if interrupted by rapid navigation
if (transition.skipTransition) return;
}
}))
]
});
transition-* and Angular Animations API in the same componentframer-motion, GSAP, or other JS animation libraries — workspace uses CSS/WAAPI/* 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';
});
}
@HostListener('window:scroll') is being used for animation — always flag for replacement with CSS View Timeline or IntersectionObserverangular-spa — Angular component patterns; reference/animations.md covers timing standards and keyframesweb-performance-optimization — Core Web Vitals, bundle size, lazy loading (loading performance, not animation runtime)tailwind-patterns — Tailwind v4 @theme config and utility patternsaccessibility-audit — WCAG 2.1 prefers-reduced-motion compliance (complements this skill's performance rules)