pixi-effects 0.16.1 → 0.16.2
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 +23 -0
- package/README.md +11 -9
- package/ai/SKILL.md +1 -1
- package/ai/chat-template.html +3 -3
- package/ai/reference/cheatsheet.md +7 -3
- package/ai/reference/pitfalls.md +1 -0
- package/ai/reference/recipes.md +23 -0
- package/ai/template.html +5 -5
- package/dist/{Base-qvqNxvyt.d.ts → Base-BQUodYZK.d.ts} +1 -1
- package/dist/{Base-DPEqyeB0.d.cts → Base-OHqp8qvX.d.cts} +1 -1
- package/dist/Composition-3WWNVRR5.cjs +14 -0
- package/dist/{Composition-ZZGSMQ4E.cjs.map → Composition-3WWNVRR5.cjs.map} +1 -1
- package/dist/Composition-VJWY2NDL.js +5 -0
- package/dist/{Composition-X2VZSVPD.js.map → Composition-VJWY2NDL.js.map} +1 -1
- package/dist/Controller.d.cts +2 -2
- package/dist/Controller.d.ts +2 -2
- package/dist/{Movie-BIGDIla0.d.ts → Movie-DilBC348.d.ts} +8 -2
- package/dist/{Movie-CJMfAizW.d.cts → Movie-oA0SUAyb.d.cts} +8 -2
- package/dist/Presenter.d.cts +2 -2
- package/dist/Presenter.d.ts +2 -2
- package/dist/{chunk-V54NBCWG.cjs → chunk-2ROVCPSL.cjs} +28 -27
- package/dist/chunk-2ROVCPSL.cjs.map +1 -0
- package/dist/{chunk-SPPNTV7E.cjs → chunk-FJA5VUOF.cjs} +30 -13
- package/dist/chunk-FJA5VUOF.cjs.map +1 -0
- package/dist/{chunk-I2YXLHIU.js → chunk-XTIHKODH.js} +5 -4
- package/dist/chunk-XTIHKODH.js.map +1 -0
- package/dist/{chunk-7D7RAQWM.js → chunk-ZNKQH356.js} +30 -13
- package/dist/chunk-ZNKQH356.js.map +1 -0
- package/dist/index.cjs +128 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +37 -5
- package/dist/index.d.ts +37 -5
- package/dist/index.js +106 -17
- package/dist/index.js.map +1 -1
- package/dist/loader.css +1 -1
- package/dist/three.cjs +5 -5
- package/dist/three.d.cts +2 -2
- package/dist/three.d.ts +2 -2
- package/dist/three.js +1 -1
- package/dist/{types-C8XEUoqe.d.cts → types-SS51lP6O.d.cts} +7 -1
- package/dist/{types-C8XEUoqe.d.ts → types-SS51lP6O.d.ts} +7 -1
- package/docs/dsl.md +31 -1
- package/llms-full.txt +63 -5
- package/package.json +2 -1
- package/dist/Composition-X2VZSVPD.js +0 -5
- package/dist/Composition-ZZGSMQ4E.cjs +0 -14
- package/dist/chunk-7D7RAQWM.js.map +0 -1
- package/dist/chunk-I2YXLHIU.js.map +0 -1
- package/dist/chunk-SPPNTV7E.cjs.map +0 -1
- package/dist/chunk-V54NBCWG.cjs.map +0 -1
package/dist/loader.css
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* pixi-effects loader: one line of HTML on the page, a few lines of CSS, no JavaScript of your own.
|
|
3
3
|
*
|
|
4
|
-
* <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pixi-effects@0.16.
|
|
4
|
+
* <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pixi-effects@0.16.2/dist/loader.css">
|
|
5
5
|
* <div class="stage" style="position: relative"> <!-- any box the canvas sits in, with position: relative -->
|
|
6
6
|
* <canvas id="stage" width="1280" height="720"></canvas>
|
|
7
7
|
* <div class="pe-loader" data-label="LOADING"></div> <!-- or class="pe-loader pe-loader--bar" -->
|
package/dist/three.cjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkFJA5VUOF_cjs = require('./chunk-FJA5VUOF.cjs');
|
|
4
4
|
require('./chunk-UQBGW5DO.cjs');
|
|
5
5
|
var pixi_js = require('pixi.js');
|
|
6
6
|
var three$1 = require('three');
|
|
7
7
|
|
|
8
|
-
var ThreeSequence = class extends
|
|
8
|
+
var ThreeSequence = class extends chunkFJA5VUOF_cjs.Sequence {
|
|
9
9
|
_renderer = null;
|
|
10
10
|
_scene = null;
|
|
11
11
|
_camera = null;
|
|
@@ -24,7 +24,7 @@ var ThreeSequence = class extends chunkSPPNTV7E_cjs.Sequence {
|
|
|
24
24
|
if (this.duration === void 0) {
|
|
25
25
|
this.duration = this.parent?.duration ?? this.root.duration;
|
|
26
26
|
}
|
|
27
|
-
const scope =
|
|
27
|
+
const scope = chunkFJA5VUOF_cjs.buildScope(this, this.parent, this.root);
|
|
28
28
|
const width = resolveDim(spec.width, scope) ?? this.parent?.width ?? this.root.width;
|
|
29
29
|
const height = resolveDim(spec.height, scope) ?? this.parent?.height ?? this.root.height;
|
|
30
30
|
const resolution = spec.resolution ?? 1;
|
|
@@ -144,12 +144,12 @@ var ThreeSequence = class extends chunkSPPNTV7E_cjs.Sequence {
|
|
|
144
144
|
function resolveDim(v, scope) {
|
|
145
145
|
if (v === void 0) return void 0;
|
|
146
146
|
if (typeof v === "number") return v;
|
|
147
|
-
return
|
|
147
|
+
return chunkFJA5VUOF_cjs.evaluateExpr(v, scope);
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
// src/three/index.ts
|
|
151
151
|
function registerThree() {
|
|
152
|
-
|
|
152
|
+
chunkFJA5VUOF_cjs.registerSequenceType("three", ThreeSequence);
|
|
153
153
|
}
|
|
154
154
|
function three(spec) {
|
|
155
155
|
return spec;
|
package/dist/three.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { d as SequenceCommon, y as PropValue, S as SequenceSpec } from './types-
|
|
1
|
+
import { d as SequenceCommon, y as PropValue, S as SequenceSpec } from './types-SS51lP6O.cjs';
|
|
2
2
|
import { Scene, Camera, WebGLRenderer } from 'three';
|
|
3
|
-
import { S as Sequence, P as PathRouters } from './Base-
|
|
3
|
+
import { S as Sequence, P as PathRouters } from './Base-OHqp8qvX.cjs';
|
|
4
4
|
import 'pixi.js';
|
|
5
5
|
import 'gsap';
|
|
6
6
|
|
package/dist/three.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { d as SequenceCommon, y as PropValue, S as SequenceSpec } from './types-
|
|
1
|
+
import { d as SequenceCommon, y as PropValue, S as SequenceSpec } from './types-SS51lP6O.js';
|
|
2
2
|
import { Scene, Camera, WebGLRenderer } from 'three';
|
|
3
|
-
import { S as Sequence, P as PathRouters } from './Base-
|
|
3
|
+
import { S as Sequence, P as PathRouters } from './Base-BQUodYZK.js';
|
|
4
4
|
import 'pixi.js';
|
|
5
5
|
import 'gsap';
|
|
6
6
|
|
package/dist/three.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Sequence, buildScope, evaluateExpr, registerSequenceType } from './chunk-
|
|
1
|
+
import { Sequence, buildScope, evaluateExpr, registerSequenceType } from './chunk-ZNKQH356.js';
|
|
2
2
|
import './chunk-DKGG7WD4.js';
|
|
3
3
|
import { Texture, Sprite } from 'pixi.js';
|
|
4
4
|
import { WebGLRenderer, Scene, PerspectiveCamera } from 'three';
|
|
@@ -255,6 +255,8 @@ interface SequenceCommon {
|
|
|
255
255
|
* camera, a threeD layer renders exactly like a 2D one.
|
|
256
256
|
*/
|
|
257
257
|
threeD?: boolean;
|
|
258
|
+
/** A threeD layer the camera passes (a fly-through): hide it quietly when it is at or behind the camera instead of warning. Default false. */
|
|
259
|
+
hideBehindCamera?: boolean;
|
|
258
260
|
}
|
|
259
261
|
interface VideoSequenceSpec extends SequenceCommon {
|
|
260
262
|
type: 'video';
|
|
@@ -453,7 +455,9 @@ interface CompositionSequenceSpec extends SequenceCommon {
|
|
|
453
455
|
}
|
|
454
456
|
/**
|
|
455
457
|
* A camera layer. Put its props in `initial` / `keyframes`:
|
|
456
|
-
* `x`, `y`, `z`, `lookAtX`, `lookAtY`, `lookAtZ`, `fov` (vertical degrees, default 40)
|
|
458
|
+
* `x`, `y`, `z`, `lookAtX`, `lookAtY`, `lookAtZ`, `fov` (vertical degrees, default 40), and `offsetX` / `offsetY` / `offsetZ` /
|
|
459
|
+
* `lookOffsetX` / `lookOffsetY` / `lookOffsetZ` (default 0): added to the position and to the look-at point, so a shake (`wiggle`) on them
|
|
460
|
+
* never collides with the camera's own moves.
|
|
457
461
|
* Unset `z` follows `fov` so the z = 0 plane stays 1:1. Affects only `threeD`
|
|
458
462
|
* siblings in the same composition. +z points toward the viewer.
|
|
459
463
|
*/
|
|
@@ -493,6 +497,8 @@ interface Trimmable {
|
|
|
493
497
|
trimStart?: PropValue;
|
|
494
498
|
/** Where the stroke ends, 0–1 of the outline. Default 1. Animatable. */
|
|
495
499
|
trimEnd?: PropValue;
|
|
500
|
+
/** A path with several sub-paths: `true` trims each sub-path on its own, so they all draw on at the same time (default: the trim walks them in order, as one outline). */
|
|
501
|
+
trimEach?: boolean;
|
|
496
502
|
}
|
|
497
503
|
interface ShapeBase extends SequenceCommon {
|
|
498
504
|
type: 'shape';
|
|
@@ -255,6 +255,8 @@ interface SequenceCommon {
|
|
|
255
255
|
* camera, a threeD layer renders exactly like a 2D one.
|
|
256
256
|
*/
|
|
257
257
|
threeD?: boolean;
|
|
258
|
+
/** A threeD layer the camera passes (a fly-through): hide it quietly when it is at or behind the camera instead of warning. Default false. */
|
|
259
|
+
hideBehindCamera?: boolean;
|
|
258
260
|
}
|
|
259
261
|
interface VideoSequenceSpec extends SequenceCommon {
|
|
260
262
|
type: 'video';
|
|
@@ -453,7 +455,9 @@ interface CompositionSequenceSpec extends SequenceCommon {
|
|
|
453
455
|
}
|
|
454
456
|
/**
|
|
455
457
|
* A camera layer. Put its props in `initial` / `keyframes`:
|
|
456
|
-
* `x`, `y`, `z`, `lookAtX`, `lookAtY`, `lookAtZ`, `fov` (vertical degrees, default 40)
|
|
458
|
+
* `x`, `y`, `z`, `lookAtX`, `lookAtY`, `lookAtZ`, `fov` (vertical degrees, default 40), and `offsetX` / `offsetY` / `offsetZ` /
|
|
459
|
+
* `lookOffsetX` / `lookOffsetY` / `lookOffsetZ` (default 0): added to the position and to the look-at point, so a shake (`wiggle`) on them
|
|
460
|
+
* never collides with the camera's own moves.
|
|
457
461
|
* Unset `z` follows `fov` so the z = 0 plane stays 1:1. Affects only `threeD`
|
|
458
462
|
* siblings in the same composition. +z points toward the viewer.
|
|
459
463
|
*/
|
|
@@ -493,6 +497,8 @@ interface Trimmable {
|
|
|
493
497
|
trimStart?: PropValue;
|
|
494
498
|
/** Where the stroke ends, 0–1 of the outline. Default 1. Animatable. */
|
|
495
499
|
trimEnd?: PropValue;
|
|
500
|
+
/** A path with several sub-paths: `true` trims each sub-path on its own, so they all draw on at the same time (default: the trim walks them in order, as one outline). */
|
|
501
|
+
trimEach?: boolean;
|
|
496
502
|
}
|
|
497
503
|
interface ShapeBase extends SequenceCommon {
|
|
498
504
|
type: 'shape';
|
package/docs/dsl.md
CHANGED
|
@@ -335,7 +335,7 @@ Every primitive draws centred on its local origin (so `anchorX`/`anchorY` and `p
|
|
|
335
335
|
keyframes: [{ at: 0, to: { endAngle: 270 }, duration: 2, ease: 'power3.out' }] } // a progress ring filling clockwise from the top
|
|
336
336
|
```
|
|
337
337
|
|
|
338
|
-
**Draw-on (`trimStart` / `trimEnd`).** On every shape except `arc`, `trimStart` and `trimEnd` (0–1, default 0 and 1, both animatable) are the part of the outline that is stroked, as fractions of its length. Animate `trimEnd` from 0 to 1 to draw a line, border or SVG path on; animate `trimStart` after it to wipe it off the same way; animate both for a travelling dash. A rect's outline starts at its top-left and goes clockwise, a circle / ellipse's at 12 o'clock and goes clockwise, a polygon's includes its closing edge, and a path's several sub-paths count as one length in order. **The fill is not trimmed** (the whole shape stays filled): fade `fillAlpha` in after the draw-on. Use `strokeCap: 'round'` for a friendly end. For `arc`, animate `endAngle` instead (a `trim…` on an arc warns).
|
|
338
|
+
**Draw-on (`trimStart` / `trimEnd`).** On every shape except `arc`, `trimStart` and `trimEnd` (0–1, default 0 and 1, both animatable) are the part of the outline that is stroked, as fractions of its length. Animate `trimEnd` from 0 to 1 to draw a line, border or SVG path on; animate `trimStart` after it to wipe it off the same way; animate both for a travelling dash. A rect's outline starts at its top-left and goes clockwise, a circle / ellipse's at 12 o'clock and goes clockwise, a polygon's includes its closing edge, and a path's several sub-paths count as one length in order (add **`trimEach: true`** to trim every sub-path on its own, so they all draw on at the same time: a glyph or a logo made of several strokes). **The fill is not trimmed** (the whole shape stays filled): fade `fillAlpha` in after the draw-on. Use `strokeCap: 'round'` for a friendly end. For `arc`, animate `endAngle` instead (a `trim…` on an arc warns).
|
|
339
339
|
|
|
340
340
|
```ts
|
|
341
341
|
{ type: 'shape', shape: 'path', d: 'M 0 50 L 45 95 L 130 0', trimEnd: 0, strokeCap: 'round', strokeJoin: 'round',
|
|
@@ -493,6 +493,9 @@ The camera is a layer: `{ "type": "camera" }`. It has no visuals. With nothing s
|
|
|
493
493
|
| `z` | camera depth. **Auto:** if you never set it, it follows `fov` so the `z = 0` plane stays 1:1 | `(H/2) / tan(fov/2)` |
|
|
494
494
|
| `lookAtX`, `lookAtY`, `lookAtZ` | the point it looks at | `W/2`, `H/2`, `0` |
|
|
495
495
|
| `fov` | vertical field of view, degrees (clamped to 1–179) | 40 |
|
|
496
|
+
| `offsetX`, `offsetY`, `offsetZ`, `lookOffsetX`, `lookOffsetY`, `lookOffsetZ` | **added** to the position and to the look-at point, on top of the camera's own move. Put a handheld shake (`wiggle()`) here and it never collides with a dolly or an orbit (two tweens on the same prop overwrite each other; these are separate props) | 0 |
|
|
497
|
+
|
|
498
|
+
A flight through the scene is [`cameraPath()`](#camerapath) (a route as keyframes); a threeD layer the camera passes on purpose takes `hideBehindCamera: true` (hidden quietly when it is at or behind the camera, instead of a warning).
|
|
496
499
|
|
|
497
500
|
```json
|
|
498
501
|
{
|
|
@@ -1259,6 +1262,33 @@ import { particles } from 'pixi-effects';
|
|
|
1259
1262
|
|
|
1260
1263
|
It throws, with the fix, when it would bake an absurd number of keyframes: lower `count`, `life` or `sampleRate`. Particles are real layers (names like `burst1-0`), so `inspect` and the timeline list them, grouped as one family.
|
|
1261
1264
|
|
|
1265
|
+
### `cameraPath`
|
|
1266
|
+
|
|
1267
|
+
A camera flight as keyframes for `x`, `y`, `z`, `lookAtX`, `lookAtY` and `lookAtZ`: a route through `[x, y, z]` points, walked by arc length (an even speed whatever the curve) and sampled once per frame.
|
|
1268
|
+
|
|
1269
|
+
```ts
|
|
1270
|
+
import { cameraPath, wiggle } from 'pixi-effects';
|
|
1271
|
+
|
|
1272
|
+
{ type: 'camera', keyframes: [
|
|
1273
|
+
...cameraPath({ points: [[640, 360, 2200], [690, 330, 900], [640, 360, -3000]], duration: 7, ease: 'power2.in', frameRate: 30 }),
|
|
1274
|
+
...wiggle({ duration: 7, freq: 9, props: { offsetX: { around: 0, amp: 5 }, offsetY: { around: 0, amp: 3 } } }), // a handheld shake on the offsets
|
|
1275
|
+
] }
|
|
1276
|
+
```
|
|
1277
|
+
|
|
1278
|
+
| Option | Notes |
|
|
1279
|
+
| ------ | ----- |
|
|
1280
|
+
| `points` | `[[x, y, z], …]`, at least two, the route in order |
|
|
1281
|
+
| `duration` | seconds; required |
|
|
1282
|
+
| `at` | start, seconds from the start of the camera layer (default 0) |
|
|
1283
|
+
| `ease` | GSAP ease for the progress along the route (default `'none'`: constant speed; `'power2.in'` an accelerating rush) |
|
|
1284
|
+
| `look` | `'ahead'` (default: face where it flies) or a fixed `[x, y, z]` |
|
|
1285
|
+
| `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 |
|
|
1286
|
+
| `smooth` | `true` (default): a smooth curve through the points (Catmull-Rom); `false`: straight lines |
|
|
1287
|
+
| `from` / `to` | fractions 0–1 of the route to start and stop at (default 0 → 1; `from: 1, to: 0` flies it backwards) |
|
|
1288
|
+
| `frameRate` | samples per second: your movie's `frameRate` (default 30) |
|
|
1289
|
+
|
|
1290
|
+
Layers the camera passes take `hideBehindCamera: true`. The recipe `camera-fly-through` in `ai/reference/recipes.md` is a whole corridor.
|
|
1291
|
+
|
|
1262
1292
|
### `followPath`
|
|
1263
1293
|
|
|
1264
1294
|
A layer travelling along an SVG path, as keyframes for `x` and `y` (and `rotation`). The layer's own `x` / `y` are the points of the path (give shapes their centre there, and text `anchorX: 0.5, anchorY: 0.5`). The path is walked by arc length, so the speed is even whatever the curve, and sampled once per frame as short straight steps: set `frameRate` to the movie's and the layer is exactly on the path at every frame.
|
package/llms-full.txt
CHANGED
|
@@ -36,7 +36,7 @@ You describe a video as a tree of plain objects; the library plays it in a canva
|
|
|
36
36
|
- **Masks live in the parent's coordinates and don't follow the masked layer.** **`filterArea` is in the layer's own coordinates.**
|
|
37
37
|
- **2.5D needs `threeD: true`** on each layer plus a `{ type: 'camera' }` layer; camera props go in `initial`/keyframes; keep every `z` below the camera distance (≈ 989 at 720p, fov 40); under a dolly zoom only `z = 0` stays fixed.
|
|
38
38
|
- **Group / parent layers = `{ type: 'null', name: 'rig' }`** (draws nothing, only moves) + `parent: 'rig'` on the layers that should follow it: their `x`/`y` are then measured from the null's origin, and the null's position / rotation / scale / alpha carry them (orbits, clock hands, grouped fades). Nulls chain. Only `null` layers can be parents; not for `threeD` layers.
|
|
39
|
-
- **Built in:** `animateText()` (per-letter / per-word / per-line entrances and exits in one call: `...animateText('Title', style, { x, y, align: 'center', at: 0.5, duration: 5, in: 'rise', out: 'fade', stagger: { each: 0.05 } })`), `splitText()` / `measureText()` (per-letter / per-word layers, exact positions, by hand), `shape: 'arc'` (progress rings, pie / donut slices: animate `endAngle`), `fillGradient` (linear/radial, alpha stops → vignettes), `{value}` counters, `repeat`/`yoyo`, `orbit()`, seeded randomness (`rand(seed)` / `noise(x, seed)` in expressions, `random(seed)` in JS, never `Math.random()`) `wiggle()` (a seeded shake as keyframes) `music` (a tune with no file: `{ type: 'audio', music: { bpm, tracks: [{ inst: 'keys', notes: 'Cmaj7:4 Am7:4' }, { inst: 'bass', notes: 'c2:2 g1:2' }], drums: { from: 8, kick: 'x...x...' } } }`: notes and chords as text, counted in beats; instruments `keys pluck pad bass sub lead bell musicbox`; `musicEnvelope(music)` feeds `react()`), `audioEnvelope()` / `bpmEnvelope()` + `react()` (visuals that follow music: `const env = await audioEnvelope('music.mp3', { frameRate })` before `movie.init`, then `keyframes: react(env, { duration, props: { scale: { base: 1, amount: 0.4, band: 'bass', release: 0.2 } } })`; `beats: true` pulses on the beats; `bpmEnvelope(120, …)` when there is no file), `deck()` + `Presenter` (a talk as one movie: pages, steps, stops that pause playback, arrow keys / click / swipe to move on: `...deck({ transition: { kind: 'slide', duration: 0.6 }, pages: [{ name, duration, stops, sequences }] })`, `new Presenter(movie, { canvas })` from `pixi-effects/presenter`; G = page overview, P = presenter view with notes and a timer; `pixi-effects-render talk.html -o talk.pdf` = a PDF of the pages), `particles()` (bursts, snow, confetti, sparks as seeded layers: `...particles({ count: 90, at: 2, life: [1, 1.8], area: { x: 640, y: 330 }, speed: [120, 420], gravity: 240, fade: { out: 0.6 } })`), `followPath()` (a layer along an SVG path: `keyframes: followPath({ d, duration, orient: true })`), path morphing (`{ shape: 'path', d, morphTo, initial/keyframes: morph 0 → 1 }`) and `stagger()` (delays for a wave of layers: `stagger(letters, { each: 0.05, from: 'center' })`, or `stagger(n, …)` for numbers; GSAP-style `each` / `amount` / `from` / `grid` / `ease`). **Not in the DSL (hand-roll with a JS loop):** other per-frame curves (waves, spirals: sample them into short linear keyframes), shared masks. (Draw-on strokes `trimEnd`, text that changes over time `set: { text }` / `visibleChars`, shake `wiggle()` and particles are built in.)
|
|
39
|
+
- **Built in:** `animateText()` (per-letter / per-word / per-line entrances and exits in one call: `...animateText('Title', style, { x, y, align: 'center', at: 0.5, duration: 5, in: 'rise', out: 'fade', stagger: { each: 0.05 } })`), `splitText()` / `measureText()` (per-letter / per-word layers, exact positions, by hand), `shape: 'arc'` (progress rings, pie / donut slices: animate `endAngle`), `fillGradient` (linear/radial, alpha stops → vignettes), `{value}` counters, `repeat`/`yoyo`, `orbit()`, seeded randomness (`rand(seed)` / `noise(x, seed)` in expressions, `random(seed)` in JS, never `Math.random()`) `wiggle()` (a seeded shake as keyframes; on a camera put it on `offsetX` / `offsetY` so it rides on the move) `cameraPath()` (a camera flight through `[x, y, z]` points as keyframes; layers it passes: `hideBehindCamera: true`) `music` (a tune with no file: `{ type: 'audio', music: { bpm, tracks: [{ inst: 'keys', notes: 'Cmaj7:4 Am7:4' }, { inst: 'bass', notes: 'c2:2 g1:2' }], drums: { from: 8, kick: 'x...x...' } } }`: notes and chords as text, counted in beats; instruments `keys pluck pad bass sub lead bell musicbox`; `musicEnvelope(music)` feeds `react()`), `audioEnvelope()` / `bpmEnvelope()` + `react()` (visuals that follow music: `const env = await audioEnvelope('music.mp3', { frameRate })` before `movie.init`, then `keyframes: react(env, { duration, props: { scale: { base: 1, amount: 0.4, band: 'bass', release: 0.2 } } })`; `beats: true` pulses on the beats; `bpmEnvelope(120, …)` when there is no file), `deck()` + `Presenter` (a talk as one movie: pages, steps, stops that pause playback, arrow keys / click / swipe to move on: `...deck({ transition: { kind: 'slide', duration: 0.6 }, pages: [{ name, duration, stops, sequences }] })`, `new Presenter(movie, { canvas })` from `pixi-effects/presenter`; G = page overview, P = presenter view with notes and a timer; `pixi-effects-render talk.html -o talk.pdf` = a PDF of the pages), `particles()` (bursts, snow, confetti, sparks as seeded layers: `...particles({ count: 90, at: 2, life: [1, 1.8], area: { x: 640, y: 330 }, speed: [120, 420], gravity: 240, fade: { out: 0.6 } })`), `followPath()` (a layer along an SVG path: `keyframes: followPath({ d, duration, orient: true })`), path morphing (`{ shape: 'path', d, morphTo, initial/keyframes: morph 0 → 1 }`) and `stagger()` (delays for a wave of layers: `stagger(letters, { each: 0.05, from: 'center' })`, or `stagger(n, …)` for numbers; GSAP-style `each` / `amount` / `from` / `grid` / `ease`). **Not in the DSL (hand-roll with a JS loop):** other per-frame curves (waves, spirals: sample them into short linear keyframes), shared masks. (Draw-on strokes `trimEnd`, text that changes over time `set: { text }` / `visibleChars`, shake `wiggle()` and particles are built in.)
|
|
40
40
|
- **Blend modes:** `blendMode: 'add' | 'screen' | 'multiply'` on any layer (glows, light leaks).
|
|
41
41
|
- **One `Movie` per page.** A sound effect needs no `duration` (it lasts as long as its clip). Seeking is safe: counters, colours and growing shapes are right after any jump.
|
|
42
42
|
|
|
@@ -130,7 +130,7 @@ Keyframe kinds: `set` (jump at `at`; undone when you seek back), `to` (animate t
|
|
|
130
130
|
| polygon | `points: [[x,y],…] open` — canvas coordinates like `line` |
|
|
131
131
|
| path | `d` (SVG path data) — canvas coordinates like `line`. **Morph:** `morphTo: 'M…Z'` + animate `morph` 0 → 1 (points half way between, closed outlines lined up so they do not twist; `morphPoints` 8–2048) |
|
|
132
132
|
|
|
133
|
-
**Draw-on:** every shape but `arc` has `trimStart` / `trimEnd` (0–1 of the outline, default 0 / 1, animatable): `trimEnd: 0` + keyframe `to: { trimEnd: 1 }` draws a line / border / path on; `trimStart` 0 → 1 after it wipes it off; both = a travelling dash. The fill is NOT trimmed. Rect outline starts top-left, circle / ellipse at 12 o'clock, both clockwise. Use `strokeCap: 'round'`.
|
|
133
|
+
**Draw-on:** every shape but `arc` has `trimStart` / `trimEnd` (0–1 of the outline, default 0 / 1, animatable): `trimEnd: 0` + keyframe `to: { trimEnd: 1 }` draws a line / border / path on; `trimStart` 0 → 1 after it wipes it off; both = a travelling dash. The fill is NOT trimmed. Rect outline starts top-left, circle / ellipse at 12 o'clock, both clockwise. A `path` with several sub-paths walks them in order as one length; `trimEach: true` trims each on its own (all draw on together). Use `strokeCap: 'round'`.
|
|
134
134
|
|
|
135
135
|
Style (`initial` / keyframes): `fillColor fillAlpha strokeColor strokeAlpha strokeWidth`; `colorSpace: 'oklab'|'oklch'` for clean colour tweens. rect/circle/ellipse are **centred on `x,y` by default** (`anchorX/anchorY` default 0.5): for a bar growing from its base use `anchorY: 1` (or `anchorX: 0` for left-to-right). **`fillGradient`** (top level or `initial`; instead of `fillColor`): `{ type?: 'linear'|'radial', stops: [[0, '#000'], [1, 'rgba(0,0,0,.6)']], angle? /* linear, deg, 90 = top→bottom */, center?, innerRadius?, radius? /* radial, 0–1 of bounds */ }` — stops may have alpha, so a radial transparent→dark is a vignette. Stroke ends and corners: `strokeCap: 'butt'|'round'|'square'`, `strokeJoin: 'miter'|'round'|'bevel'` (top level or in `initial`; not animated — `round` keeps sharp corners like an M from poking out).
|
|
136
136
|
|
|
@@ -142,6 +142,10 @@ Style (`initial` / keyframes): `fillColor fillAlpha strokeColor strokeAlpha stro
|
|
|
142
142
|
|
|
143
143
|
**three** (optional entry `pixi-effects/three`) — `three({ type:'three', width, height, setup(ctx) { …; return { objects: { knot } } }, update?, dispose? })`; drive with `'three.knot.rotation.y'` keyframes. Call `registerThree()` before `init`.
|
|
144
144
|
|
|
145
|
+
## Camera flight and a camera shake
|
|
146
|
+
|
|
147
|
+
`...cameraPath({ points: [[x, y, z], …], duration, ease, look: 'ahead' | [x, y, z], lookAhead: 0.08, smooth: true, from, to, frameRate })` → camera keyframes (`x y z lookAtX lookAtY lookAtZ`) along a smooth curve at even speed (`ease: 'power2.in'` accelerates). A handheld shake goes on the camera's **offsets** so it never collides with the move: `...wiggle({ duration, props: { offsetX: { around: 0, amp: 5 }, offsetY: {…} } })` (`offsetX/Y/Z`, `lookOffsetX/Y/Z` are added to the position and the look-at point). A threeD layer the camera passes on purpose: `hideBehindCamera: true` (hidden quietly behind the camera, no warning). Recipe `camera-fly-through`.
|
|
148
|
+
|
|
145
149
|
## 2.5D in one paragraph
|
|
146
150
|
|
|
147
151
|
Add `threeD: true` to any visual layer, then use `z`, `rotationX`, `rotationY` (centre rotation with `anchorX/Y: 0.5` or `pivotX/Y`). Add a `{ type: 'camera' }` layer for a view. A `threeD` layer at `z: 0` with the default camera looks identical to a 2D one. Consecutive `threeD` layers are drawn farthest-first (equal `z` keeps array order); non-`threeD` layers ignore the camera and keep array order. Layers at/behind the camera plane are hidden. Not supported on `threeD` layers: masks, transitions, `filterArea`.
|
|
@@ -171,7 +175,7 @@ transitions: [{ kind: 'crossfade' | 'wipe' | 'iris' | 'slide' | 'dip' | 'zoom' |
|
|
|
171
175
|
## Presets
|
|
172
176
|
|
|
173
177
|
```js
|
|
174
|
-
import { kenBurns, withFade, wiggle, stagger, followPath, particles, deck, audioEnvelope, bpmEnvelope, musicEnvelope, react, random } from 'pixi-effects';
|
|
178
|
+
import { kenBurns, withFade, wiggle, stagger, followPath, cameraPath, particles, deck, audioEnvelope, bpmEnvelope, musicEnvelope, react, random } from 'pixi-effects';
|
|
175
179
|
kenBurns({ asset, name, at, duration, motion: 'still'|'scale'|'rotation'|'position',
|
|
176
180
|
fit: 'cover'|'contain', ease, /* scale */ origin, zoom, direction, /* rotation */ angle, /* position */ from, to })
|
|
177
181
|
withFade(spec, { in: 0.5, out: 0.5 }) // alpha fade; `out` needs spec.duration
|
|
@@ -206,7 +210,7 @@ measureText('Hello', style) // → { width, height,
|
|
|
206
210
|
splitText('Hello world', style, { by: 'chars' | 'words' | 'lines', x, y, align: 'left' | 'center' | 'right' })
|
|
207
211
|
// → [{ text, x, y, width, index, line }] — one text layer per piece, same style, anchor top-left
|
|
208
212
|
```
|
|
209
|
-
Per-letter / per-word / per-line animation in one call: `...animateText(text, style, { x, y, align, by: 'chars'|'words'|'lines', at, duration /* required: until the last piece is gone */, in: 'rise'|'drop'|'fade'|'pop'|'zoom'|'slide'|'spin'|{ from, to, duration, ease }|false, out: <same, runs in the same wave>, stagger: { each, from, … }, idle: { y: 4, rotation: 1 }, styleFor: p => ({ fill }) })` → text layers `<name>-0…` (anchor centre; `x`/`y` in `from` are offsets) to spread into `sequences` (recipe `animate-text`). By hand: `splitText(...)` + one layer per piece (recipe `split-text`). A web font must be loaded before measuring.
|
|
213
|
+
Per-letter / per-word / per-line animation in one call: `...animateText(text, style, { x, y, align, by: 'chars'|'words'|'lines', at, duration /* required: until the last piece is gone */, in: 'rise'|'drop'|'fade'|'pop'|'zoom'|'slide'|'spin'|{ from, to, duration, ease }|false, out: <same, runs in the same wave>, stagger: { each, from, … }, idle: { y: 4, rotation: 1 }, styleFor: p => ({ fill }) })` → text layers `<name>-0…` (anchor centre; `x`/`y` in `from` are offsets) to spread into `sequences`. **`x` / `y` are pixel NUMBERS (an expression like `'GW/2'` throws: compute `W / 2`), and `y` is the TOP of the line box**: the visible text is centred `lineHeight / 2` below it, so an underline goes at `y + lineHeight`, not at `y` (recipe `animate-text`). By hand: `splitText(...)` + one layer per piece (recipe `split-text`). A web font must be loaded before measuring.
|
|
210
214
|
|
|
211
215
|
## Assets
|
|
212
216
|
|
|
@@ -321,6 +325,7 @@ A good habit that catches most of these: after `init`, read `window.__logs` (see
|
|
|
321
325
|
60. **Build-ups are layers and `vol` points, not louder notes.** A track's `vol` can be `[[0, 0], [8, 0.7]]` (a fade over 8 beats); the pad's `attack` can be 2–3 s; enter instruments with rests. Per-note `!` and `,` only accent and soften.
|
|
322
326
|
61. **Music is a bed: `volume` 0.5–0.8 under speech or sfx.** At `volume: 1` it peaks at −6 dBFS (an sfx peaks at −12). A layer shorter than the music cuts it with a fade; longer is silent after it unless `loop: true`. Do not also add an mp3 bed.
|
|
323
327
|
62. **Chord names are voiced close around octave 3**: use `[c3 g3 e4]` for an exact voicing or `Am7/e` for a bass note; `swing` only moves notes that start on an off-beat eighth (`.5`) or sixteenth (`.75`); the bell and music box ring for `ring` seconds whatever the written length (shorten `ring` for fast bell lines); the synth is a handy bed, not an orchestra: keep tracks to 3–6, vary the bars, and end on the tonic.
|
|
328
|
+
63. **`animateText()` / `splitText()`: `x` and `y` are pixel numbers, and `y` is the TOP of the line.** An expression such as `x: 'GW/2'` throws (the letters are measured once, at build time): write `x: W / 2`. The pieces are centred half a line below `y`, so a line drawn under the text goes at `y + lineHeight` (about 1.2 × `fontSize`), not at `y`; look at the contact sheet, the layout check cannot see an underline through a word. (`check` writes into `check-out/` of the current folder unless you pass `--out`.)
|
|
324
329
|
|
|
325
330
|
|
|
326
331
|
<!-- ===== ai/reference/recipes.md ===== -->
|
|
@@ -916,6 +921,29 @@ new Presenter(movie, { canvas }); // arrows / Space / Enter / click / swipe
|
|
|
916
921
|
|
|
917
922
|
---
|
|
918
923
|
|
|
924
|
+
## A camera flight through a corridor (`cameraPath`, `hideBehindCamera`, a handheld shake)
|
|
925
|
+
|
|
926
|
+
`cameraPath({ points, duration, ease })` is the camera's route as keyframes: a smooth curve through `[x, y, z]` points at an even speed (an `ease` shapes it; `'power2.in'` is an accelerating rush), facing where it flies (`look: 'ahead'`, the default) or at a fixed `look: [x, y, z]`. Layers the camera passes get `hideBehindCamera: true` (hidden quietly instead of a warning). A handheld shake goes on the camera's **offsets** (`offsetX`, `offsetY`, `offsetZ`, `lookOffsetX`…): they are added to the move, so a `wiggle()` never collides with the dolly.
|
|
927
|
+
|
|
928
|
+
```js
|
|
929
|
+
// @recipe camera-fly-through
|
|
930
|
+
const gate = (i, z) => ({
|
|
931
|
+
type: 'composition', name: 'gate-' + i, threeD: true, hideBehindCamera: true, width: 360, height: 480,
|
|
932
|
+
initial: { x: 640, y: 360, z, pivotX: 180, pivotY: 240 },
|
|
933
|
+
sequences: [{ type: 'shape', shape: 'rect', width: 330, height: 450, cornerRadius: 6, initial: { x: 180, y: 240, strokeColor: '#ffe9c4', strokeWidth: 6 } }],
|
|
934
|
+
});
|
|
935
|
+
return { duration: 8, sequences: [
|
|
936
|
+
{ type: 'shape', shape: 'rect', width: 'GW', height: 'GH', initial: { x: 'GW/2', y: 'GH/2', fillColor: '#0b0a10' } },
|
|
937
|
+
...Array.from({ length: 9 }, (_, i) => gate(i, -i * 380)),
|
|
938
|
+
{ type: 'camera', keyframes: [
|
|
939
|
+
...cameraPath({ points: [[640, 360, 2200], [690, 330, 900], [640, 360, -3000]], duration: 7, ease: 'power2.in', frameRate: 30 }),
|
|
940
|
+
...wiggle({ duration: 7, freq: 9, seed: 2, props: { offsetX: { around: 0, amp: 5 }, offsetY: { around: 0, amp: 3 } } }),
|
|
941
|
+
] },
|
|
942
|
+
] };
|
|
943
|
+
```
|
|
944
|
+
|
|
945
|
+
---
|
|
946
|
+
|
|
919
947
|
## Looping motion
|
|
920
948
|
|
|
921
949
|
`repeat` (a finite count) and `yoyo` go on any keyframe. Total time = `duration × (repeat + 1)`.
|
|
@@ -1405,7 +1433,7 @@ Every primitive draws centred on its local origin (so `anchorX`/`anchorY` and `p
|
|
|
1405
1433
|
keyframes: [{ at: 0, to: { endAngle: 270 }, duration: 2, ease: 'power3.out' }] } // a progress ring filling clockwise from the top
|
|
1406
1434
|
```
|
|
1407
1435
|
|
|
1408
|
-
**Draw-on (`trimStart` / `trimEnd`).** On every shape except `arc`, `trimStart` and `trimEnd` (0–1, default 0 and 1, both animatable) are the part of the outline that is stroked, as fractions of its length. Animate `trimEnd` from 0 to 1 to draw a line, border or SVG path on; animate `trimStart` after it to wipe it off the same way; animate both for a travelling dash. A rect's outline starts at its top-left and goes clockwise, a circle / ellipse's at 12 o'clock and goes clockwise, a polygon's includes its closing edge, and a path's several sub-paths count as one length in order. **The fill is not trimmed** (the whole shape stays filled): fade `fillAlpha` in after the draw-on. Use `strokeCap: 'round'` for a friendly end. For `arc`, animate `endAngle` instead (a `trim…` on an arc warns).
|
|
1436
|
+
**Draw-on (`trimStart` / `trimEnd`).** On every shape except `arc`, `trimStart` and `trimEnd` (0–1, default 0 and 1, both animatable) are the part of the outline that is stroked, as fractions of its length. Animate `trimEnd` from 0 to 1 to draw a line, border or SVG path on; animate `trimStart` after it to wipe it off the same way; animate both for a travelling dash. A rect's outline starts at its top-left and goes clockwise, a circle / ellipse's at 12 o'clock and goes clockwise, a polygon's includes its closing edge, and a path's several sub-paths count as one length in order (add **`trimEach: true`** to trim every sub-path on its own, so they all draw on at the same time: a glyph or a logo made of several strokes). **The fill is not trimmed** (the whole shape stays filled): fade `fillAlpha` in after the draw-on. Use `strokeCap: 'round'` for a friendly end. For `arc`, animate `endAngle` instead (a `trim…` on an arc warns).
|
|
1409
1437
|
|
|
1410
1438
|
```ts
|
|
1411
1439
|
{ type: 'shape', shape: 'path', d: 'M 0 50 L 45 95 L 130 0', trimEnd: 0, strokeCap: 'round', strokeJoin: 'round',
|
|
@@ -1563,6 +1591,9 @@ The camera is a layer: `{ "type": "camera" }`. It has no visuals. With nothing s
|
|
|
1563
1591
|
| `z` | camera depth. **Auto:** if you never set it, it follows `fov` so the `z = 0` plane stays 1:1 | `(H/2) / tan(fov/2)` |
|
|
1564
1592
|
| `lookAtX`, `lookAtY`, `lookAtZ` | the point it looks at | `W/2`, `H/2`, `0` |
|
|
1565
1593
|
| `fov` | vertical field of view, degrees (clamped to 1–179) | 40 |
|
|
1594
|
+
| `offsetX`, `offsetY`, `offsetZ`, `lookOffsetX`, `lookOffsetY`, `lookOffsetZ` | **added** to the position and to the look-at point, on top of the camera's own move. Put a handheld shake (`wiggle()`) here and it never collides with a dolly or an orbit (two tweens on the same prop overwrite each other; these are separate props) | 0 |
|
|
1595
|
+
|
|
1596
|
+
A flight through the scene is [`cameraPath()`](#camerapath) (a route as keyframes); a threeD layer the camera passes on purpose takes `hideBehindCamera: true` (hidden quietly when it is at or behind the camera, instead of a warning).
|
|
1566
1597
|
|
|
1567
1598
|
```json
|
|
1568
1599
|
{
|
|
@@ -2329,6 +2360,33 @@ import { particles } from 'pixi-effects';
|
|
|
2329
2360
|
|
|
2330
2361
|
It throws, with the fix, when it would bake an absurd number of keyframes: lower `count`, `life` or `sampleRate`. Particles are real layers (names like `burst1-0`), so `inspect` and the timeline list them, grouped as one family.
|
|
2331
2362
|
|
|
2363
|
+
### `cameraPath`
|
|
2364
|
+
|
|
2365
|
+
A camera flight as keyframes for `x`, `y`, `z`, `lookAtX`, `lookAtY` and `lookAtZ`: a route through `[x, y, z]` points, walked by arc length (an even speed whatever the curve) and sampled once per frame.
|
|
2366
|
+
|
|
2367
|
+
```ts
|
|
2368
|
+
import { cameraPath, wiggle } from 'pixi-effects';
|
|
2369
|
+
|
|
2370
|
+
{ type: 'camera', keyframes: [
|
|
2371
|
+
...cameraPath({ points: [[640, 360, 2200], [690, 330, 900], [640, 360, -3000]], duration: 7, ease: 'power2.in', frameRate: 30 }),
|
|
2372
|
+
...wiggle({ duration: 7, freq: 9, props: { offsetX: { around: 0, amp: 5 }, offsetY: { around: 0, amp: 3 } } }), // a handheld shake on the offsets
|
|
2373
|
+
] }
|
|
2374
|
+
```
|
|
2375
|
+
|
|
2376
|
+
| Option | Notes |
|
|
2377
|
+
| ------ | ----- |
|
|
2378
|
+
| `points` | `[[x, y, z], …]`, at least two, the route in order |
|
|
2379
|
+
| `duration` | seconds; required |
|
|
2380
|
+
| `at` | start, seconds from the start of the camera layer (default 0) |
|
|
2381
|
+
| `ease` | GSAP ease for the progress along the route (default `'none'`: constant speed; `'power2.in'` an accelerating rush) |
|
|
2382
|
+
| `look` | `'ahead'` (default: face where it flies) or a fixed `[x, y, z]` |
|
|
2383
|
+
| `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 |
|
|
2384
|
+
| `smooth` | `true` (default): a smooth curve through the points (Catmull-Rom); `false`: straight lines |
|
|
2385
|
+
| `from` / `to` | fractions 0–1 of the route to start and stop at (default 0 → 1; `from: 1, to: 0` flies it backwards) |
|
|
2386
|
+
| `frameRate` | samples per second: your movie's `frameRate` (default 30) |
|
|
2387
|
+
|
|
2388
|
+
Layers the camera passes take `hideBehindCamera: true`. The recipe `camera-fly-through` in `ai/reference/recipes.md` is a whole corridor.
|
|
2389
|
+
|
|
2332
2390
|
### `followPath`
|
|
2333
2391
|
|
|
2334
2392
|
A layer travelling along an SVG path, as keyframes for `x` and `y` (and `rotation`). The layer's own `x` / `y` are the points of the path (give shapes their centre there, and text `anchorX: 0.5, anchorY: 0.5`). The path is walked by arc length, so the speed is even whatever the curve, and sampled once per frame as short straight steps: set `frameRate` to the movie's and the layer is exactly on the path at every frame.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pixi-effects",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.2",
|
|
4
4
|
"description": "Declarative composition and video rendering for the web. Built on PixiJS v8.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"build:ai": "node scripts/build-llms.mjs",
|
|
50
50
|
"build:gallery": "node scripts/build-gallery.mjs",
|
|
51
51
|
"posters": "node scripts/make-posters.mjs",
|
|
52
|
+
"landing": "node scripts/preview-landing.mjs",
|
|
52
53
|
"guide": "node scripts/build-guide.mjs guide-preview",
|
|
53
54
|
"prepublishOnly": "npm run build:ai && npm run typecheck && npm test && npm run build"
|
|
54
55
|
},
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var chunkV54NBCWG_cjs = require('./chunk-V54NBCWG.cjs');
|
|
4
|
-
require('./chunk-SPPNTV7E.cjs');
|
|
5
|
-
require('./chunk-UQBGW5DO.cjs');
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Object.defineProperty(exports, "CompositionSequence", {
|
|
10
|
-
enumerable: true,
|
|
11
|
-
get: function () { return chunkV54NBCWG_cjs.CompositionSequence; }
|
|
12
|
-
});
|
|
13
|
-
//# sourceMappingURL=Composition-ZZGSMQ4E.cjs.map
|
|
14
|
-
//# sourceMappingURL=Composition-ZZGSMQ4E.cjs.map
|