mirror of
https://github.com/greensock/gsap-skills
synced 2026-09-22 23:34:24 +08:00
Bulk additions and refinements
This commit is contained in:
@@ -29,6 +29,37 @@ Always use **property names in camelCase** in the vars object (e.g. `backgroundC
|
||||
- **repeat** — number or `-1` for infinite.
|
||||
- **yoyo** — boolean; with repeat, alternates direction.
|
||||
- **onComplete**, **onStart**, **onUpdate** — callbacks; scoped to the Animation instance itself (Tween or Timeline).
|
||||
- **immediateRender** — When `true` (default for **from()** and **fromTo()**), the tween’s start state is applied as soon as the tween is created (avoids flash of unstyled content and works well with staggered timelines). When **multiple from() or fromTo() tweens** target the same property of the same element, set **immediateRender: false** on the later one(s) so the first tween’s end state is not overwritten before it runs; otherwise the second animation may not be visible.
|
||||
|
||||
## Transforms and CSS properties
|
||||
|
||||
GSAP’s CSSPlugin (included in core) animates DOM elements. Use **camelCase** for CSS properties (e.g. `fontSize`, `backgroundColor`). Prefer GSAP’s **transform aliases** over the raw `transform` string: they apply in a consistent order (translation → scale → rotationX/Y → skew → rotation), are more performant, and work reliably across browsers.
|
||||
|
||||
**Transform aliases (prefer over translateX(), rotate(), etc.):**
|
||||
|
||||
| GSAP property | Equivalent CSS / note |
|
||||
|---------------|------------------------|
|
||||
| `x`, `y`, `z` | translateX/Y/Z (default unit: px) |
|
||||
| `xPercent`, `yPercent` | translateX/Y in %; use for percentage-based movement; work on SVG |
|
||||
| `scale`, `scaleX`, `scaleY` | scale; `scale` sets both X and Y |
|
||||
| `rotation` | rotate (default: deg; or `"1.25rad"`) |
|
||||
| `rotationX`, `rotationY` | 3D rotate (rotationZ = rotation) |
|
||||
| `skewX`, `skewY` | skew (deg or rad string) |
|
||||
| `transformOrigin` | transform-origin (e.g. `"left top"`, `"50% 50%"`) |
|
||||
|
||||
Relative values work: `x: "+=20"`, `rotation: "-=30"`. Default units: x/y in px, rotation in deg.
|
||||
|
||||
- **autoAlpha** — Prefer over `opacity` for fade in/out. When the value is `0`, GSAP also sets `visibility: hidden` (better rendering and no pointer events); when non-zero, `visibility` is set to `inherit`. Avoids leaving invisible elements blocking clicks.
|
||||
- **CSS variables** — GSAP can animate custom properties (e.g. `"--hue": 180`, `"--size": 100`). Supported in browsers that support CSS variables.
|
||||
- **svgOrigin** _(SVG only)_ — Like `transformOrigin` but in the SVG’s **global** coordinate space (e.g. `svgOrigin: "250 100"`). Use when several SVG elements should rotate or scale around a common point. Only one of `svgOrigin` or `transformOrigin` can be used. No percentage values; units optional.
|
||||
- **Directional rotation** — Append a suffix to rotation values (string): **`_short`** (shortest path), **`_cw`** (clockwise), **`_ccw`** (counter-clockwise). Applies to `rotation`, `rotationX`, `rotationY`. Example: `rotation: "-170_short"` (20° clockwise instead of 340° counter-clockwise); `rotationX: "+=30_cw"`.
|
||||
- **clearProps** — Comma-separated list of property names (or `"all"` / `true`) to **remove** from the element’s inline style when the tween completes. Use when a class or other CSS should take over after the animation. Clearing any transform-related property (e.g. `x`, `scale`, `rotation`) clears the **entire** transform.
|
||||
|
||||
```javascript
|
||||
gsap.to(".box", { x: 100, rotation: "360_cw", duration: 1 });
|
||||
gsap.to(".fade", { autoAlpha: 0, duration: 0.5, clearProps: "visibility" });
|
||||
gsap.to(svgEl, { rotation: 90, svgOrigin: "100 100" });
|
||||
```
|
||||
|
||||
## Targets
|
||||
|
||||
@@ -139,8 +170,17 @@ Set project-wide Tween defaults with **gsap.defaults()**:
|
||||
gsap.defaults({ duration: 0.6, ease: "power2.out" });
|
||||
```
|
||||
|
||||
## Best practices
|
||||
|
||||
- ✅ Use **property names in camelCase** in vars (e.g. `backgroundColor`, `rotationX`).
|
||||
- ✅ Prefer **transform aliases** (`x`, `y`, `scale`, `rotation`, `xPercent`, `yPercent`, etc.) over animating the raw `transform` string; use **autoAlpha** instead of `opacity` for fade in/out when elements should be hidden and non-interactive at 0.
|
||||
- ✅ Use documented built-in eases; use CustomEase when a custom curve is needed.
|
||||
- ✅ Store the tween/timeline return value when controlling playback (pause, play, reverse, kill).
|
||||
|
||||
## Do Not
|
||||
|
||||
- Animate layout-heavy properties (e.g. `width`, `height`, `top`, `left`) when **transform** properties (`x`, `y`, `scaleX`, `scaleY`, `rotation`) can achieve the same effect; prefer transforms for better performance.
|
||||
- Use invalid or non-existent ease names; stick to documented eases.
|
||||
- Forget that **gsap.from()** uses the element’s current state as the end state; the initial values in the tween will be applied immediately unless `immediateRender: false` is in the `vars`.
|
||||
- ❌ Animate layout-heavy properties (e.g. `width`, `height`, `top`, `left`) when transform aliases (`x`, `y`, `scale`, `rotation`) can achieve the same effect; prefer transforms for better performance.
|
||||
- ❌ Use both **svgOrigin** and **transformOrigin** on the same SVG element; only one applies.
|
||||
- ❌ Rely on the default **immediateRender: true** when stacking multiple **from()** or **fromTo()** tweens on the same property of the same target; set **immediateRender: false** on the later tweens so they animate correctly.
|
||||
- ❌ Use invalid or non-existent ease names; stick to documented eases.
|
||||
- ❌ Forget that **gsap.from()** uses the element’s current state as the end state; the initial values in the tween will be applied immediately unless `immediateRender: false` is in the `vars`.
|
||||
|
||||
@@ -14,8 +14,8 @@ Apply when optimizing GSAP animations for smooth 60fps, reducing layout/paint co
|
||||
|
||||
Animating **transform** (`x`, `y`, `scaleX`, `scaleY`, `rotation`, `rotationX`, `rotationY`, `skewX`, `skewY`) and **opacity** keeps work on the compositor and avoids layout and most paint. Avoid animating layout-heavy properties when a transform can achieve the same effect.
|
||||
|
||||
- Prefer: **x**, **y**, **scale**, **rotation**, **opacity**.
|
||||
- Avoid when possible: **width**, **height**, **top**, **left**, **margin**, **padding** (they trigger layout and can cause jank).
|
||||
- ✅ Prefer: **x**, **y**, **scale**, **rotation**, **opacity**.
|
||||
- ❌ Avoid when possible: **width**, **height**, **top**, **left**, **margin**, **padding** (they trigger layout and can cause jank).
|
||||
|
||||
GSAP’s **x** and **y** use transforms (translate) by default; use them instead of **left**/**top** for movement.
|
||||
|
||||
@@ -62,9 +62,16 @@ document.querySelector("#container").addEventListener("mousemove", (e) => {
|
||||
- Pause or kill off-screen or inactive animations when they’re not visible (e.g. when the user navigates away).
|
||||
- Avoid animating huge numbers of properties on many elements at once; simplify or sequence if needed.
|
||||
|
||||
## Best practices
|
||||
|
||||
- ✅ Animate **transform** and **opacity**; use **will-change** in CSS only on elements that animate.
|
||||
- ✅ Use **stagger** instead of many separate tweens with manual delays when the animation is the same.
|
||||
- ✅ Use **gsap.quickTo()** for frequently updated properties (e.g. mouse followers).
|
||||
- ✅ Clean up or kill off-screen animations; call **ScrollTrigger.refresh()** when layout changes, debounced when possible.
|
||||
|
||||
## Do Not
|
||||
|
||||
- Animate **width**/ **height**/ **top**/ **left** for movement when **x**/ **y**/ **scale** can achieve the same look.
|
||||
- Set **will-change** or **force3D** on every element “just in case”; use for elements that are actually animating.
|
||||
- Create hundreds of overlapping tweens or ScrollTriggers without testing on low-end devices.
|
||||
- Ignore cleanup; stray tweens and ScrollTriggers keep running and can hurt performance and correctness.
|
||||
- ❌ Animate **width**/ **height**/ **top**/ **left** for movement when **x**/ **y**/ **scale** can achieve the same look.
|
||||
- ❌ Set **will-change** or **force3D** on every element “just in case”; use for elements that are actually animating.
|
||||
- ❌ Create hundreds of overlapping tweens or ScrollTriggers without testing on low-end devices.
|
||||
- ❌ Ignore cleanup; stray tweens and ScrollTriggers keep running and can hurt performance and correctness.
|
||||
|
||||
+203
-14
@@ -23,8 +23,8 @@ import { Draggable } from "gsap/Draggable";
|
||||
gsap.registerPlugin(ScrollToPlugin, Flip, Draggable);
|
||||
```
|
||||
|
||||
- Register before using the plugin in any tween or API call.
|
||||
- In React, register at top level or once in the app (e.g. before first useGSAP); do not register inside a component that re-renders. useGSAP is a plugin that needs to be registered before use.
|
||||
- ✅ Register before using the plugin in any tween or API call.
|
||||
- ✅ In React, register at top level or once in the app (e.g. before first useGSAP); do not register inside a component that re-renders. useGSAP is a plugin that needs to be registered before use.
|
||||
|
||||
## Scroll
|
||||
|
||||
@@ -40,7 +40,13 @@ gsap.to(window, { duration: 1, scrollTo: { y: "#section", offsetY: 50 } });
|
||||
gsap.to(scrollContainer, { duration: 1, scrollTo: { x: "max" } });
|
||||
```
|
||||
|
||||
- **scrollTo**: `{ x, y, element, offsetX, offsetY }`. Use `"max"` for maximum scroll. Element can be selector or numeric position.
|
||||
**ScrollToPlugin — key config (scrollTo object):**
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `x`, `y` | Target scroll position (number), or `"max"` for maximum |
|
||||
| `element` | Selector or element to scroll to (for scroll-into-view) |
|
||||
| `offsetX`, `offsetY` | Offset in pixels from the target position |
|
||||
|
||||
### ScrollSmoother
|
||||
|
||||
@@ -71,8 +77,15 @@ const state = Flip.getState(".item");
|
||||
Flip.from(state, { duration: 0.5, ease: "power2.inOut" });
|
||||
```
|
||||
|
||||
- **Flip.getState()** — pass element(s) or selector; returns state object.
|
||||
- **Flip.from(state, vars)** — animate from that state to current layout. Options: `absolute`, `nested`, `scale`, `simple`, etc.
|
||||
**Flip — key config (Flip.from vars):**
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `absolute` | Use `position: absolute` during the flip (default: `false`) |
|
||||
| `nested` | When true, only the first level of children is measured (better for nested transforms) |
|
||||
| `scale` | When true, scale elements to fit (avoids stretch); default `true` |
|
||||
| `simple` | When true, only position/scale are animated (faster, less accurate) |
|
||||
| `duration`, `ease` | Standard tween options |
|
||||
|
||||
#### More information
|
||||
|
||||
@@ -89,10 +102,17 @@ Draggable.create(".box", { type: "x,y", bounds: "#container", inertia: true });
|
||||
Draggable.create(".knob", { type: "rotation" });
|
||||
```
|
||||
|
||||
- **type**: `"x"`, `"y"`, `"x,y"`, `"rotation"`, etc.
|
||||
- **bounds**: element or `{ minX, maxX, minY, maxY }`.
|
||||
- **inertia**: boolean; uses InertiaPlugin if true (requires InertiaPlugin registered).
|
||||
- Events: **onDragStart**, **onDrag**, **onDragEnd**, **onThrowUpdate**, **onThrowComplete**.
|
||||
**Draggable — key config options:**
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `type` | `"x"`, `"y"`, `"x,y"`, `"rotation"`, `"scroll"` |
|
||||
| `bounds` | Element, selector, or `{ minX, maxX, minY, maxY }` to constrain drag |
|
||||
| `inertia` | `true` to enable throw/momentum (requires InertiaPlugin) |
|
||||
| `edgeResistance` | 0–1; resistance when dragging past bounds |
|
||||
| `cursor` | CSS cursor during drag |
|
||||
| `onDragStart`, `onDrag`, `onDragEnd` | Callbacks; receive event and target |
|
||||
| `onThrowUpdate`, `onThrowComplete` | Callbacks when inertia is active |
|
||||
|
||||
### Inertia (InertiaPlugin)
|
||||
|
||||
@@ -131,31 +151,145 @@ Observer.create({
|
||||
});
|
||||
```
|
||||
|
||||
**Observer — key config options:**
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `target` | Element or selector to observe |
|
||||
| `onUp`, `onDown`, `onLeft`, `onRight` | Callbacks when swipe/scroll passes tolerance in that direction |
|
||||
| `tolerance` | Pixels before direction is detected; default 10 |
|
||||
| `type` | `"touch"`, `"pointer"`, or `"wheel"` (default: `"touch,pointer"`) |
|
||||
|
||||
## Text
|
||||
|
||||
### SplitText
|
||||
|
||||
Splits text into chars, words, or lines for staggered or per-char animation. Use when animating text character-by-character or word-by-word. Restores original text on `revert()`; or let `gsap.context()` revert.
|
||||
Splits an element’s text into characters, words, and/or lines (each in its own element) for staggered or per-unit animation. Use when animating text character-by-character, word-by-word, or line-by-line. Returns an instance with **chars**, **words**, **lines** (and **masks** when `mask` is set). Restore original markup with **revert()** or let **gsap.context()** revert. Integrates with **gsap.context()**, **matchMedia()**, and **useGSAP()**. API: **SplitText.create(target, vars)** (target = selector, element, or array).
|
||||
|
||||
```javascript
|
||||
gsap.registerPlugin(SplitText);
|
||||
|
||||
const split = SplitText.create(".heading", { type: "words, chars" });
|
||||
gsap.from(split.chars, { opacity: 0, y: 20, stagger: 0.03, duration: 0.4 });
|
||||
// later: split.revert() or let gsap.context() cleanup revert
|
||||
```
|
||||
|
||||
With **onSplit()** (v3.13.0+), animations run on each split and on re-split when **autoSplit** is used; returning a tween/timeline from **onSplit()** lets SplitText clean up and sync progress on re-split:
|
||||
|
||||
```javascript
|
||||
SplitText.create(".split", {
|
||||
type: "lines",
|
||||
autoSplit: true,
|
||||
onSplit(self) {
|
||||
return gsap.from(self.lines, { y: 100, opacity: 0, stagger: 0.05, duration: 0.5 });
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
**SplitText — key config (SplitText.create vars):**
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| **type** | Comma-separated: `"chars"`, `"words"`, `"lines"`. Default `"chars,words,lines"`. Only split what is needed (e.g. `"words, chars"` if not using lines) for performance. Avoid chars-only without words/lines or use **smartWrap: true** to prevent odd line breaks. |
|
||||
| **charsClass**, **wordsClass**, **linesClass** | CSS class on each split element. Append `"++"` to add an incremented class (e.g. `linesClass: "line++"` → `line1`, `line2`, …). |
|
||||
| **aria** | `"auto"` (default), `"hidden"`, or `"none"`. Accessibility: `"auto"` adds `aria-label` on the split element and `aria-hidden` on line/word/char elements so screen readers read the label; `"hidden"` hides all from readers; `"none"` leaves aria unchanged. Use `"none"` plus a screen-reader-only duplicate if nested links/semantics must be exposed. |
|
||||
| **autoSplit** | When `true`, reverts and re-splits when fonts finish loading or when the element width changes (and lines are split), avoiding wrong line breaks. **Animations must be created inside onSplit()** so they target the newly split elements; **return** the animation from **onSplit()** for automatic cleanup and time-sync on re-split. |
|
||||
| **onSplit(self)** | Callback when split completes (and on each re-split if **autoSplit** is `true`). Receives the SplitText instance. Returning a GSAP tween or timeline enables automatic revert/sync of that animation when re-splitting. |
|
||||
| **mask** | `"lines"`, `"words"`, or `"chars"`. Wraps each unit in an extra element with `overflow: clip` for mask/reveal effects. Only one type; access wrappers on the instance’s **masks** array (or use class `-mask` if a class is set). |
|
||||
| **tag** | Wrapper element tag; default `"div"`. Use `"span"` for inline (note: transforms like rotation/scale may not render on inline elements in some browsers). |
|
||||
| **deepSlice** | When `true` (default), nested elements (e.g. `<strong>`) that span multiple lines are subdivided so lines don’t stretch vertically. Only applies when splitting lines. |
|
||||
| **ignore** | Selector or element(s) to leave unsplit (e.g. `ignore: "sup"`). |
|
||||
| **smartWrap** | When splitting **chars** only, wraps words in a `white-space: nowrap` span to avoid mid-word line breaks. Ignored if words or lines are split. Default `false`. |
|
||||
| **wordDelimiter** | Word boundary: string (default `" "`), RegExp, or `{ delimiter: RegExp, replaceWith: string }` for custom splitting (e.g. zero-width joiner for hashtags, or non-Latin). |
|
||||
| **prepareText(text, parent)** | Function that receives raw text and parent element; return modified text before splitting (e.g. to insert break markers for languages without spaces). |
|
||||
| **propIndex** | When `true`, adds a CSS variable with index on each split element (e.g. `--word: 1`, `--char: 2`). |
|
||||
| **reduceWhiteSpace** | Collapse consecutive spaces; default `true`. From v3.13.0 also honors line breaks and can insert `<br>` for `<pre>`. |
|
||||
| **onRevert** | Callback when the instance is reverted. |
|
||||
|
||||
**Tips:** Split only what is animated (e.g. skip chars if only animating words). For custom fonts, split after they load (e.g. `document.fonts.ready.then(...)`) or use **autoSplit: true** with **onSplit()**. To avoid kerning shift when splitting chars, use CSS `font-kerning: none; text-rendering: optimizeSpeed;`. Avoid `text-wrap: balance`; it can interfere with splitting. SplitText does not support SVG `<text>`.
|
||||
|
||||
**Learn more:** [SplitText](https://gsap.com/docs/v3/Plugins/SplitText/)
|
||||
|
||||
### ScrambleText
|
||||
|
||||
Animates text with a scramble/glitch effect. Use when revealing or transitioning text with a scramble.
|
||||
|
||||
```javascript
|
||||
gsap.registerPlugin(ScrambleTextPlugin);
|
||||
|
||||
gsap.to(".text", {
|
||||
duration: 1,
|
||||
scrambleText: { text: "New message", chars: "01", revealDelay: 0.5 }
|
||||
});
|
||||
```
|
||||
|
||||
## SVG
|
||||
|
||||
### DrawSVG (DrawSVGPlugin)
|
||||
|
||||
Animates SVG stroke dash (draw/undraw strokes). Use when “drawing” or “erasing” SVG paths.
|
||||
Reveals or hides the stroke of SVG elements by animating `stroke-dashoffset` / `stroke-dasharray`. Works on `<path>`, `<line>`, `<polyline>`, `<polygon>`, `<rect>`, `<ellipse>`. Use when “drawing” or “erasing” strokes.
|
||||
|
||||
**drawSVG value:** Describes the **visible segment** of the stroke along the path (start and end positions), not “animate from A to B over time.” Format: `"start end"` in percent or length. Examples: `"0% 100%"` = full stroke; `"20% 80%"` = stroke only between 20% and 80% (gaps at both ends). The tween animates from the element’s **current** segment to the **target** segment — e.g. `gsap.to("#path", { drawSVG: "0% 100%" })` goes from whatever it is now to full stroke. Single value (e.g. `0`, `"100%"`) means start is 0: `"100%"` is equivalent to `"0% 100%"`.
|
||||
|
||||
**Required:** The element must have a visible stroke — set `stroke` and `stroke-width` in CSS or as SVG attributes; otherwise nothing is drawn.
|
||||
|
||||
```javascript
|
||||
gsap.registerPlugin(DrawSVGPlugin);
|
||||
|
||||
// draw from nothing to full stroke
|
||||
gsap.from("#path", { duration: 1, drawSVG: 0 });
|
||||
// or explicit segment: from 0–0 to 0–100%
|
||||
gsap.fromTo("#path", { drawSVG: "0% 0%" }, { drawSVG: "0% 100%", duration: 1 });
|
||||
// stroke only in the middle (gaps at ends)
|
||||
gsap.to("#path", { duration: 1, drawSVG: "20% 80%" });
|
||||
```
|
||||
|
||||
**Caveats:** Only affects stroke (not fill). Prefer single-segment `<path>` elements; multi-segment paths can render oddly in some browsers. Contents of `<use>` cannot be visually changed. **DrawSVGPlugin.getLength(element)** and **DrawSVGPlugin.getPosition(element)** return stroke length and current position.
|
||||
|
||||
**Learn more:** [DrawSVG](https://gsap.com/docs/v3/Plugins/DrawSVGPlugin)
|
||||
|
||||
### MorphSVG (MorphSVGPlugin)
|
||||
|
||||
Morphs one SVG shape into another. Use when morphing paths (e.g. icon A → icon B). Uses path data; shapes do not need to have the same number of points; they're converted to cubic beziers internally and points are added as necessary.
|
||||
Morphs one SVG shape into another by animating the `d` attribute (path data). Start and end shapes do not need the same number of points — MorphSVG converts to cubic beziers and adds points as needed. Use for icon-to-icon morphs, shape transitions, or path-based animations. Works on `<path>`, `<polyline>`, and `<polygon>`; `<circle>`, `<rect>`, `<ellipse>`, and `<line>` are converted internally or via **MorphSVGPlugin.convertToPath(selector | element)** (replaces the element in the DOM with a `<path>`).
|
||||
|
||||
**morphSVG value:** Can be a **selector** (e.g. `"#lightning"`), an **element**, **raw path data** (e.g. `"M47.1,0.8 73.3,0.8..."`), or for polygon/polyline a **points string** (e.g. `"240,220 240,70 70,70 70,220"`). For full config use the **object form** with **shape** as the only required property.
|
||||
|
||||
```javascript
|
||||
gsap.registerPlugin(MorphSVGPlugin);
|
||||
|
||||
// convert primitives to path first if needed:
|
||||
MorphSVGPlugin.convertToPath("circle, rect, ellipse, line");
|
||||
|
||||
gsap.to("#diamond", { duration: 1, morphSVG: "#lightning", ease: "power2.inOut" });
|
||||
// object form:
|
||||
gsap.to("#diamond", {
|
||||
duration: 1,
|
||||
morphSVG: { shape: "#lightning", type: "rotational", shapeIndex: 2 }
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
**MorphSVG — key config (morphSVG object):**
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| **shape** | _(Required.)_ Target shape: selector, element, or raw path string. |
|
||||
| **type** | `"linear"` (default) or `"rotational"`. Rotational uses angle/length interpolation and can avoid kinks mid-morph; try it when linear looks wrong. |
|
||||
| **map** | How segments are matched: `"size"` (default), `"position"`, or `"complexity"`. Use when start/end segments don’t line up; if none work, split into multiple paths and morph each. |
|
||||
| **shapeIndex** | Offsets which point in the start path maps to the first point in the end path (avoids shape “crossing over” or inverting). Number for single-segment paths; **array** for multi-segment (e.g. `[5, 1, -8]`). Negative reverses that segment. Use **shapeIndex: "log"** once to log the auto-calculated value, then paste the number/array into the tween. **findShapeIndex(start, end)** (separate utility) provides an interactive UI to find a good value. Only applies to closed paths. |
|
||||
| **smooth** | (v3.14+). Adds smoothing points. Number (e.g. `80`), `"auto"`, or object: `{ points: 40 \| "auto", redraw: true \| false, persist: true \| false }`. `redraw: false` keeps original anchors (perfect fidelity, less even spacing). `persist: false` removes added points when the tween ends. Use when the default morph looks jagged or unnatural. |
|
||||
| **curveMode** | Boolean (v3.14+). Interpolates control-handle angle/length instead of raw x/y to avoid kinks on curves. Try if a morph has a mid-morph kink. |
|
||||
| **origin** | Rotation origin for **type: "rotational"**. String: `"50% 50%"` (default) or `"20% 60%, 35% 90%"` for different start/end origins. |
|
||||
| **precision** | Decimal places for output path data; default `2`. |
|
||||
| **precompile** | Array of precomputed path strings (or use **precompile: "log"** once, copy from console). Skips expensive startup calculations; use for very complex morphs. Only for `<path>` (convert polygon/polyline first). |
|
||||
| **render** | Function(rawPath, target) called each update — e.g. draw to canvas. RawPath is an array of segments (each segment = array of alternating x,y cubic bezier coords). |
|
||||
| **updateTarget** | When using **render** (e.g. canvas-only), set **updateTarget: false** so the original `<path>` is not updated. **MorphSVGPlugin.defaultUpdateTarget** sets default. |
|
||||
|
||||
**Utilities:** **MorphSVGPlugin.convertToPath(selector | element)** converts circle/rect/ellipse/line/polygon/polyline to `<path>` in the DOM. **MorphSVGPlugin.rawPathToString(rawPath)** and **stringToRawPath(d)** convert between path strings and raw arrays. The plugin stores the original `d` on the target (e.g. for tweening back: `morphSVG: "#originalId"` or the same element).
|
||||
|
||||
**Tips:** For twisted or inverted morphs, set **shapeIndex** (use `"log"` or findShapeIndex()). For multi-segment paths, **shapeIndex** is an array (one value per segment). Precompile only when the first frame is slow; it does not fix jank during the tween (simplify the SVG or reduce size if needed).
|
||||
|
||||
**Learn more:** [MorphSVG](https://gsap.com/docs/v3/Plugins/MorphSVGPlugin)
|
||||
|
||||
### MotionPath (MotionPathPlugin)
|
||||
|
||||
@@ -170,10 +304,27 @@ gsap.to(".dot", {
|
||||
});
|
||||
```
|
||||
|
||||
**MotionPath — key config (motionPath object):**
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `path` | SVG path element, selector, or path data string |
|
||||
| `align` | Path element or selector to align the target to |
|
||||
| `alignOrigin` | `[x, y]` origin (0–1); default `[0.5, 0.5]` |
|
||||
| `autoRotate` | Rotate element to follow path tangent |
|
||||
| `curviness` | 0–2; path smoothing |
|
||||
|
||||
### MotionPathHelper
|
||||
|
||||
Visual editor for MotionPath (alignment, offset). Use during development to tune path alignment.
|
||||
|
||||
```javascript
|
||||
gsap.registerPlugin(MotionPathPlugin, MotionPathHelperPlugin);
|
||||
|
||||
const helper = MotionPathHelper.create(".dot", "#path", { end: 0.5 });
|
||||
// adjust in UI, then use helper.path or helper.getProgress() in your animation
|
||||
```
|
||||
|
||||
## Easing
|
||||
|
||||
### CustomEase
|
||||
@@ -204,10 +355,35 @@ Bounce-style easing with configurable strength.
|
||||
|
||||
2D physics (velocity, angle, gravity). Use when animating with simple physics (e.g. projectiles, bouncing).
|
||||
|
||||
```javascript
|
||||
gsap.registerPlugin(Physics2DPlugin);
|
||||
|
||||
gsap.to(".ball", {
|
||||
duration: 2,
|
||||
physics2D: {
|
||||
velocity: 250,
|
||||
angle: 80,
|
||||
gravity: 500
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### PhysicsProps (PhysicsPropsPlugin)
|
||||
|
||||
Applies physics to property values. Use for physics-driven property animation.
|
||||
|
||||
```javascript
|
||||
gsap.registerPlugin(PhysicsPropsPlugin);
|
||||
|
||||
gsap.to(".obj", {
|
||||
duration: 2,
|
||||
physicsProps: {
|
||||
x: { velocity: 100, end: 300 },
|
||||
y: { velocity: -50, acceleration: 200 }
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
### GSDevTools
|
||||
@@ -225,10 +401,23 @@ GSDevTools.create({ animation: tl });
|
||||
|
||||
Integrates GSAP with PixiJS for animating Pixi display objects. Register when animating Pixi objects with GSAP.
|
||||
|
||||
```javascript
|
||||
gsap.registerPlugin(PixiPlugin);
|
||||
|
||||
const sprite = new PIXI.Sprite(texture);
|
||||
gsap.to(sprite, { pixi: { x: 200, y: 100, scale: 1.5 }, duration: 1 });
|
||||
```
|
||||
|
||||
## Best practices
|
||||
|
||||
- ✅ Register every plugin used with **gsap.registerPlugin()** before first use.
|
||||
- ✅ Use **Flip.getState()** → DOM change → **Flip.from()** for layout transitions; use **Draggable** + **InertiaPlugin** for drag with momentum.
|
||||
- ✅ Revert plugin instances (e.g. `SplitTextInstance.revert()`) when components unmount or elements are removed.
|
||||
|
||||
## Do Not
|
||||
|
||||
- Use a plugin in a tween or API without registering it first (**gsap.registerPlugin()**).
|
||||
- Ship GSDevTools or development-only plugins to production.
|
||||
- ❌ Use a plugin in a tween or API without registering it first (**gsap.registerPlugin()**).
|
||||
- ❌ Ship GSDevTools or development-only plugins to production.
|
||||
|
||||
### Learn More
|
||||
|
||||
|
||||
@@ -37,9 +37,9 @@ useGSAP(() => {
|
||||
}, { scope: containerRef });
|
||||
```
|
||||
|
||||
- Pass a **scope** (ref or element) so selectors like `.box` are scoped to that root.
|
||||
- Cleanup (reverting animations and ScrollTriggers) runs automatically on unmount.
|
||||
- Use **contextSafe** from the hook's return value to wrap callbacks (e.g. onComplete) so they no-op after unmount and avoid React warnings.
|
||||
- ✅ Pass a **scope** (ref or element) so selectors like `.box` are scoped to that root.
|
||||
- ✅ Cleanup (reverting animations and ScrollTriggers) runs automatically on unmount.
|
||||
- ✅ Use **contextSafe** from the hook's return value to wrap callbacks (e.g. onComplete) so they no-op after unmount and avoid React warnings.
|
||||
|
||||
## Refs for Targets
|
||||
|
||||
@@ -73,8 +73,8 @@ useEffect(() => {
|
||||
}, []);
|
||||
```
|
||||
|
||||
- Pass a **scope** (ref or element) as the second argument so selectors are scoped to that node.
|
||||
- **Always** return a cleanup that calls **ctx.revert()**.
|
||||
- ✅ Pass a **scope** (ref or element) as the second argument so selectors are scoped to that node.
|
||||
- ✅ **Always** return a cleanup that calls **ctx.revert()**.
|
||||
|
||||
## Context-Safe Callbacks
|
||||
|
||||
@@ -116,11 +116,17 @@ GSAP runs in the browser. Do not call gsap or ScrollTrigger during SSR.
|
||||
- Use **useGSAP** (or useEffect) so all GSAP code runs only on the client.
|
||||
- If GSAP is imported at top level, ensure the app does not execute gsap.* or ScrollTrigger.* during server render. Dynamic import inside useEffect is an option if tree-shaking or bundle size is a concern.
|
||||
|
||||
## Best practices
|
||||
|
||||
- ✅ Use **useGSAP()** when @gsap/react is available; use **gsap.context()** + **ctx.revert()** in useEffect when it is not.
|
||||
- ✅ Use refs for targets and pass a **scope** so selectors are limited to the component.
|
||||
- ✅ Run GSAP only on the client (useGSAP or useEffect); do not call gsap or ScrollTrigger during SSR.
|
||||
|
||||
## Do Not
|
||||
|
||||
- Animate using selector strings that can match elements outside the current component unless a `scope` is defined in useGSAP or gsap.context() so only elements inside the component are affected.
|
||||
- Skip cleanup; always revert context or revert tweens/ScrollTriggers in the effect return to avoid leaks and updates on unmounted nodes.
|
||||
- Run GSAP or ScrollTrigger during SSR; keep all usage inside client-only lifecycle (e.g. useGSAP).
|
||||
- ❌ Animate using selector strings that can match elements outside the current component unless a `scope` is defined in useGSAP or gsap.context() so only elements inside the component are affected.
|
||||
- ❌ Skip cleanup; always revert context or kill tweens/ScrollTriggers in the effect return to avoid leaks and updates on unmounted nodes.
|
||||
- ❌ Run GSAP or ScrollTrigger during SSR; keep all usage inside client-only lifecycle (e.g. useGSAP).
|
||||
|
||||
|
||||
### Learn More
|
||||
|
||||
@@ -35,7 +35,116 @@ gsap.to(".box", {
|
||||
});
|
||||
```
|
||||
|
||||
**start** / **end**: viewport position vs. trigger position. Format `"triggerPosition viewportPosition"`. Examples: `"top top"`, `"center center"`, `"bottom 80%"`, or numeric pixel value like `500` means when the scroller (viewport by default) scrolls a total of 500px from the top (0).
|
||||
**start** / **end**: viewport position vs. trigger position. Format `"triggerPosition viewportPosition"`. Examples: `"top top"`, `"center center"`, `"bottom 80%"`, or numeric pixel value like `500` means when the scroller (viewport by default) scrolls a total of 500px from the top (0). Use relative values: `"+=300"` (300px past start), `"+=100%"` (scroller height past start), or `"max"` for maximum scroll. Wrap in **clamp()** (v3.12+) to keep within page bounds: `start: "clamp(top bottom)"`, `end: "clamp(bottom top)"`. Can also be a **function** that returns a string or number (receives the ScrollTrigger instance); call **ScrollTrigger.refresh()** when layout changes.
|
||||
|
||||
## Key config options
|
||||
|
||||
Main properties for the `scrollTrigger` config object (shorthand: `scrollTrigger: ".selector"` sets only `trigger`). See [ScrollTrigger docs](https://gsap.com/docs/v3/Plugins/ScrollTrigger/) for the full list.
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| **trigger** | String \| Element | Element whose position defines where the ScrollTrigger starts. Required (or use shorthand). |
|
||||
| **start** | String \| Number \| Function | When the trigger becomes active. Default `"top bottom"` (or `"top top"` if `pin: true`). |
|
||||
| **end** | String \| Number \| Function | When the trigger ends. Default `"bottom top"`. Use `endTrigger` if end is based on a different element. |
|
||||
| **endTrigger** | String \| Element | Element used for **end** when different from trigger. |
|
||||
| **scrub** | Boolean \| Number | Link animation progress to scroll. `true` = direct; number = seconds for playhead to "catch up". |
|
||||
| **toggleActions** | String | Four actions in order: **onEnter**, **onLeave**, **onEnterBack**, **onLeaveBack**. Each: `"play"`, `"pause"`, `"resume"`, `"reset"`, `"restart"`, `"complete"`, `"reverse"`, `"none"`. Default `"play none none none"`. |
|
||||
| **pin** | Boolean \| String \| Element | Pin an element while active. `true` = pin the trigger. Don't animate the pinned element itself; animate children. |
|
||||
| **pinSpacing** | Boolean \| String | Default `true` (adds spacer so layout doesn't collapse). `false` or `"margin"`. |
|
||||
| **horizontal** | Boolean | `true` for horizontal scrolling. |
|
||||
| **scroller** | String \| Element | Scroll container (default: viewport). Use selector or element for a scrollable div. |
|
||||
| **markers** | Boolean \| Object | `true` for dev markers; or `{ startColor, endColor, fontSize, ... }`. Remove in production. |
|
||||
| **once** | Boolean | If `true`, kills the ScrollTrigger after end is reached once (animation keeps running). |
|
||||
| **id** | String | Unique id for **ScrollTrigger.getById(id)**. |
|
||||
| **refreshPriority** | Number | Lower = refreshed first. Use when creating ScrollTriggers in non–top-to-bottom order: set so triggers refresh in page order (first on page = lower number). |
|
||||
| **toggleClass** | String \| Object | Add/remove class when active. String = on trigger; or `{ targets: ".x", className: "active" }`. |
|
||||
| **snap** | Number \| Array \| Function \| "labels" \| Object | Snap to progress values. Number = increments (e.g. `0.25`); array = specific values; `"labels"` = timeline labels; object: `{ snapTo: 0.25, duration: 0.3, delay: 0.1, ease: "power1.inOut" }`. |
|
||||
| **containerAnimation** | Tween \| Timeline | For "fake" horizontal scroll: the timeline/tween that moves content horizontally. ScrollTrigger ties vertical scroll to this animation's progress. See **Horizontal scroll (containerAnimation)** below. Pinning and snapping are not available on containerAnimation-based ScrollTriggers. |
|
||||
| **onEnter**, **onLeave**, **onEnterBack**, **onLeaveBack** | Function | Callbacks when crossing start/end; receive the ScrollTrigger instance (`progress`, `direction`, `isActive`, `getVelocity()`). |
|
||||
| **onUpdate**, **onToggle**, **onRefresh**, **onScrubComplete** | Function | **onUpdate** fires when progress changes; **onToggle** when active flips; **onRefresh** after recalc; **onScrubComplete** when numeric scrub finishes. |
|
||||
|
||||
**Standalone ScrollTrigger** (no linked tween): use **ScrollTrigger.create()** with the same config and use callbacks for custom behavior (e.g. update UI from `self.progress`).
|
||||
|
||||
```javascript
|
||||
ScrollTrigger.create({
|
||||
trigger: "#id",
|
||||
start: "top top",
|
||||
end: "bottom 50%+=100px",
|
||||
onUpdate: (self) => console.log(self.progress.toFixed(3), self.direction)
|
||||
});
|
||||
```
|
||||
|
||||
## ScrollTrigger.batch()
|
||||
|
||||
**ScrollTrigger.batch(triggers, vars)** creates one ScrollTrigger per target and **batches** their callbacks (onEnter, onLeave, etc.) within a short interval. Use it to coordinate an animation (e.g. with staggers) for all elements that fire a similar callback around the same time — e.g. animate every element that just entered the viewport in one go. Good alternative to IntersectionObserver. Returns an Array of ScrollTrigger instances.
|
||||
|
||||
- **triggers**: selector text (e.g. `".box"`) or Array of elements.
|
||||
- **vars**: standard ScrollTrigger config (start, end, once, callbacks, etc.). Do **not** pass `trigger` (targets are the triggers) or animation-related options: `animation`, `invalidateOnRefresh`, `onSnapComplete`, `onScrubComplete`, `scrub`, `snap`, `toggleActions`.
|
||||
|
||||
**Callback signature:** Batched callbacks receive **two** parameters (unlike normal ScrollTrigger callbacks, which receive the instance):
|
||||
1. **targets** — Array of trigger elements that fired this callback within the interval.
|
||||
2. **scrollTriggers** — Array of the ScrollTrigger instances that fired. Use for progress, direction, or `kill()`.
|
||||
|
||||
**Batch options in vars:**
|
||||
- **interval** (Number) — Max time in seconds to collect each batch. Default is roughly one requestAnimationFrame. When the first callback of a type fires, the timer starts; the batch is delivered when the interval elapses or when **batchMax** is reached.
|
||||
- **batchMax** (Number | Function) — Max elements per batch. When full, the callback fires and the next batch starts. Use a **function** that returns a number for responsive layouts; it runs on refresh (resize, tab focus, etc.).
|
||||
|
||||
```javascript
|
||||
ScrollTrigger.batch(".box", {
|
||||
onEnter: (elements, triggers) => {
|
||||
gsap.to(elements, { opacity: 1, y: 0, stagger: 0.15 });
|
||||
},
|
||||
onLeave: (elements, triggers) => {
|
||||
gsap.to(elements, { opacity: 0, y: 100 });
|
||||
},
|
||||
start: "top 80%",
|
||||
end: "bottom 20%"
|
||||
});
|
||||
```
|
||||
|
||||
With **batchMax** and **interval** for finer control:
|
||||
|
||||
```javascript
|
||||
ScrollTrigger.batch(".card", {
|
||||
interval: 0.1,
|
||||
batchMax: 4,
|
||||
onEnter: (batch) => gsap.to(batch, { opacity: 1, y: 0, stagger: 0.1, overwrite: true }),
|
||||
onLeaveBack: (batch) => gsap.set(batch, { opacity: 0, y: 50, overwrite: true })
|
||||
});
|
||||
```
|
||||
|
||||
See [ScrollTrigger.batch()](https://gsap.com/docs/v3/Plugins/ScrollTrigger/static.batch/) in the GSAP docs.
|
||||
|
||||
## ScrollTrigger.scrollerProxy()
|
||||
|
||||
**ScrollTrigger.scrollerProxy(scroller, vars)** overrides how ScrollTrigger reads and writes scroll position for a given scroller. Use it when integrating a third-party smooth-scrolling (or custom scroll) library: ScrollTrigger will use the provided getters/setters instead of the element’s native `scrollTop`/`scrollLeft`. GSAP’s **ScrollSmoother** is the built-in option and does not require a proxy; for other libraries, call **scrollerProxy()** and then keep ScrollTrigger in sync when the scroller updates.
|
||||
|
||||
- **scroller**: selector or element (e.g. `"body"`, `".container"`).
|
||||
- **vars**: object with **scrollTop** and/or **scrollLeft** functions. Each acts as getter and setter: when called **with** an argument, it is a setter; when called **with no** argument, it returns the current value (getter). At least one of **scrollTop** or **scrollLeft** is required.
|
||||
|
||||
**Optional in vars:**
|
||||
- **getBoundingClientRect** — Function returning `{ top, left, width, height }` for the scroller (often `{ top: 0, left: 0, width: window.innerWidth, height: window.innerHeight }` for the viewport). Needed when the scroller’s real rect is not the default.
|
||||
- **scrollWidth** / **scrollHeight** — Getter/setter functions (same pattern: argument = setter, no argument = getter) when the library exposes different dimensions.
|
||||
- **fixedMarkers** (Boolean) — When `true`, markers are treated as `position: fixed`. Useful when the scroller is translated (e.g. by a smooth-scroll lib) and markers move incorrectly.
|
||||
- **pinType** — `"fixed"` or `"transform"`. Controls how pinning is applied for this scroller. Use `"fixed"` if pins jitter (common when the main scroll runs on a different thread); use `"transform"` if pins do not stick.
|
||||
|
||||
**Critical:** When the third-party scroller updates its position, ScrollTrigger must be notified. Register **ScrollTrigger.update** as a listener (e.g. `smoothScroller.addListener(ScrollTrigger.update)`). Without this, ScrollTrigger’s calculations will be out of date.
|
||||
|
||||
```javascript
|
||||
// Example: proxy body scroll to a third-party scroll instance
|
||||
ScrollTrigger.scrollerProxy(document.body, {
|
||||
scrollTop(value) {
|
||||
if (arguments.length) scrollbar.scrollTop = value;
|
||||
return scrollbar.scrollTop;
|
||||
},
|
||||
getBoundingClientRect() {
|
||||
return { top: 0, left: 0, width: window.innerWidth, height: window.innerHeight };
|
||||
}
|
||||
});
|
||||
scrollbar.addListener(ScrollTrigger.update);
|
||||
```
|
||||
|
||||
See [ScrollTrigger.scrollerProxy()](https://gsap.com/docs/v3/Plugins/ScrollTrigger/static.scrollerProxy/) in the GSAP docs.
|
||||
|
||||
## Scrub
|
||||
|
||||
@@ -106,24 +215,78 @@ tl.to(".a", { x: 100 }).to(".b", { y: 50 }).to(".c", { opacity: 0 });
|
||||
|
||||
The timeline’s progress is tied to scroll through the trigger’s start/end range.
|
||||
|
||||
## Horizontal scroll (containerAnimation)
|
||||
|
||||
A common pattern: **pin** a section, then as the user scrolls **vertically**, content inside moves **horizontally** (“fake” horizontal scroll). Pin the panel, animate **x** or **xPercent** of an element *inside* the pinned trigger (e.g. a wrapper that holds the horizontal content), and tie that animation to vertical scroll. Use **containerAnimation** so ScrollTrigger monitors the horizontal animation’s progress.
|
||||
|
||||
**Critical:** The horizontal tween/timeline **must** use **ease: "none"**. Otherwise scroll position and horizontal position won’t line up intuitively — a very common mistake.
|
||||
|
||||
1. Pin the section (trigger = the full-viewport panel).
|
||||
2. Build a tween that animates the inner content’s **x** or **xPercent** (e.g. to `x: () => (targets.length - 1) * -window.innerWidth` or a negative `xPercent` to move left). Use **ease: "none"** on that tween.
|
||||
3. Attach ScrollTrigger to that tween with **pin: true**, **scrub: true**
|
||||
4. To trigger things based on the horizontal movement caused by that tween, set **containerAnimation** to that tween.
|
||||
|
||||
```javascript
|
||||
const scrollingEl = document.querySelector(".horizontal-el");
|
||||
// Panel = pinned viewport-sized section. .horizontal-wrap = inner content that moves left.
|
||||
const scrollTween = gsap.to(scrollingEl, {
|
||||
xPercent: () => Max.max(0, window.innerWidth - scrollingEl.offsetWidth),
|
||||
ease: "none", // ease: "none" is required
|
||||
scrollTrigger: {
|
||||
trigger: scrollingEl,
|
||||
pin: scrollingEl.parentNode, // wrapper so that we're not animating the pinned element
|
||||
start: "top top",
|
||||
end: "+=1000"
|
||||
}
|
||||
});
|
||||
|
||||
// other tweens that trigger based on horizontal movement should reference the containerAnimation:
|
||||
gsap.to(".nested-el-1", {
|
||||
y: 100,
|
||||
scrollTrigger: {
|
||||
containerAnimation: scrollTween, // IMPORTANT
|
||||
trigger: ".nested-wrapper-1",
|
||||
start: "left center", // based on horizontal movement
|
||||
toggleActions: "play none none reset"
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
**Caveats:** Pinning and snapping are not available on ScrollTriggers that use **containerAnimation**. The container animation must use **ease: "none"**. Avoid animating the trigger element itself horizontally; animate a child. If the trigger is moved, **start**/**end** must be offset accordingly.
|
||||
|
||||
## Refresh and Cleanup
|
||||
|
||||
- **ScrollTrigger.refresh()** — recalculate positions (e.g. after DOM/layout changes, fonts loaded, or dynamic content).
|
||||
- **ScrollTrigger.refresh()** — recalculate positions (e.g. after DOM/layout changes, fonts loaded, or dynamic content). Automatically called on viewport resize, debounced 200ms. Refresh runs in creation order (or by **refreshPriority**); create ScrollTriggers top-to-bottom on the page or set **refreshPriority** so they refresh in that order.
|
||||
- When removing animated elements or changing pages (e.g. in SPAs), **kill** associated ScrollTrigger instances so they don’t run on stale elements:
|
||||
|
||||
```javascript
|
||||
ScrollTrigger.getAll().forEach(t => t.kill());
|
||||
// or kill by trigger element
|
||||
ScrollTrigger.getById("myTrigger")?.kill();
|
||||
// or kill by the id assigned to the ScrollTrigger in its config object like {id: "my-id", ...}
|
||||
ScrollTrigger.getById("my-id")?.kill();
|
||||
```
|
||||
|
||||
In React, kill in a cleanup (e.g. in useEffect return) when the component unmounts.
|
||||
In React, use the `useGSAP()` hook (@gsap/react NPM package) to ensure proper cleanup automatically, or manually kill in a cleanup (e.g. in useEffect return) when the component unmounts.
|
||||
|
||||
## Best practices
|
||||
|
||||
- ✅ **gsap.registerPlugin(ScrollTrigger)** once before any ScrollTrigger usage.
|
||||
- ✅ Call **ScrollTrigger.refresh()** after DOM/layout changes (new content, images, fonts) that affect trigger positions. Whenever the viewport is resized, `ScrollTrigger.refresh()` is automatically called (debounced 200ms)
|
||||
- ✅ In React, use the `useGSAP()` hook to ensure that all ScrollTriggers and GSAP animations are reverted and cleaned up when necessary, or use a `gsap.context()` to do it manually in a useEffect/useLayoutEffect cleanup function.
|
||||
- ✅ Use **scrub** for scroll-linked progress or **toggleActions** for discrete play/reverse; do not use both on the same trigger.
|
||||
- ✅ For fake horizontal scroll with **containerAnimation**, use **ease: "none"** on the horizontal tween/timeline so scroll and horizontal position stay in sync.
|
||||
- ✅ Create ScrollTriggers in the order they appear on the page (top to bottom, scroll 0 → max). When they are created in a different order (e.g. dynamic or async), set **refreshPriority** on each so they are refreshed in that same top-to-bottom order (first section on page = lower number).
|
||||
|
||||
## Do Not
|
||||
|
||||
- Nest ScrollTriggered animations inside of a parent timeline. ScrollTriggers should only exist on top-level animations. Wrong: `gsap.timeline().to(..., {scrollTrigger: {...}})`. Correct: `gsap.timeline({scrollTrigger: {...}}).to(...)` (the tween inside the timeline shouldn't have a ScrollTrigger).
|
||||
- Forget to **gsap.registerPlugin(ScrollTrigger)** before using ScrollTrigger.
|
||||
- Use **scrub** and **toggleActions** together on the same ScrollTrigger; choose one behavior. If both exist, scrub wins.
|
||||
- Leave **markers: true** in production.
|
||||
- Omit **refresh()** after layout changes (new content, images, fonts) that affect trigger positions. ScrollTrigger will automatically call ScrollTrigger.refresh() when the viewport resizes.
|
||||
- ❌ Nest ScrollTriggered animations inside of a parent timeline. ScrollTriggers should only exist on top-level animations. Wrong: `gsap.timeline().to(..., {scrollTrigger: {...}})`. Correct: `gsap.timeline({scrollTrigger: {...}}).to(...)` (the tween inside the timeline shouldn't have a ScrollTrigger).
|
||||
- ❌ Forget to **gsap.registerPlugin(ScrollTrigger)** before using ScrollTrigger.
|
||||
- ❌ Use **scrub** and **toggleActions** together on the same ScrollTrigger; choose one behavior. If both exist, **scrub** wins.
|
||||
- ❌ Use an ease other than **"none"** on the horizontal animation when using **containerAnimation** for fake horizontal scroll; it breaks the 1:1 scroll-to-position mapping.
|
||||
- ❌ Create ScrollTriggers in random or async order without setting **refreshPriority**; refresh runs in creation order (or by refreshPriority), and wrong order can affect layout (e.g. pin spacing). Create them top-to-bottom or assign **refreshPriority** so they refresh in page order.
|
||||
- ❌ Leave **markers: true** in production.
|
||||
- ❌ Forget **refresh()** after layout changes (new content, images, fonts) that affect trigger positions; viewport resize is handled automatically.
|
||||
|
||||
### Learn More
|
||||
|
||||
https://gsap.com/docs/v3/Plugins/ScrollTrigger/
|
||||
|
||||
|
||||
@@ -89,7 +89,14 @@ master.to(".c", { opacity: 0 }, "+=0.2");
|
||||
- **tl.progress(0.5)** — seek to 50%.
|
||||
- **tl.kill()** — kill timeline and (by default) its children.
|
||||
|
||||
## Best practices
|
||||
|
||||
- ✅ Use the **position parameter** (third argument) to place tweens at specific times or relative to labels.
|
||||
- ✅ Add **labels** with `addLabel()` for readable, maintainable sequencing.
|
||||
- ✅ Pass **defaults** into the timeline constructor so child tweens inherit duration, ease, etc.
|
||||
- ✅ Put ScrollTrigger on the timeline (or top-level tween), not on tweens inside a timeline.
|
||||
|
||||
## Do Not
|
||||
|
||||
- Forget that **duration** on the timeline constructor is not the same as tween duration; timeline “duration” is determined by its children.
|
||||
- Nest animations that contain a ScrollTrigger; ScrollTriggers should only be on top-level Tweens/Timelines.
|
||||
- ❌ Forget that **duration** on the timeline constructor is not the same as tween duration; timeline “duration” is determined by its children.
|
||||
- ❌ Nest animations that contain a ScrollTrigger; ScrollTriggers should only be on top-level Tweens/Timelines.
|
||||
|
||||
+125
-33
@@ -14,65 +14,109 @@ Apply when writing or reviewing code that uses **gsap.utils** for math, array/co
|
||||
|
||||
**gsap.utils** provides pure helpers; no need to register. Use in tween vars (e.g. function-based values), in ScrollTrigger or Observer callbacks, or in any JS that drives GSAP. All are on **gsap.utils** (e.g. `gsap.utils.clamp()`).
|
||||
|
||||
**Omitting the value: function form.** Many utils accept the value to transform as the **last** argument. If you omit that argument, the util returns a **function** that accepts the value later. Use the function form when you need to clamp, map, normalize, or snap many values with the same config (e.g. in a mousemove handler or tween callback). **Exception: random()** — pass **true** as the last argument to get a reusable function (do not omit the value); see [random()](https://gsap.com/docs/v3/GSAP/UtilityMethods/random()).
|
||||
|
||||
```javascript
|
||||
// With value: returns the result
|
||||
gsap.utils.clamp(0, 100, 150); // 100
|
||||
|
||||
// Without value: returns a function you call with the value later
|
||||
let c = gsap.utils.clamp(0, 100);
|
||||
c(150); // 100
|
||||
c(-10); // 0
|
||||
```
|
||||
|
||||
## Clamping and Ranges
|
||||
|
||||
### clamp(min, max, value)
|
||||
### clamp(min, max, value?)
|
||||
|
||||
Constrains a value between min and max.
|
||||
Constrains a value between min and max. Omit **value** to get a function: `clamp(min, max)(value)`.
|
||||
|
||||
```javascript
|
||||
gsap.utils.clamp(0, 100, 150); // 100
|
||||
gsap.utils.clamp(0, 100, -10); // 0
|
||||
|
||||
let clampFn = gsap.utils.clamp(0, 100);
|
||||
clampFn(150); // 100
|
||||
```
|
||||
|
||||
### mapRange(inMin, inMax, outMin, outMax, value)
|
||||
### mapRange(inMin, inMax, outMin, outMax, value?)
|
||||
|
||||
Maps a value from one range to another. Use when converting scroll position, progress (0–1), or input range to an animation range.
|
||||
Maps a value from one range to another. Use when converting scroll position, progress (0–1), or input range to an animation range. Omit **value** to get a function: `mapRange(inMin, inMax, outMin, outMax)(value)`.
|
||||
|
||||
```javascript
|
||||
gsap.utils.mapRange(0, 100, 0, 500, 50); // 250
|
||||
gsap.utils.mapRange(0, 1, 0, 360, 0.5); // 180 (progress to degrees)
|
||||
gsap.utils.mapRange(0, 1, 0, 360, 0.5); // 180 (progress to degrees)
|
||||
|
||||
let mapFn = gsap.utils.mapRange(0, 100, 0, 500);
|
||||
mapFn(50); // 250
|
||||
```
|
||||
|
||||
### normalize(min, max, value)
|
||||
### normalize(min, max, value?)
|
||||
|
||||
Returns a value normalized to 0–1 for the given range. Inverse of mapping when the target range is 0–1.
|
||||
Returns a value normalized to 0–1 for the given range. Inverse of mapping when the target range is 0–1. Omit **value** to get a function: `normalize(min, max)(value)`.
|
||||
|
||||
```javascript
|
||||
gsap.utils.normalize(0, 100, 50); // 0.5
|
||||
gsap.utils.normalize(0, 100, 50); // 0.5
|
||||
gsap.utils.normalize(100, 300, 200); // 0.5
|
||||
|
||||
let normFn = gsap.utils.normalize(0, 100);
|
||||
normFn(50); // 0.5
|
||||
```
|
||||
|
||||
### interpolate(start, end, progress)
|
||||
### interpolate(start, end, progress?)
|
||||
|
||||
Interpolates between two values at a given progress (0–1). Handles numbers, colors, and objects with matching keys.
|
||||
Interpolates between two values at a given progress (0–1). Handles numbers, colors, and objects with matching keys. Omit **progress** to get a function: `interpolate(start, end)(progress)`.
|
||||
|
||||
```javascript
|
||||
gsap.utils.interpolate(0, 100, 0.5); // 50
|
||||
gsap.utils.interpolate("#ff0000", "#0000ff", 0.5); // mid color
|
||||
gsap.utils.interpolate({ x: 0, y: 0 }, { x: 100, y: 50 }, 0.5); // { x: 50, y: 25 }
|
||||
|
||||
let lerp = gsap.utils.interpolate(0, 100);
|
||||
lerp(0.5); // 50
|
||||
```
|
||||
|
||||
## Random and Snap
|
||||
|
||||
### random(min, max, increment?)
|
||||
### random(minimum, maximum[, snapIncrement, returnFunction]) / random(array[, returnFunction])
|
||||
|
||||
Returns a random number between min and max. Optional **increment** snaps to steps (e.g. `increment: 1` for integers).
|
||||
Returns a random number in the range **minimum**–**maximum**, or a random element from an **array**. Optional **snapIncrement** snaps the result to the nearest multiple (e.g. `5` → multiples of 5). **To get a reusable function**, pass **true** as the last argument (**returnFunction**); the returned function takes no args and returns a new random value each time. This is the only util that uses `true` for the function form instead of omitting the value.
|
||||
|
||||
```javascript
|
||||
gsap.utils.random(10, 20); // e.g. 14.3
|
||||
gsap.utils.random(1, 10, 1); // integer 1–10
|
||||
gsap.utils.random(-100, 100, 10); // -100, -90, ..., 100
|
||||
// immediate value: number in range
|
||||
gsap.utils.random(-100, 100); // e.g. 42.7
|
||||
gsap.utils.random(0, 500, 5); // 0–500, snapped to nearest 5
|
||||
|
||||
// reusable function: pass true as last argument
|
||||
let randomFn = gsap.utils.random(-200, 500, 10, true);
|
||||
randomFn(); // random value in range, snapped to 10
|
||||
randomFn(); // another random value
|
||||
|
||||
// array: pick one value at random
|
||||
gsap.utils.random(["red", "blue", "green"]); // "red", "blue", or "green"
|
||||
let randomFromArray = gsap.utils.random([0, 100, 200], true);
|
||||
randomFromArray(); // 0, 100, or 200
|
||||
```
|
||||
|
||||
### snap(snapTo, value)
|
||||
**String form in tween vars:** use `"random(-100, 100)"`, `"random(-100, 100, 5)"`, or `"random([0, 100, 200])"`; GSAP evaluates it per target.
|
||||
|
||||
Snaps a value to the nearest multiple of **snapTo**, or to the nearest value in an array of allowed values.
|
||||
```javascript
|
||||
gsap.to(".box", { x: "random(-100, 100, 5)", duration: 1 });
|
||||
gsap.to(".item", { backgroundColor: "random([red, blue, green])" });
|
||||
```
|
||||
|
||||
### snap(snapTo, value?)
|
||||
|
||||
Snaps a value to the nearest multiple of **snapTo**, or to the nearest value in an array of allowed values. Omit **value** to get a function: `snap(snapTo)(value)` (or `snap(snapArray)(value)`).
|
||||
|
||||
```javascript
|
||||
gsap.utils.snap(10, 23); // 20
|
||||
gsap.utils.snap(0.25, 0.7); // 0.75
|
||||
gsap.utils.snap([0, 100, 200], 150); // 100 or 200 (nearest in array)
|
||||
|
||||
let snapFn = gsap.utils.snap(10);
|
||||
snapFn(23); // 20
|
||||
```
|
||||
|
||||
Use in tweens for grid or step-based animation:
|
||||
@@ -89,16 +133,50 @@ Returns a new array with the same elements in random order. Use for randomizing
|
||||
gsap.utils.shuffle([1, 2, 3, 4]); // e.g. [3, 1, 4, 2]
|
||||
```
|
||||
|
||||
### distribute(baseObject)
|
||||
### distribute(config)
|
||||
|
||||
Distributes values across a range (e.g. for positioning many elements). Returns a function that, given an index and total, returns a value in the range. See GSAP docs for **base**, **amount**, **from**, **grid**, etc.
|
||||
**Returns a function** that assigns a value to each target based on its position in the array (or in a grid). Used internally for advanced staggers; use it whenever you need values spread across many elements (e.g. scale, opacity, x, delay). The returned function receives `(index, target, targets)` — either call it manually or pass the result directly into a tween; GSAP will call it per target with index, element, and array.
|
||||
|
||||
**Config (all optional):**
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `base` | Number | Starting value. Default `0`. |
|
||||
| `amount` | Number | Total to distribute across all targets (added to base). E.g. `amount: 1` with 100 targets → 0.01 between each. Use **each** instead to set a fixed step per target. |
|
||||
| `each` | Number | Amount to add between each target (added to base). E.g. `each: 1` with 4 targets → 0, 1, 2, 3. Use **amount** instead to split a total. |
|
||||
| `from` | Number \| String \| Array | Where distribution starts: index, or `"start"`, `"center"`, `"edges"`, `"random"`, `"end"`, or ratios like `[0.25, 0.75]`. Default `0`. |
|
||||
| `grid` | String \| Array | Use grid position instead of flat index: `[rows, columns]` (e.g. `[5, 10]`) or `"auto"` to detect. Omit for flat array. |
|
||||
| `axis` | String | For grid: limit to one axis (`"x"` or `"y"`). |
|
||||
| `ease` | Ease | Distribute values along an ease curve (e.g. `"power1.inOut"`). Default `"none"`. |
|
||||
|
||||
**In a tween:** pass the result of `distribute(config)` as the property value; GSAP calls the function for each target with `(index, target, targets)`.
|
||||
|
||||
```javascript
|
||||
const dist = gsap.utils.distribute({ base: 0, amount: 100, from: "center" });
|
||||
dist(0, 5); // value for index 0 of 5
|
||||
dist(2, 5); // value for index 2 of 5
|
||||
// Scale: middle elements 0.5, outer edges 3 (amount 2.5 distributed from center)
|
||||
gsap.to(".class", {
|
||||
scale: gsap.utils.distribute({
|
||||
base: 0.5,
|
||||
amount: 2.5,
|
||||
from: "center"
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
**Manual use:** call the returned function with `(index, target, targets)` to get the value for that index.
|
||||
|
||||
```javascript
|
||||
const distributor = gsap.utils.distribute({
|
||||
base: 50,
|
||||
amount: 100,
|
||||
from: "center",
|
||||
ease: "power1.inOut"
|
||||
});
|
||||
const targets = gsap.utils.toArray(".box");
|
||||
const valueForIndex2 = distributor(2, targets[2], targets);
|
||||
```
|
||||
|
||||
See [distribute()](https://gsap.com/docs/v3/GSAP/UtilityMethods/distribute/) for more.
|
||||
|
||||
## Units and Parsing
|
||||
|
||||
### getUnit(value)
|
||||
@@ -120,12 +198,15 @@ gsap.utils.unitize(100, "px"); // "100px"
|
||||
gsap.utils.unitize("2rem", "px"); // "2rem" (unchanged)
|
||||
```
|
||||
|
||||
### splitColor(color)
|
||||
### splitColor(color, returnHSL?)
|
||||
|
||||
Splits a color string into RGB components (0–255) or an array. Use when animating color components or building gradients.
|
||||
Converts a color string into an array: **[red, green, blue]** (0–255), or **[red, green, blue, alpha]** (4 elements for RGBA when alpha is present or required). Pass **true** as the second argument (**returnHSL**) to get **[hue, saturation, lightness]** or **[hue, saturation, lightness, alpha]** (HSL/HSLA) instead. Works with `"rgb()"`, `"rgba()"`, `"hsl()"`, `"hsla()"`, hex, and named colors (e.g. `"red"`). Use when animating color components or building gradients. See [splitColor()](https://gsap.com/docs/v3/GSAP/UtilityMethods/splitColor/).
|
||||
|
||||
```javascript
|
||||
gsap.utils.splitColor("#ff0000"); // [255, 0, 0] or similar
|
||||
gsap.utils.splitColor("red"); // [255, 0, 0]
|
||||
gsap.utils.splitColor("#6fb936"); // [111, 185, 54]
|
||||
gsap.utils.splitColor("rgba(204, 153, 51, 0.5)"); // [204, 153, 51, 0.5] (4 elements)
|
||||
gsap.utils.splitColor("#6fb936", true); // [94, 55, 47] (HSL: hue, saturation, lightness)
|
||||
```
|
||||
|
||||
## Arrays and Collections
|
||||
@@ -162,28 +243,39 @@ const fn = gsap.utils.pipe(
|
||||
fn(50); // normalized then snapped
|
||||
```
|
||||
|
||||
### wrap(min, max, value)
|
||||
### wrap(min, max, value?)
|
||||
|
||||
Wraps a value into the range min–max (inclusive min, exclusive max). Use for infinite scroll or cyclic values.
|
||||
Wraps a value into the range min–max (inclusive min, exclusive max). Use for infinite scroll or cyclic values. Omit **value** to get a function: `wrap(min, max)(value)`.
|
||||
|
||||
```javascript
|
||||
gsap.utils.wrap(0, 360, 370); // 10
|
||||
gsap.utils.wrap(0, 360, -10); // 350
|
||||
gsap.utils.wrap(0, 360, -10); // 350
|
||||
|
||||
let wrapFn = gsap.utils.wrap(0, 360);
|
||||
wrapFn(370); // 10
|
||||
```
|
||||
|
||||
### wrapYoyo(min, max, value)
|
||||
### wrapYoyo(min, max, value?)
|
||||
|
||||
Wraps value in range with a yoyo (bounces at ends). Use for back-and-forth within a range.
|
||||
Wraps value in range with a yoyo (bounces at ends). Use for back-and-forth within a range. Omit **value** to get a function: `wrapYoyo(min, max)(value)`.
|
||||
|
||||
```javascript
|
||||
gsap.utils.wrapYoyo(0, 100, 150); // 50 (bounces back)
|
||||
|
||||
let wrapY = gsap.utils.wrapYoyo(0, 100);
|
||||
wrapY(150); // 50
|
||||
```
|
||||
|
||||
## Best practices
|
||||
|
||||
- ✅ Omit the value argument to get a reusable function when the same range/config is used many times (e.g. scroll handler, tween callback): `let mapFn = gsap.utils.mapRange(0, 1, 0, 360); mapFn(progress)`.
|
||||
- ✅ Use **snap** for grid-aligned or step-based values; use **toArray** when GSAP or your code needs a real array from a selector or NodeList.
|
||||
- ✅ Use **gsap.utils.selector(scope)** in components so selectors are scoped to a container or ref.
|
||||
|
||||
## Do Not
|
||||
|
||||
- Use **gsap.utils** for DOM queries when a simple selector or ref is enough; **toArray** is for when GSAP or your code needs a real array.
|
||||
- Assume **mapRange** / **normalize** handle units; they work on numbers. Use **getUnit** / **unitize** when units matter.
|
||||
- Override or rely on undocumented behavior; stick to the documented API.
|
||||
- ❌ Assume **mapRange** / **normalize** handle units; they work on numbers. Use **getUnit** / **unitize** when units matter.
|
||||
- ❌ Override or rely on undocumented behavior; stick to the documented API.
|
||||
|
||||
### Learn More
|
||||
|
||||
|
||||
Reference in New Issue
Block a user