Files
hyperframes/skills/hyperframes-animation/adapters/gsap-transforms-and-perf.md
T
Vance IngallsandClaude Opus 4.8 58f7dc1c72 docs(skills): teach transforms-over-layout-props up front
Reframe the gsap "prefer transforms" guidance from a GPU-perf nicety to the
real HyperFrames render-correctness reason: the seek-by-frame capture engine
integer-snaps layout props, so slow tweens stutter. Add the CSS-rest + x/y
offset conversion recipe, the parent-relative %/px note, and the one
exception (html-in-canvas <canvas layoutsubtree> elements). Frames the
gsap_non_transform_motion lint rule as the backstop, not the teacher, so
agents reach for transforms up front instead of relying on lint to reject.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 09:56:37 -07:00

113 lines
5.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Transforms and Performance
## Transform Aliases
Prefer GSAP's transform aliases over raw `transform` strings:
| GSAP property | Equivalent |
| --------------------------- | --------------------- |
| `x`, `y`, `z` | `translateX/Y/Z` (px) |
| `xPercent`, `yPercent` | `translateX/Y` in `%` |
| `scale`, `scaleX`, `scaleY` | `scale` |
| `rotation` | `rotate` (deg) |
| `rotationX`, `rotationY` | 3D rotate |
| `skewX`, `skewY` | `skew` |
| `transformOrigin` | `transform-origin` |
Aliases let GSAP track and interpolate each axis independently, which prevents accidental overwrites between separate tweens on the same element.
## autoAlpha
Prefer `autoAlpha` over `opacity` for show/hide:
```javascript
gsap.to(".panel", { autoAlpha: 0, duration: 0.4 });
```
`autoAlpha: 0` sets both `opacity: 0` and `visibility: hidden`, which removes the element from hit-testing and accessibility tree at zero alpha — closer to "gone" than plain `opacity: 0`.
## clearProps
Removes inline styles set by GSAP when the tween completes:
```javascript
gsap.to(".item", { x: 100, rotation: 45, clearProps: "all" });
gsap.to(".item", { x: 100, rotation: 45, clearProps: "rotation,x" });
```
Useful at the end of an animation segment to hand the element back to CSS.
## CSS Variables
```javascript
gsap.to(".chart", { "--hue": 180, duration: 1 });
```
Animate any custom property. Works for color, length, number — anything CSS will interpolate.
## Relative and Directional Values
- Relative: `"+=20"`, `"-=10"`, `"*=2"`.
- Directional rotation: `"360_cw"`, `"-170_short"`, `"90_ccw"` — controls which way the angle takes when going between two values.
## SVG Specifics
- `svgOrigin` sets transform origin in the SVG's global coordinate space (not the element's local box). **Do not** combine `svgOrigin` with `transformOrigin` on the same element — pick one.
- Animate SVG transform attributes via the same alias names (`x`, `y`, `rotation`) — GSAP handles the SVG-specific quirks.
## Performance Rules
### Animate transforms, not layout properties
Animate `x`, `y`, `scale`, `rotation`, `opacity`. Never animate `left`, `right`, `top`, `bottom`, `width`, `height`, `margin*` — and never `roundProps`.
This is a **render-correctness** rule in HyperFrames, not just a GPU-performance nicety. The renderer seeks frame-by-frame and screenshots each frame, and the browser compositor snaps layout properties to whole device pixels. On a fast tween the per-frame step is several pixels, so the snap is invisible; on a slow tween or a long ease-out tail the value moves less than a pixel per frame — it holds the same pixel for several frames, then jumps a whole one. The result is motion that looks smooth when fast but visibly stutters when slow. Transforms interpolate sub-pixel and stay smooth at any speed. `roundProps` forces the same integer snap onto a transform — don't use it.
**Convert a position animation to a transform** by leaving the element at its resting `left`/`top` in CSS and animating the _offset_ with `x`/`y`:
```javascript
// CSS: #card { left: 1340px; top: 540px } ← resting position stays in CSS
tl.to("#card", { left: 1340, top: 540, duration: 1 }); // ✗ stutters
tl.fromTo("#card", { x: 640, y: 0 }, { x: 0, y: 0, duration: 1 }); // ✓ x/y = delta from CSS rest (640 = startLeft 1340)
```
For a parent-relative `left: "100%"` sweep, use `xPercent: 100` only when the element is the full width of its container; otherwise convert to pixels (`x: containerWidth`).
**The one exception:** elements drawn through the html-in-canvas API — those under a `<canvas layoutsubtree>` ancestor, e.g. the `liquid-glass-*` blocks. The canvas rasterizes from sub-pixel `getComputedStyle`, so layout props don't snap there and those elements keep `left`/`top`. Everything the browser lays out (plain DOM) follows the rule.
The `gsap_non_transform_motion` lint rule is the backstop, not the teacher — reach for transforms from the start instead of animating layout props and waiting for lint to reject them.
### will-change (sparingly)
```css
.title {
will-change: transform;
}
```
Only on elements that _actually_ animate. Applied everywhere it becomes useless and burns memory.
### gsap.quickTo for frequent updates (preview-only)
For high-frequency updates driven by **events** — pointer move, scroll, audio scrub — `quickTo` reuses the same tween instead of creating a new one each frame:
```javascript
const xTo = gsap.quickTo("#cursor", "x", { duration: 0.4, ease: "power3" });
const yTo = gsap.quickTo("#cursor", "y", { duration: 0.4, ease: "power3" });
container.addEventListener("mousemove", (e) => {
xTo(e.pageX);
yTo(e.pageY);
});
```
> **Render mode has no input events.** The renderer seeks frame-by-frame; `mousemove`, `scroll`, etc. never fire. `quickTo`'s main use case applies in **live preview** in the browser only. For audio-reactive motion in renders, pre-extract audio data and drive the timeline declaratively (see `../rules/gsap-effects.md`).
### Stagger beats N tweens
One tween with `stagger` beats N tweens with manual delays for both readability and runtime cost.
### Cleanup
In live preview, pause or `kill()` off-screen animations. Render mode is unaffected (the renderer drives time directly).