pixi-effects 0.26.2 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/README.md +8 -10
- package/ai/CHAT.md +1 -1
- package/ai/chat-template.html +4 -6
- package/ai/tools/render.mjs +16 -11
- package/dist/{Base-CpdDs4HT.d.ts → Base-CVezIEdo.d.ts} +2 -4
- package/dist/{Base-TbBhHPrG.d.cts → Base-DzC3i919.d.cts} +2 -4
- package/dist/Composition-4TYDRRRY.js +7 -0
- package/dist/{Composition-H4QA24MV.js.map → Composition-4TYDRRRY.js.map} +1 -1
- package/dist/Composition-QCOWQSDB.cjs +16 -0
- package/dist/{Composition-HM4YT5L7.cjs.map → Composition-QCOWQSDB.cjs.map} +1 -1
- package/dist/Controller.d.cts +2 -3
- package/dist/Controller.d.ts +2 -3
- package/dist/{Movie-i05Oh9mJ.d.cts → Movie-CQ0Nz62a.d.cts} +12 -6
- package/dist/{Movie-DVeDTdnc.d.ts → Movie-M9dKm9dI.d.ts} +12 -6
- package/dist/Presenter.d.cts +2 -3
- package/dist/Presenter.d.ts +2 -3
- package/dist/{blendModes-YV43K6E7.cjs → blendModes-HDOP6R7T.cjs} +5 -5
- package/dist/{blendModes-YV43K6E7.cjs.map → blendModes-HDOP6R7T.cjs.map} +1 -1
- package/dist/{blendModes-G3NOAU3T.js → blendModes-PAVTW3BO.js} +4 -4
- package/dist/{blendModes-G3NOAU3T.js.map → blendModes-PAVTW3BO.js.map} +1 -1
- package/dist/{chunk-TLYTYBWX.js → chunk-45QKLD6M.js} +9 -11
- package/dist/chunk-45QKLD6M.js.map +1 -0
- package/dist/{chunk-GBUM7X2S.js → chunk-CKTS6G6I.js} +7 -11
- package/dist/chunk-CKTS6G6I.js.map +1 -0
- package/dist/{chunk-555URQHQ.cjs → chunk-NDR2VQFV.cjs} +91 -95
- package/dist/chunk-NDR2VQFV.cjs.map +1 -0
- package/dist/{chunk-3DLYAPK7.cjs → chunk-P4GY3Z4G.cjs} +5 -5
- package/dist/{chunk-3DLYAPK7.cjs.map → chunk-P4GY3Z4G.cjs.map} +1 -1
- package/dist/{chunk-WPXDRSGN.cjs → chunk-RQMI7EJT.cjs} +1170 -280
- package/dist/chunk-RQMI7EJT.cjs.map +1 -0
- package/dist/{chunk-X3HB4DSR.js → chunk-TGJACVLD.js} +1150 -263
- package/dist/chunk-TGJACVLD.js.map +1 -0
- package/dist/{chunk-AXZUKNXF.js → chunk-TYWFKCPY.js} +4 -4
- package/dist/{chunk-AXZUKNXF.js.map → chunk-TYWFKCPY.js.map} +1 -1
- package/dist/{chunk-ECA2WNRX.cjs → chunk-ZMAD3KJU.cjs} +9 -11
- package/dist/chunk-ZMAD3KJU.cjs.map +1 -0
- package/dist/index.cjs +189 -116
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -14
- package/dist/index.d.ts +16 -14
- package/dist/index.js +136 -45
- package/dist/index.js.map +1 -1
- package/dist/loader.css +1 -1
- package/dist/three.cjs +6 -6
- package/dist/three.d.cts +2 -3
- package/dist/three.d.ts +2 -3
- package/dist/three.js +2 -2
- package/dist/{types-DSGwN5w1.d.ts → types-DUVefWiB.d.cts} +128 -3
- package/dist/{types-DSGwN5w1.d.cts → types-DUVefWiB.d.ts} +128 -3
- package/docs/api.md +16 -6
- package/docs/dsl.md +14 -14
- package/llms-full.txt +38 -28
- package/llms.txt +1 -1
- package/package.json +3 -4
- package/skills/pixi-effects/SKILL.md +1 -1
- package/skills/pixi-effects/reference/cheatsheet.md +3 -3
- package/skills/pixi-effects/reference/pitfalls.md +3 -3
- package/skills/pixi-effects/reference/recipes.md +1 -1
- package/skills/pixi-effects/template.html +6 -8
- package/dist/Composition-H4QA24MV.js +0 -7
- package/dist/Composition-HM4YT5L7.cjs +0 -16
- package/dist/chunk-555URQHQ.cjs.map +0 -1
- package/dist/chunk-ECA2WNRX.cjs.map +0 -1
- package/dist/chunk-GBUM7X2S.js.map +0 -1
- package/dist/chunk-TLYTYBWX.js.map +0 -1
- package/dist/chunk-WPXDRSGN.cjs.map +0 -1
- package/dist/chunk-X3HB4DSR.js.map +0 -1
package/llms-full.txt
CHANGED
|
@@ -6,7 +6,7 @@ Concatenation of the skill, cheatsheet, pitfalls, recipes and the DSL / API refe
|
|
|
6
6
|
|
|
7
7
|
<!-- ===== skills/pixi-effects/SKILL.md ===== -->
|
|
8
8
|
|
|
9
|
-
> Written for pixi-effects 0.
|
|
9
|
+
> Written for pixi-effects 0.27.0. Update this skill with `npx skills update`; the newest docs are at https://yjmtmtk.github.io/pixi-effects/
|
|
10
10
|
|
|
11
11
|
# pixi-effects — write a video as data
|
|
12
12
|
|
|
@@ -75,7 +75,7 @@ A video is **data**: a tree of plain objects. Everything below is a JS object yo
|
|
|
75
75
|
| Canvas | pixels, origin top-left, `+x` right, `+y` down. Typical: 1280×720 @ 30 fps |
|
|
76
76
|
| Angles | **degrees**: `rotation`, `skew*`, `rotationX`, `rotationY` |
|
|
77
77
|
| Depth (`z`) | `+z` toward the viewer (bigger, nearer) |
|
|
78
|
-
| Numbers | any number may be an **expression string**: `'GW/2 - w/2'`, `'min(W,H)*0.4'` (build-time only; no per-frame time variable). Functions: `min max abs floor ceil round sqrt pow sin cos tan lerp clamp smoothstep mod step`, `PI`, and **seeded** `rand(seed)` (0…1) / `noise(x, seed)` (−1…1): give each layer its own seed, never `Math.random()`. **
|
|
78
|
+
| Numbers | any number may be an **expression string**: `'GW/2 - w/2'`, `'min(W,H)*0.4'` (build-time only; no per-frame time variable). Functions: `min max abs floor ceil round sqrt pow sin cos tan lerp clamp smoothstep mod step`, `PI`, and **seeded** `rand(seed)` (0…1) / `noise(x, seed)` (−1…1): give each layer its own seed, never `Math.random()`. **Relative values** work in a keyframe's `to` / `from` / `set` (not in `initial`, a style, or the start of a `from`+`to`): `'+=36'` / `'-=36'` are measured from where the layer stands, and the right side is an expression (`'-=GW*0.1'`). A slide-in is `from: { x: '-=36' }`, a nudge `to: { y: '+=40' }`; keyframes one after another stack; with `repeat` the same move replays from the same start (it does not stack). Expressions work in layer `x y` and the other numeric props, **not** in `line.from / to`, `polygon.points` or `path.d`: compute those in JS (`const W = 1920`) |
|
|
79
79
|
| Colours | `'#rrggbb'` strings or numbers; text colour is `style.fill`, shape colour is `fillColor`/`strokeColor`, image colour is `tint` |
|
|
80
80
|
| Z-order | **array order**, later = on top. A layer is hidden (not removed) outside `[at, at+duration)` and keeps its last values |
|
|
81
81
|
| Time of a sequence's `at` | seconds from the start of its **parent composition** (default 0) |
|
|
@@ -111,7 +111,7 @@ blendMode: 'normal' | 'add' | 'screen' | 'multiply' | 'overlay' | 'soft-light' |
|
|
|
111
111
|
|
|
112
112
|
Animatable props (`initial` / keyframes): `x y alpha rotation scale scaleX scaleY pivotX pivotY anchorX anchorY skewX skewY tint width height visible autoAlpha`, plus for shapes their style/geometry, for text `fill`, for audio `volume`, for 3D `z rotationX rotationY`, filter params as `'filters.<name>.<param>'`, three objects as `'three.<obj>.<path>'`. **These go in `initial` / keyframes, never at the top level of a shape or text layer** (top-level `x`, `rotation`, `strokeColor`, `fillAlpha` each warn "is not a … key"): the top level of a shape holds its geometry (`width height radius d startAngle endAngle cornerRadius anchorX anchorY`), `trimStart` / `trimEnd`, `strokeCap` / `strokeJoin`, `fillGradient`.
|
|
113
113
|
|
|
114
|
-
Keyframe kinds: `set` (jump at `at`; undone when you seek back), `to` (animate to), `from` (animate from), `from`+`to
|
|
114
|
+
Keyframe kinds: `set` (jump at `at`; undone when you seek back), `to` (animate to), `from` (animate from), `from`+`to`; a value may be relative to where the layer stands (`from: { x: '-=36' }` slides in from 36 px to the left of its place). `from` / `from+to` show their start value from the layer's start, so a delayed fade-in needs no `initial.alpha`. `repeat` (finite extra plays), `yoyo`, `repeatDelay` loop any of them (endless repeats are not allowed). `ease` = an ease name (`'none'`, `'power2.out'`, `'expo.out'`, `'back.out(1.7)'`, `'elastic.out(1,0.5)'`, `'sine.inOut'`, `'bounce.out'`, **`'cubic-bezier(.4, 0, .2, 1)'`** (CSS syntax, x1 and x2 in 0…1; `.4, 0, .2, 1` is a gentle in-out; `.2, .8, .2, 1` is FRONT-LOADED, not slow motion: half of the change in the first 13 % of the time, 90 % by 39 %, 99 % by 74 %, then a long settle, so what sits at the END of the content gets the glide; `.2, .8, .5, 1` has a shorter tail). `power1…4` are the powers 2…5: `power2` is a cubic, `power2.in` has done only 12.5 % at half time (42 % at three quarters)… and springs: `'spring(mass, stiffness, damping)'` or `'spring.gentle' | 'snappy' | 'bouncy' | 'wobbly' | 'slow'`; **`duration: 'auto'` = the time the spring takes to settle** (presets, settle time / highest value reached as a multiple of the target: `snappy` 0.42 s / 1.01, `gentle` 0.72 s / 1.02, `bouncy` 1.08 s / 1.28, `slow` 1.29 s / 1.00, `wobbly` 1.61 s / 1.40: time the next beat from these) (real physics; with a number `duration` is the settle time and only the damping ratio shapes it; a damping ratio of 1 or more never overshoots, so `spring(1,170,26)` does not bounce: use damping below 20 at stiffness 170; `'auto'` works on keyframes only, not in `animateText` / `orbit` tweens; a `stagger` ease may be a spring, its delays are held inside the spread). Default ease is linear.
|
|
115
115
|
|
|
116
116
|
## Layer types
|
|
117
117
|
|
|
@@ -233,7 +233,7 @@ const movie = new Movie();
|
|
|
233
233
|
await movie.init({ canvas, width, height, duration, frameRate, background, assets, composition });
|
|
234
234
|
movie.play(); movie.pause();
|
|
235
235
|
await movie.gotoFrame(n, true); // seek (frames; 30 fps => t = n/30)
|
|
236
|
-
const blob = await movie.render({ format: 'mp4' }); // 'mp4' | 'webm' | 'mov' | 'mkv'; ~real-time. Part of it: `range: [2, 5]` (seconds) or `range: 'title'` (a top-level layer's span; picture and sound); `scale: 0.5` / `draft: true` (half size, low quality, no motion blur: a smaller file, not a faster drawing). Fast motion: `movie.init({ motionBlur: true })` (8 samples, 180° shutter; or a number / { samples: 2–64, shutter: 0–1 }) blurs render / snapshot / contactSheet, not live playback; a render takes `samples`× as long
|
|
236
|
+
const blob = await movie.render({ format: 'mp4' }); // 'mp4' | 'webm' | 'mov' | 'mkv'; ~real-time. `format: 'wav'` / `'ogg'` writes only the movie's SOUND (a `music` score as an audio file). `video: { codec: 'hevc' | 'av1' | 'vp9' | 'avc', bitrate: '8M', hardware, keyFrameInterval }` (a codec this browser cannot encode names the one that works). Part of it: `range: [2, 5]` (seconds) or `range: 'title'` (a top-level layer's span; picture and sound); `scale: 0.5` / `draft: true` (half size, low quality, no motion blur: a smaller file, not a faster drawing). Fast motion: `movie.init({ motionBlur: true })` (8 samples, 180° shutter; or a number / { samples: 2–64, shutter: 0–1 }) blurs render / snapshot / contactSheet, not live playback; a render takes `samples`× as long
|
|
237
237
|
movie.on('progress', e => e.progress /* 0–100 */);
|
|
238
238
|
movie.audioBuffer // mixed audio (after init), if any audio layers
|
|
239
239
|
await movie.contactSheet({ count: 6, as: 'dataURL' }) // ONE image of several labelled frames — look at it. Options: frames | times | count, columns, cellWidth, as
|
|
@@ -305,14 +305,14 @@ A good habit that catches most of these: after `init`, read `window.__logs` (see
|
|
|
305
305
|
30c. **Look with the built-in tools, not OS screenshots**: `await movie.contactSheet({ count: 6, as: 'dataURL' })` returns one labelled image of the whole animation; `await movie.inspect(frame)` returns every layer's canvas bounds and `issues`. If you do use a browser tool, `screenshot` needs an **absolute path**, macOS `sed -i` needs `''`, and a static server may redirect `/x.html` → `/x` and drop `?query`. Keep the template's `try/catch` around `init` so failures land in `window.__logs`. Check ≥ 5 timestamps including mid-transition. *(fixed: tools built in)*
|
|
306
306
|
31. **Check audio with `movie.inspectAudio()`**: every sound measured on its own (`sound.length`, `peakDb`, `loudestAt`, `brightnessHz`), when it plays, loudness and brightness per 0.1 s window, and `issues` (the mix was limited, a layer is inaudible, an sfx is cut off by the end). Decoding a render by hand is no longer needed: `render()` encodes the same mix, on time in mp4 / mov / webm / mkv. *(fixed)*
|
|
307
307
|
|
|
308
|
-
31b. **A mistyped `ease` now warns** (`ease: 'power3.outt'` → did you mean `power3.out`). It used to run silently as
|
|
308
|
+
31b. **A mistyped `ease` now warns** (`ease: 'power3.outt'` → did you mean `power3.out`). It used to run silently as the default ease (`power1.out`), so the motion changed and nothing said so.
|
|
309
309
|
31c. **`draft` and `scale` make the file smaller, not the drawing faster** (measured: a 48 s 1080p film exports in about the same time as a draft; the file is 7× smaller). To save time, export a part (`range` / `--range` / `--scene`), look at single moments (`check --no-export --at …`), and keep motion blur off while you iterate.
|
|
310
310
|
|
|
311
311
|
## Round 2 — found by building 16 portfolio pieces (examples/gallery)
|
|
312
312
|
|
|
313
313
|
32. **Seeking is safe now** *(fixed)*: a `set` keyframe (counter value, text fill, image tint, shape style) is undone when you seek back before it, and shapes are redrawn before they are culled, so a rect growing from `width: 0` at the left edge draws after any jump seek (contact sheet, snapshot, scrubbing, export). The old workarounds (`width: 0.01`, a one-frame `to` instead of `set`) are not needed.
|
|
314
314
|
33. **A mask starts and ends with the layer it masks** *(fixed)*, so its keyframes count from that layer's start. A mask with an explicit `at` keeps composition time. Before, a mask `at: 0` reveal had finished before a layer that appeared at 8 s, and the layer stayed invisible without any warning.
|
|
315
|
-
34. **`from` and `from+to` show their start value from the LAYER's start**, not from the keyframe's `at` (GSAP
|
|
315
|
+
34. **`from` and `from+to` show their start value from the LAYER's start**, not from the keyframe's `at` (as in GSAP and After Effects): a delayed fade-in `{ at: 2, from: { alpha: 0 }, to: { alpha: 1 } }` needs no `initial.alpha`. To keep the current value until `at` and then jump, use `set`. *(docs)*
|
|
316
316
|
35. **One `Movie` per page.** Creating a second Movie (for an isolated repro) and destroying it breaks the first (`null.clear`). Reload instead. *(docs)*
|
|
317
317
|
36. **Multi-line text is centre-aligned by default** (`style.align: 'left'` for left-aligned paragraphs). *(docs)*
|
|
318
318
|
37. **Composition size.** A 2D composition does not clip its children (a child outside `width × height` still draws), but its rectangle is the culling area: if the whole rectangle is off the canvas the group is skipped even when a child is on screen — keep it near what it draws, or canvas-sized. A `threeD` composition (a card) IS clipped at its `width × height` (it is rendered into a texture that size): size it for the soft shadow too. A growing `threeD` shape or text is fine: its texture is resized in place (it used to warn "destroyed while still bound"). *(measured; docs / fixed)*
|
|
@@ -352,7 +352,7 @@ A good habit that catches most of these: after `init`, read `window.__logs` (see
|
|
|
352
352
|
70. **`speed` is a setting, `time` is the property.** `speed: 2` on the layer; to change the speed over time animate `time` (a curve of seconds in the file), never `keyframes: [{ to: { speed: 3 } }]` (warns). `speed: 0` is not allowed (hold a moment with `time` to the same value twice); `speed` together with keyframed `time`: `time` wins. Other names (`playbackRate`, `timeScale`, `rate`, `reverse: true`, `timeRemap`) are warned about with what to write. `particles({ speed })` is something else: launch speed in px per second.
|
|
353
353
|
71. **Sound follows `speed` like a tape: the pitch moves with it.** An audio file or a video's sound at `speed: 2` is an octave up; backward plays reversed. Not for `sfx` / `music` layers (warns): change an sfx with `pitch` (semitones), or put the sfx **inside a remapped composition** and it follows that clock (slower and lower, or backward).
|
|
354
354
|
72. **A video at `speed: 2` with no `duration` is half as long; with a `duration` longer than the file it holds the last frame** (add `loop: true` to repeat). A negative `time` counts back from the end of the file (`initial: { time: -2 }` = two seconds before the end).
|
|
355
|
-
73. **Know the shape of the curve before you time a beat to it.** `cubic-bezier(.2, .8, .2, 1)` does half of its change in the first 13 % of the time and 99 % by 74 % (not slow motion: over 6 s the first 2 s of content pass in 0.6 s); `.2, .8, .5, 1` has a shorter tail.
|
|
355
|
+
73. **Know the shape of the curve before you time a beat to it.** `cubic-bezier(.2, .8, .2, 1)` does half of its change in the first 13 % of the time and 99 % by 74 % (not slow motion: over 6 s the first 2 s of content pass in 0.6 s); `.2, .8, .5, 1` has a shorter tail. `power2` is a cubic (`power1` = t², `power3` = t⁴): `power2.in` has done 12.5 % at half time and 42 % at three quarters, so a rewind with it dismantles most of the title in the last third.
|
|
356
356
|
74. **`focus` as a layer name means that layer's FIRST `z`, and the name must be unique.** (Two layers with one name: the first `threeD` one is used and it warns.) The camera does not follow a layer whose `z` moves (it warns): to follow it, animate `focus` itself with numbers. A name no layer has, or a layer that is not `threeD`, warns and the `z = 0` plane is used (did-you-mean for typos).
|
|
357
357
|
75. **Depth of field blurs `threeD` layers only, and the layer's own depth decides it.** A 2D layer never blurs; a `threeD` layer at the focus depth stays sharp; the depth is measured along the camera's view direction, so with an orbiting or tilted camera a layer off to the side of the look-at point is a little deeper than its `z` says and blurs a little even at the focus `z`; one blur for the whole layer, so a tilted floor is one blur (split it into layers).
|
|
358
358
|
76. **`aperture` is the lens diameter in px, not a 0–1 strength.** 30 is the default (clearly shallow), 0 turns it off, 60 is strong, 100 is extreme (small bright objects speckle); the blur never exceeds 32 px. 20 or more blurred layers at once warns (one filter pass each, more on WebGPU).
|
|
@@ -480,7 +480,7 @@ return [
|
|
|
480
480
|
|
|
481
481
|
## Rewind and play again: a composition whose own time you control
|
|
482
482
|
|
|
483
|
-
Put the part you want to rewind, slow down or freeze **in a composition**, then give that composition `speed` (one straight speed: `0.5` slow motion, `-1` backward) or `time` keyframes (any curve of its own time: play, hold, rewind, play slowly). The layers inside write `at` / `duration` / `keyframes` in the composition's own seconds; the composition's own `at` / `duration` stay in the outer time. A sound or `sfx` inside follows the clock (backward and lower when it rewinds, like a tape). `cubic-bezier(.2, .8, .2, 1)` is front-loaded, not slow motion: half of the replay passes in the first 13 % of its time and 99 % by 74 %, so the replay opens quickly and what sits at the END of the content (the ring, the settle) gets the long glide.
|
|
483
|
+
Put the part you want to rewind, slow down or freeze **in a composition**, then give that composition `speed` (one straight speed: `0.5` slow motion, `-1` backward) or `time` keyframes (any curve of its own time: play, hold, rewind, play slowly). The layers inside write `at` / `duration` / `keyframes` in the composition's own seconds; the composition's own `at` / `duration` stay in the outer time. A sound or `sfx` inside follows the clock (backward and lower when it rewinds, like a tape). `cubic-bezier(.2, .8, .2, 1)` is front-loaded, not slow motion: half of the replay passes in the first 13 % of its time and 99 % by 74 %, so the replay opens quickly and what sits at the END of the content (the ring, the settle) gets the long glide. `power2` is a cubic: `power2.in` has done only 12.5 % at half time, so the dismantling bunches up in the last third of the rewind.
|
|
484
484
|
|
|
485
485
|
```js
|
|
486
486
|
// @recipe rewind-and-replay
|
|
@@ -1922,7 +1922,7 @@ Fill a shape with a gradient instead of `fillColor` (top level or in `initial`).
|
|
|
1922
1922
|
|
|
1923
1923
|
#### `colorSpace`
|
|
1924
1924
|
|
|
1925
|
-
Per-shape choice of how colour keyframes are interpolated. Default `'rgb'` (
|
|
1925
|
+
Per-shape choice of how colour keyframes are interpolated. Default `'rgb'` (a straight lerp of the red, green and blue channels) is fast but classic — a red → green ramp passes through muddy olive at the midpoint. The two perceptually uniform options keep saturation through the transition:
|
|
1926
1926
|
|
|
1927
1927
|
| Value | Behaviour |
|
|
1928
1928
|
|-----------|---------------------------------------------------------------------------------------------------|
|
|
@@ -2445,7 +2445,7 @@ Supported formats are whatever PixiJS Assets and the browser's audio/video decod
|
|
|
2445
2445
|
interface Keyframe {
|
|
2446
2446
|
at?: number; // start, in seconds from the start of THIS sequence; negative = back from the sequence's end (-0.5 = 0.5 s before it ends)
|
|
2447
2447
|
duration?: number | 'auto'; // seconds (default 0 — instantaneous); 'auto' = the time a spring ease takes to settle
|
|
2448
|
-
ease?: string; //
|
|
2448
|
+
ease?: string; // Ease name (default 'none'), or a spring: 'spring(mass, stiffness, damping)', 'spring.gentle|snappy|bouncy|wobbly|slow'
|
|
2449
2449
|
set?: Props; // jump to these values at `at`
|
|
2450
2450
|
to?: Props; // animate from current to these values over `duration`
|
|
2451
2451
|
from?: Props; // animate from these values to current
|
|
@@ -2453,6 +2453,8 @@ interface Keyframe {
|
|
|
2453
2453
|
}
|
|
2454
2454
|
```
|
|
2455
2455
|
|
|
2456
|
+
**Relative values.** A number in `to`, `from` or `set` may be written `'+=36'` or `'-=36'`: it is measured from where the property stands when the keyframe starts (the way GSAP writes them), and the right side is an expression (`'-=GW*0.1'`). `from: { x: '-=36' }` starts 36 px to the left of where the layer is and arrives there (the usual slide-in); `to: { y: '+=40' }` moves 40 px further. Several keyframes one after another stack, whatever order you seek in; with `repeat` the same move plays again from the same start (it does not stack). A relative value in `initial`, a style, or the start side of a `from` + `to` keyframe has nothing to be measured from: it warns and is read as the number.
|
|
2457
|
+
|
|
2456
2458
|
The four kinds are mutually exclusive per keyframe:
|
|
2457
2459
|
|
|
2458
2460
|
- **`set`** — instantaneous property assignment at `at`.
|
|
@@ -2515,13 +2517,13 @@ If `at < 0`, it's interpreted as `duration + at` — measured back from the end
|
|
|
2515
2517
|
|
|
2516
2518
|
### Easing
|
|
2517
2519
|
|
|
2518
|
-
`'cubic-bezier(x1, y1, x2, y2)'` is the CSS curve exactly (x1 and x2 between 0 and 1, y1 and y2 may overshoot): `'cubic-bezier(.4, 0, .2, 1)'` is a gentle in-out, `'cubic-bezier(.2, .8, .2, 1)'` is front-loaded, not slow motion (half of the change in the first 13 % of the time, 99 % by 74 %, then a long settle).
|
|
2520
|
+
`'cubic-bezier(x1, y1, x2, y2)'` is the CSS curve exactly (x1 and x2 between 0 and 1, y1 and y2 may overshoot): `'cubic-bezier(.4, 0, .2, 1)'` is a gentle in-out, `'cubic-bezier(.2, .8, .2, 1)'` is front-loaded, not slow motion (half of the change in the first 13 % of the time, 99 % by 74 %, then a long settle). `power2` is a cubic: `power2.in` has done only 12.5 % at half time. A malformed one warns once and runs as `'none'`.
|
|
2519
2521
|
|
|
2520
|
-
|
|
2522
|
+
Ease strings (the names GSAP made familiar): `'none'`, `'linear'`, `'power1.in'` ... `'power4.inOut'`, `'sine.in/out/inOut'`, `'expo.in/out/inOut'`, `'circ.in/out/inOut'`, `'back.in/out/inOut(overshoot)'`, `'elastic.in/out/inOut(amplitude, period)'`, `'bounce.in/out/inOut'`. `'steps(n)'` is n equal steps.
|
|
2521
2523
|
|
|
2522
2524
|
### PIXI shorthands
|
|
2523
2525
|
|
|
2524
|
-
These keys are
|
|
2526
|
+
These keys are shorthands the timeline understands when used in `initial` / `set` / `to` / `from` / `keyframes`:
|
|
2525
2527
|
|
|
2526
2528
|
```
|
|
2527
2529
|
scale, scaleX, scaleY
|
|
@@ -2532,12 +2534,10 @@ position, positionX, positionY
|
|
|
2532
2534
|
tilePosition, tilePositionX, tilePositionY
|
|
2533
2535
|
tileScale, tileScaleX, tileScaleY
|
|
2534
2536
|
tint, autoAlpha
|
|
2535
|
-
|
|
2536
|
-
blur, blurX, blurY, blurPadding
|
|
2537
|
-
lineColor, lineAlpha, fillColor, fillAlpha
|
|
2537
|
+
fillColor, fillAlpha (a shape's own colour keys)
|
|
2538
2538
|
```
|
|
2539
2539
|
|
|
2540
|
-
(In addition to plain DisplayObject props like `x`, `y`, `rotation`, `alpha`, `width`, `height`, `visible`.) **Angles are in degrees**: `rotation`, `skew` / `skewX` / `skewY
|
|
2540
|
+
(In addition to plain DisplayObject props like `x`, `y`, `rotation`, `alpha`, `width`, `height`, `visible`.) `blur`, `colorize` and the other colour-matrix shorthands of GSAP's PixiPlugin are gone: use a named filter and animate `filters.<name>.<prop>` (below). **Angles are in degrees**: `rotation`, `skew` / `skewX` / `skewY`, and `rotationX` / `rotationY` for 3D layers.
|
|
2541
2541
|
|
|
2542
2542
|
### Filter keyframe paths
|
|
2543
2543
|
|
|
@@ -2664,7 +2664,7 @@ Common fields (`TransitionCommon`):
|
|
|
2664
2664
|
| `to` | string | sibling sequence's `name`. Must be declared **after** `from` in `sequences[]`. |
|
|
2665
2665
|
| `at` | number | start of the transition, in the **parent composition's** time (like a sequence's `at`, not sequence-local); negative = back from the parent's end. |
|
|
2666
2666
|
| `duration` | number | seconds, must be > 0. |
|
|
2667
|
-
| `ease` | string? |
|
|
2667
|
+
| `ease` | string? | Ease name. Default `'none'` (linear). |
|
|
2668
2668
|
|
|
2669
2669
|
Validation runs at composition build time. Errors throw with the offending `transitions[<index>]` quoted in the message: missing names, `to` before `from`, transition window outside either sequence's lifespan, duplicate use of one sequence as `from`, `from === to`, `duration <= 0`.
|
|
2670
2670
|
|
|
@@ -2931,7 +2931,7 @@ The image is centred on the canvas; `fit` (default `'cover'`) controls how the t
|
|
|
2931
2931
|
| `name?` | string | sequence name so transitions can reference it |
|
|
2932
2932
|
| `at?` | number | start time, parent-relative seconds |
|
|
2933
2933
|
| `fit?` | `'cover'` \| `'contain'` | how the texture fills the canvas. Default `'cover'`. |
|
|
2934
|
-
| `ease?` | string |
|
|
2934
|
+
| `ease?` | string | Ease name. Default `'sine.inOut'`. |
|
|
2935
2935
|
|
|
2936
2936
|
#### `motion: 'still'`
|
|
2937
2937
|
|
|
@@ -3067,7 +3067,7 @@ import { cameraPath, wiggle } from 'pixi-effects';
|
|
|
3067
3067
|
| `points` | `[[x, y, z], …]`, at least two, the route in order |
|
|
3068
3068
|
| `duration` | seconds; required |
|
|
3069
3069
|
| `at` | start, seconds from the start of the camera layer (default 0) |
|
|
3070
|
-
| `ease` |
|
|
3070
|
+
| `ease` | Ease for the progress along the route (default `'none'`: constant speed; `'power2.in'` an accelerating rush) |
|
|
3071
3071
|
| `look` | `'ahead'` (default: face where it flies) or a fixed `[x, y, z]` |
|
|
3072
3072
|
| `lookAhead` | with `'ahead'`: how far ahead, as a fraction 0–1 of the route (default 0.08); past the end it continues along the last direction |
|
|
3073
3073
|
| `smooth` | `true` (default): a smooth curve through the points (Catmull-Rom); `false`: straight lines |
|
|
@@ -3093,7 +3093,7 @@ import { followPath } from 'pixi-effects';
|
|
|
3093
3093
|
| `d` | the route: SVG path data in canvas coordinates; several sub-paths are walked in order; a closed one comes back to its start |
|
|
3094
3094
|
| `duration` | seconds; required |
|
|
3095
3095
|
| `at` | start, seconds from the start of the layer (default 0): give the layer its own `at` so it is not drawn at the origin before the trip |
|
|
3096
|
-
| `ease` |
|
|
3096
|
+
| `ease` | Ease for the progress along the path (default `'none'`: constant speed) |
|
|
3097
3097
|
| `from` / `to` | fractions 0–1 of the path to start and stop at (default 0 → 1; `from: 1, to: 0` goes backwards) |
|
|
3098
3098
|
| `orient` / `rotate` | `orient: true` also keyframes `rotation` (degrees) to face the way it is going, without ever jumping by a turn; `rotate` adds an offset (`-90` for an image that points up) |
|
|
3099
3099
|
| `frameRate` | samples per second (default 30) |
|
|
@@ -3162,7 +3162,7 @@ const ripple = stagger(18, { each: 0.12, grid: [6, 3], from: 'center', ease: 'si
|
|
|
3162
3162
|
| `amount` | seconds from the first to the last, however many there are |
|
|
3163
3163
|
| `from` | `'start'` (default), `'end'`, `'center'` (outward), `'edges'` (inward), `'random'` (a seeded shuffle of the same delays) or an item index |
|
|
3164
3164
|
| `grid` | `[columns, rows]`: items fill the grid row by row, and the wave travels by straight-line distance (a ripple) |
|
|
3165
|
-
| `ease` |
|
|
3165
|
+
| `ease` | an ease that reshapes the spread (default `'none'`); the first stays at 0, the last at the full spread |
|
|
3166
3166
|
| `seed` | for `from: 'random'`: another seed, another order |
|
|
3167
3167
|
|
|
3168
3168
|
With a layer array each layer's own `at` is kept and the delay is added to it. Keyframes inside the layers stay as they are (they count from the layer's start).
|
|
@@ -3347,14 +3347,16 @@ Renders the entire timeline to a single video file. Pauses playback first.
|
|
|
3347
3347
|
|
|
3348
3348
|
```ts
|
|
3349
3349
|
interface RenderOptions {
|
|
3350
|
-
format?: 'mp4' | 'mov' | 'webm' | 'mkv'; // default 'mp4'
|
|
3350
|
+
format?: 'mp4' | 'mov' | 'webm' | 'mkv' | 'wav' | 'ogg'; // default 'mp4'; wav / ogg: ONLY the movie's sound (an error when it has none)
|
|
3351
3351
|
video?: {
|
|
3352
|
-
codec?: string; // default per format (mp4/mov→avc, webm
|
|
3353
|
-
bitrate?: 'very-low' | 'low' | 'medium' | 'high' | 'very-high'; // default 'high'
|
|
3352
|
+
codec?: string; // 'avc' (H.264) | 'hevc' | 'vp9' | 'av1' | 'vp8'; default per format (mp4/mov→avc, webm→vp9, mkv→vp9)
|
|
3353
|
+
bitrate?: 'very-low' | 'low' | 'medium' | 'high' | 'very-high' | number | string; // default 'high'; or bits a second: 8_000_000, '8M', '800k'
|
|
3354
|
+
hardware?: 'no-preference' | 'prefer-hardware' | 'prefer-software'; // where to encode (default 'no-preference')
|
|
3355
|
+
keyFrameInterval?: number; // seconds between keyframes (default 2): shorter seeks faster, a bigger file
|
|
3354
3356
|
};
|
|
3355
3357
|
audio?: {
|
|
3356
|
-
codec?: string; // default per format (mp4/mov→aac, webm/mkv→opus)
|
|
3357
|
-
bitrate?: 'very-low' | 'low' | 'medium' | 'high' | 'very-high'; // default 'high'
|
|
3358
|
+
codec?: string; // default per format (mp4/mov→aac, webm/mkv/ogg→opus, wav→pcm-s16)
|
|
3359
|
+
bitrate?: 'very-low' | 'low' | 'medium' | 'high' | 'very-high' | number | string; // default 'high' (not for wav: it is not compressed)
|
|
3358
3360
|
};
|
|
3359
3361
|
motionBlur?: boolean | number | { samples?: number; shutter?: number }; // overrides movie.init's; false turns it off
|
|
3360
3362
|
}
|
|
@@ -3369,6 +3371,10 @@ interface RenderOptions {
|
|
|
3369
3371
|
- After a blurred snapshot, contact sheet or render the playhead and the stage are on an exact frame again, not a sample.
|
|
3370
3372
|
- `pixi-effects-render --motion-blur 8 [--shutter 0.5]` sets it for one file.
|
|
3371
3373
|
|
|
3374
|
+
**Video codec:** unless you name `video.codec`, the usual one for the format is used (mp4 / mov: `avc`; webm: `vp9`); if this browser cannot encode it, the next that works is used and a warning says so. A codec you name is never swapped: it is used, or the error names the one that would work (`"av1" ... "avc" would work: render({ format: 'mp4', video: { codec: 'avc' } })`). `hevc` and `av1` make smaller files at the same quality but play in fewer places than `avc`; `hardware: 'prefer-hardware'` asks the GPU's encoder.
|
|
3375
|
+
|
|
3376
|
+
**Sound only:** `render({ format: 'wav' })` (16-bit PCM, no loss) or `render({ format: 'ogg' })` (Opus) writes the movie's mixed sound without drawing a frame: a tune written as `music` becomes an audio file. `range` cuts it like a video. `pixi-effects render page.html -o mix.wav` does the same from a script. `mp3` and `flac` are not offered: browsers cannot encode them.
|
|
3377
|
+
|
|
3372
3378
|
**Audio codec fallback:** unless you name `audio.codec`, the first codec this browser can actually encode is used (mp4: `aac`, then `opus`, `mp3`, `flac`; webm: `opus`, `vorbis`). Chrome on Linux has no AAC encoder, so there an mp4 with sound carries Opus and a warning says so (before, the export failed with a message about encoder configurations). A codec you name is never swapped: if it cannot be encoded the error says which one would work.
|
|
3373
3379
|
|
|
3374
3380
|
Returns a `Blob` whose `type` is the container's MIME (e.g. `video/mp4`). Emits `'progress'` repeatedly during the render.
|
|
@@ -3397,6 +3403,10 @@ The whole movie as rows on a time axis, for a person to read at a glance. `timel
|
|
|
3397
3403
|
|
|
3398
3404
|
`movie.timelineSvg()` is the chart alone (one `<svg>`, geometry in `data-*` attributes, each row `<g class="row" data-start>`): `pixi-effects-view` puts a playhead on it and turns a click into a seek.
|
|
3399
3405
|
|
|
3406
|
+
### `ease(name): (p: number) => number`
|
|
3407
|
+
|
|
3408
|
+
The function behind an ease name (`ease('power2.out')(0.5)`, `ease('spring.bouncy')`, `ease('cubic-bezier(.4, 0, .2, 1)')`), for a preset or a check of your own. A name nobody knows warns once and runs as `power1.out`.
|
|
3409
|
+
|
|
3400
3410
|
### `movie.destroy(): Promise<void>`
|
|
3401
3411
|
|
|
3402
3412
|
Pauses playback, destroys the underlying PIXI Application, releases audio buffers and AudioContext, and marks the instance unusable.
|
|
@@ -3440,7 +3450,7 @@ The names are the ones an HTML5 `<video>` uses, so a player written for `<video>
|
|
|
3440
3450
|
| `volume` | number | 0..1 getter/setter; immediate. Setter clamps and applies to active audio |
|
|
3441
3451
|
| `muted` | boolean | getter/setter; immediate |
|
|
3442
3452
|
| `app` | `pixi.js Application \| null` | PIXI Application instance (advanced/escape hatch) |
|
|
3443
|
-
| `timeline` |
|
|
3453
|
+
| `timeline` | `Timeline \| null` | the movie's timeline (advanced): `time(t)`, `duration()`, `segments()` |
|
|
3444
3454
|
| `stops` | `Stop[]` | the composition's stops (see Presentations); empty for an ordinary movie |
|
|
3445
3455
|
| `stopIndex`, `pageIndex`, `pageCount`, `currentStop` | number, number, number, `Stop \| null` | where the playhead is among the stops and pages (`-1` / `null` before the first stop) |
|
|
3446
3456
|
|
package/llms.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# pixi-effects
|
|
2
2
|
|
|
3
|
-
> Declarative motion graphics and video for the web. Describe a video as plain objects (text, shapes, images, video, audio, nested compositions, a 2.5D camera, optional three.js), play it in a canvas, export MP4 / WebM / MOV. Built on PixiJS v8
|
|
3
|
+
> Declarative motion graphics and video for the web. Describe a video as plain objects (text, shapes, images, video, audio, nested compositions, a 2.5D camera, optional three.js), play it in a canvas, export MP4 / WebM / MOV. Built on PixiJS v8 and mediabunny. Written to be authored by AI: no per-frame code.
|
|
4
4
|
|
|
5
5
|
Rules that cause most failures (details in the skill and pitfalls):
|
|
6
6
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pixi-effects",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"description": "Declarative composition and video rendering for the web. Built on PixiJS v8.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"files": ["dist", "ai", "skills", "docs/dsl.md", "docs/api.md", "llms.txt", "llms-full.txt", "CHANGELOG.md", "README.md", "LICENSE"],
|
|
30
30
|
"keywords": [
|
|
31
31
|
"pixi", "pixijs", "video", "composition", "compositing", "rendering",
|
|
32
|
-
"after-effects", "motion-graphics", "timeline", "webgl", "
|
|
32
|
+
"after-effects", "motion-graphics", "timeline", "webgl", "keyframe", "typescript",
|
|
33
33
|
"three", "threejs"
|
|
34
34
|
],
|
|
35
35
|
"license": "MIT",
|
|
@@ -62,7 +62,6 @@
|
|
|
62
62
|
"mediabunny": "^1.61.3"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
|
-
"gsap": "^3.12.0",
|
|
66
65
|
"pixi.js": "^8.0.0",
|
|
67
66
|
"three": ">=0.150.0",
|
|
68
67
|
"pixi-filters": "^6.1.0"
|
|
@@ -76,7 +75,7 @@
|
|
|
76
75
|
"typescript": "^5.5.0",
|
|
77
76
|
"vitest": "^2.1.0",
|
|
78
77
|
"happy-dom": "^15.0.0",
|
|
79
|
-
"gsap": "
|
|
78
|
+
"gsap": "3.15.0",
|
|
80
79
|
"marked": "^18.0.14",
|
|
81
80
|
"pixi.js": "^8.22.0",
|
|
82
81
|
"pixi-filters": "^6.1.5",
|
|
@@ -3,7 +3,7 @@ name: pixi-effects
|
|
|
3
3
|
description: Use when asked to make, animate, preview or export a video, motion graphics, an animated title, a lower third, kinetic type, a promo, a slideshow with transitions, an animated chart or data story, a social clip, a 2.5D / depth scene, or music and sound for a video, written as code in JavaScript or HTML. pixi-effects is a JS library where a video is a plain-object composition (text, shape, image, video, audio and file-free sfx / music, camera, light and shader layers with keyframes) that plays in the browser, is checked by a tool the agent can run, and exports MP4 / WebM / MOV with no server. Not for a project that already uses another video tool (Remotion, Motion Canvas, ffmpeg); follow that tool instead.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
> Written for pixi-effects 0.
|
|
6
|
+
> Written for pixi-effects 0.27.0. Update this skill with `npx skills update`; the newest docs are at https://yjmtmtk.github.io/pixi-effects/
|
|
7
7
|
|
|
8
8
|
# pixi-effects — write a video as data
|
|
9
9
|
|
|
@@ -9,7 +9,7 @@ A video is **data**: a tree of plain objects. Everything below is a JS object yo
|
|
|
9
9
|
| Canvas | pixels, origin top-left, `+x` right, `+y` down. Typical: 1280×720 @ 30 fps |
|
|
10
10
|
| Angles | **degrees**: `rotation`, `skew*`, `rotationX`, `rotationY` |
|
|
11
11
|
| Depth (`z`) | `+z` toward the viewer (bigger, nearer) |
|
|
12
|
-
| Numbers | any number may be an **expression string**: `'GW/2 - w/2'`, `'min(W,H)*0.4'` (build-time only; no per-frame time variable). Functions: `min max abs floor ceil round sqrt pow sin cos tan lerp clamp smoothstep mod step`, `PI`, and **seeded** `rand(seed)` (0…1) / `noise(x, seed)` (−1…1): give each layer its own seed, never `Math.random()`. **
|
|
12
|
+
| Numbers | any number may be an **expression string**: `'GW/2 - w/2'`, `'min(W,H)*0.4'` (build-time only; no per-frame time variable). Functions: `min max abs floor ceil round sqrt pow sin cos tan lerp clamp smoothstep mod step`, `PI`, and **seeded** `rand(seed)` (0…1) / `noise(x, seed)` (−1…1): give each layer its own seed, never `Math.random()`. **Relative values** work in a keyframe's `to` / `from` / `set` (not in `initial`, a style, or the start of a `from`+`to`): `'+=36'` / `'-=36'` are measured from where the layer stands, and the right side is an expression (`'-=GW*0.1'`). A slide-in is `from: { x: '-=36' }`, a nudge `to: { y: '+=40' }`; keyframes one after another stack; with `repeat` the same move replays from the same start (it does not stack). Expressions work in layer `x y` and the other numeric props, **not** in `line.from / to`, `polygon.points` or `path.d`: compute those in JS (`const W = 1920`) |
|
|
13
13
|
| Colours | `'#rrggbb'` strings or numbers; text colour is `style.fill`, shape colour is `fillColor`/`strokeColor`, image colour is `tint` |
|
|
14
14
|
| Z-order | **array order**, later = on top. A layer is hidden (not removed) outside `[at, at+duration)` and keeps its last values |
|
|
15
15
|
| Time of a sequence's `at` | seconds from the start of its **parent composition** (default 0) |
|
|
@@ -45,7 +45,7 @@ blendMode: 'normal' | 'add' | 'screen' | 'multiply' | 'overlay' | 'soft-light' |
|
|
|
45
45
|
|
|
46
46
|
Animatable props (`initial` / keyframes): `x y alpha rotation scale scaleX scaleY pivotX pivotY anchorX anchorY skewX skewY tint width height visible autoAlpha`, plus for shapes their style/geometry, for text `fill`, for audio `volume`, for 3D `z rotationX rotationY`, filter params as `'filters.<name>.<param>'`, three objects as `'three.<obj>.<path>'`. **These go in `initial` / keyframes, never at the top level of a shape or text layer** (top-level `x`, `rotation`, `strokeColor`, `fillAlpha` each warn "is not a … key"): the top level of a shape holds its geometry (`width height radius d startAngle endAngle cornerRadius anchorX anchorY`), `trimStart` / `trimEnd`, `strokeCap` / `strokeJoin`, `fillGradient`.
|
|
47
47
|
|
|
48
|
-
Keyframe kinds: `set` (jump at `at`; undone when you seek back), `to` (animate to), `from` (animate from), `from`+`to
|
|
48
|
+
Keyframe kinds: `set` (jump at `at`; undone when you seek back), `to` (animate to), `from` (animate from), `from`+`to`; a value may be relative to where the layer stands (`from: { x: '-=36' }` slides in from 36 px to the left of its place). `from` / `from+to` show their start value from the layer's start, so a delayed fade-in needs no `initial.alpha`. `repeat` (finite extra plays), `yoyo`, `repeatDelay` loop any of them (endless repeats are not allowed). `ease` = an ease name (`'none'`, `'power2.out'`, `'expo.out'`, `'back.out(1.7)'`, `'elastic.out(1,0.5)'`, `'sine.inOut'`, `'bounce.out'`, **`'cubic-bezier(.4, 0, .2, 1)'`** (CSS syntax, x1 and x2 in 0…1; `.4, 0, .2, 1` is a gentle in-out; `.2, .8, .2, 1` is FRONT-LOADED, not slow motion: half of the change in the first 13 % of the time, 90 % by 39 %, 99 % by 74 %, then a long settle, so what sits at the END of the content gets the glide; `.2, .8, .5, 1` has a shorter tail). `power1…4` are the powers 2…5: `power2` is a cubic, `power2.in` has done only 12.5 % at half time (42 % at three quarters)… and springs: `'spring(mass, stiffness, damping)'` or `'spring.gentle' | 'snappy' | 'bouncy' | 'wobbly' | 'slow'`; **`duration: 'auto'` = the time the spring takes to settle** (presets, settle time / highest value reached as a multiple of the target: `snappy` 0.42 s / 1.01, `gentle` 0.72 s / 1.02, `bouncy` 1.08 s / 1.28, `slow` 1.29 s / 1.00, `wobbly` 1.61 s / 1.40: time the next beat from these) (real physics; with a number `duration` is the settle time and only the damping ratio shapes it; a damping ratio of 1 or more never overshoots, so `spring(1,170,26)` does not bounce: use damping below 20 at stiffness 170; `'auto'` works on keyframes only, not in `animateText` / `orbit` tweens; a `stagger` ease may be a spring, its delays are held inside the spread). Default ease is linear.
|
|
49
49
|
|
|
50
50
|
## Layer types
|
|
51
51
|
|
|
@@ -167,7 +167,7 @@ const movie = new Movie();
|
|
|
167
167
|
await movie.init({ canvas, width, height, duration, frameRate, background, assets, composition });
|
|
168
168
|
movie.play(); movie.pause();
|
|
169
169
|
await movie.gotoFrame(n, true); // seek (frames; 30 fps => t = n/30)
|
|
170
|
-
const blob = await movie.render({ format: 'mp4' }); // 'mp4' | 'webm' | 'mov' | 'mkv'; ~real-time. Part of it: `range: [2, 5]` (seconds) or `range: 'title'` (a top-level layer's span; picture and sound); `scale: 0.5` / `draft: true` (half size, low quality, no motion blur: a smaller file, not a faster drawing). Fast motion: `movie.init({ motionBlur: true })` (8 samples, 180° shutter; or a number / { samples: 2–64, shutter: 0–1 }) blurs render / snapshot / contactSheet, not live playback; a render takes `samples`× as long
|
|
170
|
+
const blob = await movie.render({ format: 'mp4' }); // 'mp4' | 'webm' | 'mov' | 'mkv'; ~real-time. `format: 'wav'` / `'ogg'` writes only the movie's SOUND (a `music` score as an audio file). `video: { codec: 'hevc' | 'av1' | 'vp9' | 'avc', bitrate: '8M', hardware, keyFrameInterval }` (a codec this browser cannot encode names the one that works). Part of it: `range: [2, 5]` (seconds) or `range: 'title'` (a top-level layer's span; picture and sound); `scale: 0.5` / `draft: true` (half size, low quality, no motion blur: a smaller file, not a faster drawing). Fast motion: `movie.init({ motionBlur: true })` (8 samples, 180° shutter; or a number / { samples: 2–64, shutter: 0–1 }) blurs render / snapshot / contactSheet, not live playback; a render takes `samples`× as long
|
|
171
171
|
movie.on('progress', e => e.progress /* 0–100 */);
|
|
172
172
|
movie.audioBuffer // mixed audio (after init), if any audio layers
|
|
173
173
|
await movie.contactSheet({ count: 6, as: 'dataURL' }) // ONE image of several labelled frames — look at it. Options: frames | times | count, columns, cellWidth, as
|
|
@@ -53,14 +53,14 @@ A good habit that catches most of these: after `init`, read `window.__logs` (see
|
|
|
53
53
|
30c. **Look with the built-in tools, not OS screenshots**: `await movie.contactSheet({ count: 6, as: 'dataURL' })` returns one labelled image of the whole animation; `await movie.inspect(frame)` returns every layer's canvas bounds and `issues`. If you do use a browser tool, `screenshot` needs an **absolute path**, macOS `sed -i` needs `''`, and a static server may redirect `/x.html` → `/x` and drop `?query`. Keep the template's `try/catch` around `init` so failures land in `window.__logs`. Check ≥ 5 timestamps including mid-transition. *(fixed: tools built in)*
|
|
54
54
|
31. **Check audio with `movie.inspectAudio()`**: every sound measured on its own (`sound.length`, `peakDb`, `loudestAt`, `brightnessHz`), when it plays, loudness and brightness per 0.1 s window, and `issues` (the mix was limited, a layer is inaudible, an sfx is cut off by the end). Decoding a render by hand is no longer needed: `render()` encodes the same mix, on time in mp4 / mov / webm / mkv. *(fixed)*
|
|
55
55
|
|
|
56
|
-
31b. **A mistyped `ease` now warns** (`ease: 'power3.outt'` → did you mean `power3.out`). It used to run silently as
|
|
56
|
+
31b. **A mistyped `ease` now warns** (`ease: 'power3.outt'` → did you mean `power3.out`). It used to run silently as the default ease (`power1.out`), so the motion changed and nothing said so.
|
|
57
57
|
31c. **`draft` and `scale` make the file smaller, not the drawing faster** (measured: a 48 s 1080p film exports in about the same time as a draft; the file is 7× smaller). To save time, export a part (`range` / `--range` / `--scene`), look at single moments (`check --no-export --at …`), and keep motion blur off while you iterate.
|
|
58
58
|
|
|
59
59
|
## Round 2 — found by building 16 portfolio pieces (examples/gallery)
|
|
60
60
|
|
|
61
61
|
32. **Seeking is safe now** *(fixed)*: a `set` keyframe (counter value, text fill, image tint, shape style) is undone when you seek back before it, and shapes are redrawn before they are culled, so a rect growing from `width: 0` at the left edge draws after any jump seek (contact sheet, snapshot, scrubbing, export). The old workarounds (`width: 0.01`, a one-frame `to` instead of `set`) are not needed.
|
|
62
62
|
33. **A mask starts and ends with the layer it masks** *(fixed)*, so its keyframes count from that layer's start. A mask with an explicit `at` keeps composition time. Before, a mask `at: 0` reveal had finished before a layer that appeared at 8 s, and the layer stayed invisible without any warning.
|
|
63
|
-
34. **`from` and `from+to` show their start value from the LAYER's start**, not from the keyframe's `at` (GSAP
|
|
63
|
+
34. **`from` and `from+to` show their start value from the LAYER's start**, not from the keyframe's `at` (as in GSAP and After Effects): a delayed fade-in `{ at: 2, from: { alpha: 0 }, to: { alpha: 1 } }` needs no `initial.alpha`. To keep the current value until `at` and then jump, use `set`. *(docs)*
|
|
64
64
|
35. **One `Movie` per page.** Creating a second Movie (for an isolated repro) and destroying it breaks the first (`null.clear`). Reload instead. *(docs)*
|
|
65
65
|
36. **Multi-line text is centre-aligned by default** (`style.align: 'left'` for left-aligned paragraphs). *(docs)*
|
|
66
66
|
37. **Composition size.** A 2D composition does not clip its children (a child outside `width × height` still draws), but its rectangle is the culling area: if the whole rectangle is off the canvas the group is skipped even when a child is on screen — keep it near what it draws, or canvas-sized. A `threeD` composition (a card) IS clipped at its `width × height` (it is rendered into a texture that size): size it for the soft shadow too. A growing `threeD` shape or text is fine: its texture is resized in place (it used to warn "destroyed while still bound"). *(measured; docs / fixed)*
|
|
@@ -100,7 +100,7 @@ A good habit that catches most of these: after `init`, read `window.__logs` (see
|
|
|
100
100
|
70. **`speed` is a setting, `time` is the property.** `speed: 2` on the layer; to change the speed over time animate `time` (a curve of seconds in the file), never `keyframes: [{ to: { speed: 3 } }]` (warns). `speed: 0` is not allowed (hold a moment with `time` to the same value twice); `speed` together with keyframed `time`: `time` wins. Other names (`playbackRate`, `timeScale`, `rate`, `reverse: true`, `timeRemap`) are warned about with what to write. `particles({ speed })` is something else: launch speed in px per second.
|
|
101
101
|
71. **Sound follows `speed` like a tape: the pitch moves with it.** An audio file or a video's sound at `speed: 2` is an octave up; backward plays reversed. Not for `sfx` / `music` layers (warns): change an sfx with `pitch` (semitones), or put the sfx **inside a remapped composition** and it follows that clock (slower and lower, or backward).
|
|
102
102
|
72. **A video at `speed: 2` with no `duration` is half as long; with a `duration` longer than the file it holds the last frame** (add `loop: true` to repeat). A negative `time` counts back from the end of the file (`initial: { time: -2 }` = two seconds before the end).
|
|
103
|
-
73. **Know the shape of the curve before you time a beat to it.** `cubic-bezier(.2, .8, .2, 1)` does half of its change in the first 13 % of the time and 99 % by 74 % (not slow motion: over 6 s the first 2 s of content pass in 0.6 s); `.2, .8, .5, 1` has a shorter tail.
|
|
103
|
+
73. **Know the shape of the curve before you time a beat to it.** `cubic-bezier(.2, .8, .2, 1)` does half of its change in the first 13 % of the time and 99 % by 74 % (not slow motion: over 6 s the first 2 s of content pass in 0.6 s); `.2, .8, .5, 1` has a shorter tail. `power2` is a cubic (`power1` = t², `power3` = t⁴): `power2.in` has done 12.5 % at half time and 42 % at three quarters, so a rewind with it dismantles most of the title in the last third.
|
|
104
104
|
74. **`focus` as a layer name means that layer's FIRST `z`, and the name must be unique.** (Two layers with one name: the first `threeD` one is used and it warns.) The camera does not follow a layer whose `z` moves (it warns): to follow it, animate `focus` itself with numbers. A name no layer has, or a layer that is not `threeD`, warns and the `z = 0` plane is used (did-you-mean for typos).
|
|
105
105
|
75. **Depth of field blurs `threeD` layers only, and the layer's own depth decides it.** A 2D layer never blurs; a `threeD` layer at the focus depth stays sharp; the depth is measured along the camera's view direction, so with an orbiting or tilted camera a layer off to the side of the look-at point is a little deeper than its `z` says and blurs a little even at the focus `z`; one blur for the whole layer, so a tilted floor is one blur (split it into layers).
|
|
106
106
|
76. **`aperture` is the lens diameter in px, not a 0–1 strength.** 30 is the default (clearly shallow), 0 turns it off, 60 is strong, 100 is extreme (small bright objects speckle); the blur never exceeds 32 px. 20 or more blurred layers at once warns (one filter pass each, more on WebGPU).
|
|
@@ -95,7 +95,7 @@ return [
|
|
|
95
95
|
|
|
96
96
|
## Rewind and play again: a composition whose own time you control
|
|
97
97
|
|
|
98
|
-
Put the part you want to rewind, slow down or freeze **in a composition**, then give that composition `speed` (one straight speed: `0.5` slow motion, `-1` backward) or `time` keyframes (any curve of its own time: play, hold, rewind, play slowly). The layers inside write `at` / `duration` / `keyframes` in the composition's own seconds; the composition's own `at` / `duration` stay in the outer time. A sound or `sfx` inside follows the clock (backward and lower when it rewinds, like a tape). `cubic-bezier(.2, .8, .2, 1)` is front-loaded, not slow motion: half of the replay passes in the first 13 % of its time and 99 % by 74 %, so the replay opens quickly and what sits at the END of the content (the ring, the settle) gets the long glide.
|
|
98
|
+
Put the part you want to rewind, slow down or freeze **in a composition**, then give that composition `speed` (one straight speed: `0.5` slow motion, `-1` backward) or `time` keyframes (any curve of its own time: play, hold, rewind, play slowly). The layers inside write `at` / `duration` / `keyframes` in the composition's own seconds; the composition's own `at` / `duration` stay in the outer time. A sound or `sfx` inside follows the clock (backward and lower when it rewinds, like a tape). `cubic-bezier(.2, .8, .2, 1)` is front-loaded, not slow motion: half of the replay passes in the first 13 % of its time and 99 % by 74 %, so the replay opens quickly and what sits at the END of the content (the ring, the settle) gets the long glide. `power2` is a cubic: `power2.in` has done only 12.5 % at half time, so the dismantling bunches up in the last third of the rewind.
|
|
99
99
|
|
|
100
100
|
```js
|
|
101
101
|
// @recipe rewind-and-replay
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<title>pixi-effects video</title>
|
|
7
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
7
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pixi-effects@0.27.0/dist/loader.css" /> <!-- the loader: HTML + CSS only, so it is on screen at once and keeps moving while the page builds the video -->
|
|
8
8
|
<style>
|
|
9
9
|
body { background: #0d1220; margin: 0; padding: 16px; }
|
|
10
10
|
.stage { position: relative; width: min(960px, 100%); } /* the player and the loader both sit inside this box */
|
|
@@ -36,19 +36,17 @@
|
|
|
36
36
|
{
|
|
37
37
|
"imports": {
|
|
38
38
|
"pixi.js": "https://esm.sh/pixi.js@8.22.0?bundle-deps",
|
|
39
|
-
"gsap": "https://esm.sh/gsap@3.12.5",
|
|
40
|
-
"gsap/PixiPlugin": "https://esm.sh/gsap@3.12.5/PixiPlugin",
|
|
41
39
|
"mediabunny": "https://esm.sh/mediabunny",
|
|
42
|
-
"pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
43
|
-
"pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
44
|
-
"pixi-effects/presenter": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
45
|
-
"pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
40
|
+
"pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.27.0/dist/index.js",
|
|
41
|
+
"pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.27.0/dist/Controller.js",
|
|
42
|
+
"pixi-effects/presenter": "https://cdn.jsdelivr.net/npm/pixi-effects@0.27.0/dist/Presenter.js",
|
|
43
|
+
"pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.27.0/dist/three.js",
|
|
46
44
|
"pixi-filters": "https://esm.sh/pixi-filters@6.1.5?external=pixi.js",
|
|
47
45
|
"three": "https://esm.sh/three@0.178.0"
|
|
48
46
|
}
|
|
49
47
|
}
|
|
50
48
|
</script>
|
|
51
|
-
<link rel="modulepreload" href="https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
49
|
+
<link rel="modulepreload" href="https://cdn.jsdelivr.net/npm/pixi-effects@0.27.0/dist/index.js" /> <!-- after the import map: the library downloads while the page is parsed -->
|
|
52
50
|
|
|
53
51
|
<script type="module">
|
|
54
52
|
import { Movie, kenBurns, withFade } from 'pixi-effects';
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export { CompositionSequence } from './chunk-GBUM7X2S.js';
|
|
2
|
-
import './chunk-X3HB4DSR.js';
|
|
3
|
-
import './chunk-AXZUKNXF.js';
|
|
4
|
-
import './chunk-TLYTYBWX.js';
|
|
5
|
-
import './chunk-BXOCEZ4U.js';
|
|
6
|
-
//# sourceMappingURL=Composition-H4QA24MV.js.map
|
|
7
|
-
//# sourceMappingURL=Composition-H4QA24MV.js.map
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var chunk555URQHQ_cjs = require('./chunk-555URQHQ.cjs');
|
|
4
|
-
require('./chunk-WPXDRSGN.cjs');
|
|
5
|
-
require('./chunk-3DLYAPK7.cjs');
|
|
6
|
-
require('./chunk-ECA2WNRX.cjs');
|
|
7
|
-
require('./chunk-TWZNOUM7.cjs');
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
Object.defineProperty(exports, "CompositionSequence", {
|
|
12
|
-
enumerable: true,
|
|
13
|
-
get: function () { return chunk555URQHQ_cjs.CompositionSequence; }
|
|
14
|
-
});
|
|
15
|
-
//# sourceMappingURL=Composition-HM4YT5L7.cjs.map
|
|
16
|
-
//# sourceMappingURL=Composition-HM4YT5L7.cjs.map
|