pixi-effects 0.31.0 → 0.32.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 +18 -0
- package/README.md +5 -5
- package/ai/chat-template.html +3 -3
- package/ai/tools/render.mjs +19 -2
- package/dist/{Base-jRiMHcWT.d.ts → Base-CXsMdmJB.d.ts} +1 -1
- package/dist/{Base-BKagtWc0.d.cts → Base-pNq6sVGl.d.cts} +1 -1
- package/dist/Composition-LNMKWXSL.cjs +16 -0
- package/dist/{Composition-V2XYSW6P.cjs.map → Composition-LNMKWXSL.cjs.map} +1 -1
- package/dist/Composition-RF7QOEXT.js +7 -0
- package/dist/{Composition-DQCPJPFE.js.map → Composition-RF7QOEXT.js.map} +1 -1
- package/dist/Controller.d.cts +2 -2
- package/dist/Controller.d.ts +2 -2
- package/dist/{Movie-BH81stkb.d.cts → Movie-CL6SsAWe.d.cts} +1 -1
- package/dist/{Movie-CfFPItoL.d.ts → Movie-f0tQG9y5.d.ts} +1 -1
- package/dist/Spans-NQGKVMR5.cjs +55 -0
- package/dist/Spans-NQGKVMR5.cjs.map +1 -0
- package/dist/Spans-SDXABOV6.js +53 -0
- package/dist/Spans-SDXABOV6.js.map +1 -0
- package/dist/{blendModes-RIVZXL5A.js → blendModes-MVRLG3T7.js} +4 -4
- package/dist/{blendModes-RIVZXL5A.js.map → blendModes-MVRLG3T7.js.map} +1 -1
- package/dist/{blendModes-ETMYWATF.cjs → blendModes-VTKDXWEN.cjs} +5 -5
- package/dist/{blendModes-ETMYWATF.cjs.map → blendModes-VTKDXWEN.cjs.map} +1 -1
- package/dist/{chunk-MMTESMJ5.js → chunk-AW5NPDE4.js} +1363 -1151
- package/dist/chunk-AW5NPDE4.js.map +1 -0
- package/dist/{chunk-RCVZX2JP.js → chunk-C5BNHVKK.js} +36 -3
- package/dist/chunk-C5BNHVKK.js.map +1 -0
- package/dist/{chunk-L73KRFXI.cjs → chunk-CRUDOKSD.cjs} +91 -91
- package/dist/chunk-CRUDOKSD.cjs.map +1 -0
- package/dist/{chunk-BGMCZA36.js → chunk-D5BFQVKL.js} +6 -6
- package/dist/chunk-D5BFQVKL.js.map +1 -0
- package/dist/{chunk-LYOKH7KE.cjs → chunk-FCGYJCVO.cjs} +36 -3
- package/dist/chunk-FCGYJCVO.cjs.map +1 -0
- package/dist/{chunk-UTIBYMIO.js → chunk-FCMOREIS.js} +10 -6
- package/dist/chunk-FCMOREIS.js.map +1 -0
- package/dist/{chunk-X3WHETQ3.cjs → chunk-K3MAEKFM.cjs} +42 -38
- package/dist/chunk-K3MAEKFM.cjs.map +1 -0
- package/dist/{chunk-4KB3GUEK.js → chunk-QFNS6BFL.js} +4 -4
- package/dist/{chunk-4KB3GUEK.js.map → chunk-QFNS6BFL.js.map} +1 -1
- package/dist/{chunk-DJ5B7IKT.cjs → chunk-QHDN6TF5.cjs} +1377 -1161
- package/dist/chunk-QHDN6TF5.cjs.map +1 -0
- package/dist/{chunk-2DXPTKQZ.cjs → chunk-Z437UWI4.cjs} +5 -5
- package/dist/{chunk-2DXPTKQZ.cjs.map → chunk-Z437UWI4.cjs.map} +1 -1
- package/dist/index.cjs +33 -33
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +5 -5
- package/dist/loader.css +1 -1
- package/dist/present.cjs +5 -5
- package/dist/present.d.cts +2 -2
- package/dist/present.d.ts +2 -2
- package/dist/present.js +5 -5
- package/dist/three.cjs +6 -6
- package/dist/three.d.cts +2 -2
- package/dist/three.d.ts +2 -2
- package/dist/three.js +2 -2
- package/dist/{types-CK_crNAI.d.ts → types-CavnUvd5.d.cts} +26 -1
- package/dist/{types-CK_crNAI.d.cts → types-CavnUvd5.d.ts} +26 -1
- package/docs/dsl.md +14 -4
- package/llms-full.txt +19 -8
- package/package.json +1 -1
- package/skills/pixi-effects/SKILL.md +1 -1
- package/skills/pixi-effects/reference/cheatsheet.md +2 -2
- package/skills/pixi-effects/reference/pitfalls.md +2 -2
- package/skills/pixi-effects/template.html +6 -6
- package/dist/Composition-DQCPJPFE.js +0 -7
- package/dist/Composition-V2XYSW6P.cjs +0 -16
- package/dist/chunk-BGMCZA36.js.map +0 -1
- package/dist/chunk-DJ5B7IKT.cjs.map +0 -1
- package/dist/chunk-L73KRFXI.cjs.map +0 -1
- package/dist/chunk-LYOKH7KE.cjs.map +0 -1
- package/dist/chunk-MMTESMJ5.js.map +0 -1
- package/dist/chunk-RCVZX2JP.js.map +0 -1
- package/dist/chunk-UTIBYMIO.js.map +0 -1
- package/dist/chunk-X3WHETQ3.cjs.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.32.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
|
|
|
@@ -159,7 +159,7 @@ Keyframe kinds: `set` (jump at `at`; undone when you seek back), `to` (animate t
|
|
|
159
159
|
|
|
160
160
|
## Layer types
|
|
161
161
|
|
|
162
|
-
**text** — `text`, `style` (any PixiJS TextStyle field: `fontSize fontFamily fontWeight fill letterSpacing lineHeight align wordWrap wordWrapWidth stroke dropShadow padding`; expressions OK for numbers), `colorSpace`. Default anchor is **top-left**; use `anchorX/anchorY: 0.5` to centre on `x,y`. A text layer has one animatable number, `value`, printed where the text contains `{value}` (`text: '{value} users'`, `initial: { value: 0 }`, keyframe `to: { value: 2480 }`; `format: { decimals, grouping, pad /* zero-pad the whole part: pad 2 → 05 */ }`) — that is how counters work. **Changing text:** a keyframe `set: { text: 'Two' }` swaps the string at that time (undone on seek back; `to` / `from` cannot tween a string); `visibleChars` (number, first N characters; starts at 0 once animated) is a typewriter: `to: { visibleChars: 30 }, duration: 2.4, ease: 'none'` — use `anchorX: 0` + `style.align: 'left'` so it grows rightward. Multi-line text is centre-aligned by default (`style.align: 'left'` to change).
|
|
162
|
+
**text** — `text`, `style` (any PixiJS TextStyle field: `fontSize fontFamily fontWeight fill letterSpacing lineHeight align wordWrap wordWrapWidth stroke dropShadow padding`; expressions OK for numbers), `colorSpace`. Default anchor is **top-left**; use `anchorX/anchorY: 0.5` to centre on `x,y`. A text layer has one animatable number, `value`, printed where the text contains `{value}` (`text: '{value} users'`, `initial: { value: 0 }`, keyframe `to: { value: 2480 }`; `format: { decimals, grouping, pad /* zero-pad the whole part: pad 2 → 05 */ }`) — that is how counters work. **Changing text:** a keyframe `set: { text: 'Two' }` swaps the string at that time (undone on seek back; `to` / `from` cannot tween a string); `visibleChars` (number, first N characters; starts at 0 once animated) is a typewriter: `to: { visibleChars: 30 }, duration: 2.4, ease: 'none'` — use `anchorX: 0` + `style.align: 'left'` so it grows rightward. Multi-line text is centre-aligned by default (`style.align: 'left'` to change). **Colour inside one line:** `spans: [{ text: 'type', fill: '#7fa07f' }, ': ', { text: "'a'", fill: '#ffd166', fontWeight: '700' }, ',\n next line']` instead of `text` (strings use the layer's `style`; a span changes only fill / fontWeight / fontStyle / fontVariant / letterSpacing / dropShadow, never a size, font or stroke (those are the layer's `style`); `\n` starts a line; `style.align: 'left'` for code; `visibleChars` types over all the spans; it becomes a group of text layers `<name>.0…`).
|
|
163
163
|
|
|
164
164
|
**image** — `asset`, `tint`, `colorSpace: 'rgb'|'oklab'|'oklch'`. Default anchor top-left; natural size = `w`,`h`.
|
|
165
165
|
|
|
@@ -264,7 +264,7 @@ measureText('Hello', style) // → { width, height,
|
|
|
264
264
|
splitText('Hello world', style, { by: 'chars' | 'words' | 'lines', x, y, align: 'left' | 'center' | 'right' })
|
|
265
265
|
// → [{ text, x, y, width, index, line }] — one text layer per piece, same style, anchor top-left
|
|
266
266
|
```
|
|
267
|
-
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, in the same wave; by default BACKWARDS FROM THE LAYER'S END (at + duration, so to be gone at time T set duration = T − at); `out: { preset: 'fade', at: 6 }` starts the exit at composition second 6 instead>, 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.
|
|
267
|
+
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, in the same wave (leave `out` out and the pieces stay until the layer ends); by default BACKWARDS FROM THE LAYER'S END (at + duration, so to be gone at time T set duration = T − at); `out: { preset: 'fade', at: 6 }` starts the exit at composition second 6 instead>, 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.
|
|
268
268
|
|
|
269
269
|
## Assets
|
|
270
270
|
|
|
@@ -421,7 +421,7 @@ A good habit that catches most of these: after `init`, read `window.__logs` (see
|
|
|
421
421
|
93. **A `threeD` layer's shadow budget is four casters per receiver, counted in layer order, not geometry.** Five letters as five `castsShadows` layers over one floor: the fifth casts nothing (the warning names it). Make the word ONE text layer (the light is per pixel, each letter is still lit on its own) or give only the four that matter `castsShadows`. Splitting the floor does not help: each half takes the first four.
|
|
422
422
|
94. **`resolution` below 1 softens crisp lines in a shader.** `fwidth` anti-aliased edges come out a little blurry after the upscale: keep `resolution: 1` for line art and use 0.5 only for soft fields (flowing colour, glow).
|
|
423
423
|
95. **A `color-dodge` leak over a dark picture is a muddy red disc.** Dodge divides each channel of near-black separately, red first. Cut the dodge to the bright subject with a matte (`mask: 'letters'`, a matte-only copy of the word) and put the warm haze in a separate `screen` radial.
|
|
424
|
-
96. **A filter option that is a point (`rgbSplit.red`, `glitch` offsets)
|
|
424
|
+
96. **A filter option that is a point (`rgbSplit.red`, `glitch` offsets) is tweened one leaf at a time** (`'filters.s.red.x'`; the whole point as a value switches, it does not tween), and a scalar too: `set: { 'filters.tear.seed': n }` steps `glitch.seed` (a `set` keyframe works on filter options; no warning, visible tearing). To switch between looks on one subject, put the subject in time-windowed `composition` copies, each carrying the filters of its look.
|
|
425
425
|
97. **A deck inside your own composition loses its `transitions` and `stops`** unless you take them across: `const d = deck({ width: 1920, height: 1080, pages })`, then `{ type: 'composition', sequences: d.composition.sequences, transitions: d.composition.transitions }`. Pass `width` / `height` yourself for a page that is not the movie's own size.
|
|
426
426
|
98. **A matte layer needs a fill.** A named matte (`mask: 'glass'`) that is a shape with no `fillColor` / `fillGradient` (or a text with no fill) cuts everything away and nothing warns: the layers it should show simply vanish. Give the matte a fill (white for `alpha`, a gradient for `luma`).
|
|
427
427
|
99. **In a `music` score a capital letter is a chord and a lowercase one a note.** `G7` is G dominant seventh, `C5` a power chord, `C6` a sixth chord; `g7`, `c5`, `c6` are notes (octave 7 is a very high note: it used to be what `G7` meant, silently). A bare note in capitals (`Bb2`) is refused with the lowercase spelling (`bb2`); inside brackets (`[C4 E4 G4]`) any case is a note. An unknown chord kind suggests the nearest (`Cmin7` → `Cm7`, `Csus` → `Csus4`, `CM7` → `Cmaj7`).
|
|
@@ -432,6 +432,7 @@ A good habit that catches most of these: after `init`, read `window.__logs` (see
|
|
|
432
432
|
104. **A variable moves values, not timing, and it is read when the composition is built.** `at` and `duration` are plain numbers; `vars: { shift }` works in `x: 'W/2 + shift'` and `'{title}'` inside any string, not in `at: 'shift'`. `setVars` builds everything again (about 20 ms, plus the sound only if it changed): for a continuous input call it as often as you like, calls that overlap are joined, but do not use it for something that should move every frame (that is a keyframe).
|
|
433
433
|
105. **Layout that follows the screen is written in `W` / `H` / `GW` / `GH`, never in pixels.** After `movie.resize(w, h)` an expression sees the new size; a literal `x: 960` stays at 960. A string variable cannot be read in an expression (`x: 'title'` is a number context): use `{title}` in a string, or declare the number. A name that is declared but never read, and a `{name}` that was not declared, are both said at `init` (usually a typo). Load a web font BEFORE the first `setVars` that changes a text (see 103): the text is measured again each rebuild.
|
|
434
434
|
106. **A text outline used to spike; it is round-joined now.** `style: { stroke: { color: '#000', width: 12 } }` was drawn with PixiJS's default `miter` join, so the sharp corners of W, V, A, k and 4 grew spikes ("the outline sticks out"). The library now adds `join: 'round'` to an outline that names no join; say `join: 'miter'` or `'bevel'` if you want one. The stroke is centred on the glyph edge, so half of it eats into the letter: keep `width` to about a tenth of `fontSize`, or for a thick outline put a second copy of the text with the stroke UNDER the plain one.
|
|
435
|
+
107. **`spans` places pieces by measuring, so the font must be loaded first, and a span cannot change size.** A web font that is not loaded yet is measured as the fallback font and the pieces drift apart: `await document.fonts.load('30px "Name"')` before `movie.init`. `fontSize` / `fontFamily` / `lineHeight` / `align` on a span are ignored (warned): they belong to the layer's `style`. `fill`, `text` and `value` keyframes do not reach a spans layer (it is a group of text layers).
|
|
435
436
|
|
|
436
437
|
|
|
437
438
|
<!-- ===== skills/pixi-effects/reference/recipes.md ===== -->
|
|
@@ -1870,7 +1871,7 @@ interface SequenceCommon {
|
|
|
1870
1871
|
| Layer | Where `x, y` land by default | To change it |
|
|
1871
1872
|
|---|---|---|
|
|
1872
1873
|
| `shape` rect / circle / ellipse | the **centre** of the shape | `anchorX` / `anchorY` (0–1). A bar growing from its base: `anchorY: 1`; left to right: `anchorX: 0` |
|
|
1873
|
-
| `shape` line / polygon / path | the centre of the shape's bounds | `pivotX` / `pivotY` (in the shape's own canvas coordinates): the point that sits at `x, y` and that rotation / scale turn about. **`anchorX` / `anchorY` do nothing on these.** |
|
|
1874
|
+
| `shape` line / polygon / path | the centre of the shape's bounds | `pivotX` / `pivotY` (in the shape's own canvas coordinates): the point that sits at `x, y` and that rotation / scale turn about. **`anchorX` / `anchorY` do nothing on these.** A `path` whose `d` is already written in canvas coordinates takes no `x` / `y` at all (they would move its centre to that point); to move it, change the coordinates or wrap it in a composition. |
|
|
1874
1875
|
| `text`, `image`, `video` | the **top-left** corner | `anchorX` / `anchorY: 0.5` to centre |
|
|
1875
1876
|
| `composition` | its top-left corner | `pivotX` / `pivotY` = the point that sits at `x, y` and that rotation / scale turn about |
|
|
1876
1877
|
|
|
@@ -1940,6 +1941,16 @@ A text layer has one animatable number, `value`, printed wherever the text conta
|
|
|
1940
1941
|
|
|
1941
1942
|
`format`: `decimals` (default 0), thousands `grouping` (default false) and `pad` (zero-pad the whole part to that many digits: `pad: 2` gives `05`, so a clock is `text: '18:42:{value}'` with `format: { pad: 2 }`). Put prefixes and suffixes in the text (`'${value}'`, `'{value}%'`). If `value` is animated but the text has no `{value}`, a warning says so.
|
|
1942
1943
|
|
|
1944
|
+
**Colour inside one text layer: `spans`.** Instead of `text`, write an array of strings (drawn in the layer's own `style`) and `{ text, fill, … }` objects; they are shown one after the other, and `\n` inside a span starts a new line:
|
|
1945
|
+
|
|
1946
|
+
```ts
|
|
1947
|
+
{ type: 'text', name: 'code', style: { fontFamily: 'Menlo', fontSize: 30, fill: '#e8f5e8', align: 'left' },
|
|
1948
|
+
spans: [{ text: 'type', fill: '#7fa07f' }, ': ', { text: "'text'", fill: '#ffd166', fontWeight: '700' }, ',\n at: 0.5'],
|
|
1949
|
+
initial: { x: 120, y: 200 } }
|
|
1950
|
+
```
|
|
1951
|
+
|
|
1952
|
+
A span may change `fill`, `fontWeight`, `fontStyle`, `fontVariant`, `letterSpacing` and `dropShadow`; a size, a font, `lineHeight`, `align` or a `stroke` belongs to the layer's `style` (one outline per layer: a stroke shifts a piece by half its width; a span that sets one of these is warned about once and the key is ignored; `style.wordWrap` and `style.leading` do not apply either: the lines are the `\n` of the spans). `style.align` places the lines on the width of the block (`'left'`, `'center'` — the default, as for every text — or `'right'`). At build time the layer becomes a group (a composition) of ordinary text layers, one per piece of a line, named `<name>.0`, `<name>.1` …, each at the x it has when the pieces are measured one after the other (the same measuring as `splitText`), so `inspect`, the timeline view and `check` see them. The group carries the layer's `name`, `at`, `duration`, `initial` (`anchorX` / `anchorY` are numbers, 0.5 centres the whole block, and cannot be keyframed), `keyframes` (`x`, `y`, `alpha`, `scale`, `rotation`, …), `filters`, `mask` and `blendMode`. A **typewriter** works over all the spans: `initial: { visibleChars: 0 }` and a keyframe `to: { visibleChars: N }` count every character in order, the newline included. `fill`, `text`, `value`, `fillGradient`, `format` and `colorSpace` do not apply to a group (a warning says so): use a separate text layer, or put the colour in a span. Measuring needs the font loaded (`await document.fonts.load('30px "Name"')` for a web font), as for `splitText`; the size and spacing in `style` may be numbers, expressions (`'GH * 0.05'`) or declared variables: they are resolved before the pieces are measured, and a missing `fontSize` is measured as the text layer's own default (Arial, 36).
|
|
1953
|
+
|
|
1943
1954
|
### `image`
|
|
1944
1955
|
|
|
1945
1956
|
Renders a [PIXI.Sprite](https://pixijs.com/8.x/guides/components/scene-objects/sprite/sprite) from a registered asset. Intrinsic `w`/`h` come from the loaded texture.
|
|
@@ -3076,7 +3087,7 @@ A scaled punch-in / punch-out. By default `B` opens up: it starts large and zoom
|
|
|
3076
3087
|
|
|
3077
3088
|
### `dissolve`
|
|
3078
3089
|
|
|
3079
|
-
|
|
3090
|
+
A noise reveal driven by deterministic 2D Perlin noise. Pixels with a low noise value reveal first; as `uProgress` advances, more pixels reveal. At the default `scale` of 30 it reads as large blotches (about 40 px each), not as grain: `scale: 200` or more gives a fine grain. The same `seed` always produces the same dissolve pattern, so a render is bit-exact reproducible.
|
|
3080
3091
|
|
|
3081
3092
|
```ts
|
|
3082
3093
|
{
|
|
@@ -3186,7 +3197,7 @@ Gotchas, all measured:
|
|
|
3186
3197
|
- **Distortion:** `warp` (`wave`: a flag or the surface of water, `haze`: heat shimmer; moves with the frame's time, no keyframes needed, and pads the layer by `strength` so a shifted edge is not cut), `twist` (a swirl), `bulgePinch` (a lens), `shockwave` (a ring that spreads), `rgbSplit` (colour fringes), `glitch` (slices). `warp`'s time is the time of the frame (inside a time-remapped composition, that composition's clock), `speed: 0` stands still, `strength` is in px and is capped at 200. It bends the layer's picture; to draw something that does not exist yet, use a `shader` layer.
|
|
3187
3198
|
- **Centres and offsets are in canvas pixels, not the layer's:** `twist.offset`, `bulgePinch.center`, `zoomBlur.center`, `radialBlur.center`, `shockwave.center` default to the canvas's top-left corner, so a layer in the middle looks unaffected. Put the centre on the layer: `{ type: 'twist', offset: { x: 640, y: 360 } }` for a layer centred on a 1280×720 canvas.
|
|
3188
3199
|
- **A filter works inside the layer's bounds.** Glow, dropShadow, outline and blur draw outside it and are clipped unless you widen the area (`filterArea: { x: -24, y: -24, width: w + 48, height: h + 48 }`, in the layer's own coordinates). Do not widen it for `grayscale`, `oldFilm` or other filters that paint the whole area: they fill the extra margin with black.
|
|
3189
|
-
- **Animate scalars by name** (`'filters.halo.outerStrength'`). Options that are points (`rgbSplit.red`, `dropShadow.offset`)
|
|
3200
|
+
- **Animate scalars by name** (`'filters.halo.outerStrength'`). Options that are points (`rgbSplit.red`, `dropShadow.offset`) are animated one leaf at a time: `'filters.<name>.red.x'` / `'filters.<name>.red.y'` tween like any number (the whole point `red: { x, y }` as a keyframe value switches at its keyframe and does not tween; a path that leads nowhere is warned about once and ignored). `pixelate` animates through `sizeX` / `sizeY`, not `size`.
|
|
3190
3201
|
- `shockwave` and `godray` are driven by `time` (a still frame at `time: 0` looks like nothing): animate it. `radialBlur`, `shockwave` and `tiltShift` positions are canvas pixels too.
|
|
3191
3202
|
- Some filters take their value as one argument; the DSL hides that (`{ type: 'pixelate', size: 8 }`, `{ type: 'emboss', strength: 6 }`).
|
|
3192
3203
|
|
|
@@ -3217,7 +3228,7 @@ import { GlowFilter, OldFilmFilter } from 'pixi-filters';
|
|
|
3217
3228
|
Notes:
|
|
3218
3229
|
|
|
3219
3230
|
- `filter` must be a `Filter` instance (constructor must have run on the consumer side).
|
|
3220
|
-
-
|
|
3231
|
+
- A dotted key (`'filters.s.red.x'`) is a path into the filter: the leaf of the point is written in place, which `pixi-filters` reads when it draws (checked on `RGBSplitFilter` in a real browser, `tests/tools/pointOptions.test.ts`). Before 0.32 such a key was written as a property literally named `red.x` and nothing moved.
|
|
3221
3232
|
- Without a `name`, the filter still applies but cannot be addressed via `filters.<name>.<prop>` keyframe paths.
|
|
3222
3233
|
|
|
3223
3234
|
Notes:
|
package/package.json
CHANGED
|
@@ -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.32.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
|
|
|
@@ -50,7 +50,7 @@ Keyframe kinds: `set` (jump at `at`; undone when you seek back), `to` (animate t
|
|
|
50
50
|
|
|
51
51
|
## Layer types
|
|
52
52
|
|
|
53
|
-
**text** — `text`, `style` (any PixiJS TextStyle field: `fontSize fontFamily fontWeight fill letterSpacing lineHeight align wordWrap wordWrapWidth stroke dropShadow padding`; expressions OK for numbers), `colorSpace`. Default anchor is **top-left**; use `anchorX/anchorY: 0.5` to centre on `x,y`. A text layer has one animatable number, `value`, printed where the text contains `{value}` (`text: '{value} users'`, `initial: { value: 0 }`, keyframe `to: { value: 2480 }`; `format: { decimals, grouping, pad /* zero-pad the whole part: pad 2 → 05 */ }`) — that is how counters work. **Changing text:** a keyframe `set: { text: 'Two' }` swaps the string at that time (undone on seek back; `to` / `from` cannot tween a string); `visibleChars` (number, first N characters; starts at 0 once animated) is a typewriter: `to: { visibleChars: 30 }, duration: 2.4, ease: 'none'` — use `anchorX: 0` + `style.align: 'left'` so it grows rightward. Multi-line text is centre-aligned by default (`style.align: 'left'` to change).
|
|
53
|
+
**text** — `text`, `style` (any PixiJS TextStyle field: `fontSize fontFamily fontWeight fill letterSpacing lineHeight align wordWrap wordWrapWidth stroke dropShadow padding`; expressions OK for numbers), `colorSpace`. Default anchor is **top-left**; use `anchorX/anchorY: 0.5` to centre on `x,y`. A text layer has one animatable number, `value`, printed where the text contains `{value}` (`text: '{value} users'`, `initial: { value: 0 }`, keyframe `to: { value: 2480 }`; `format: { decimals, grouping, pad /* zero-pad the whole part: pad 2 → 05 */ }`) — that is how counters work. **Changing text:** a keyframe `set: { text: 'Two' }` swaps the string at that time (undone on seek back; `to` / `from` cannot tween a string); `visibleChars` (number, first N characters; starts at 0 once animated) is a typewriter: `to: { visibleChars: 30 }, duration: 2.4, ease: 'none'` — use `anchorX: 0` + `style.align: 'left'` so it grows rightward. Multi-line text is centre-aligned by default (`style.align: 'left'` to change). **Colour inside one line:** `spans: [{ text: 'type', fill: '#7fa07f' }, ': ', { text: "'a'", fill: '#ffd166', fontWeight: '700' }, ',\n next line']` instead of `text` (strings use the layer's `style`; a span changes only fill / fontWeight / fontStyle / fontVariant / letterSpacing / dropShadow, never a size, font or stroke (those are the layer's `style`); `\n` starts a line; `style.align: 'left'` for code; `visibleChars` types over all the spans; it becomes a group of text layers `<name>.0…`).
|
|
54
54
|
|
|
55
55
|
**image** — `asset`, `tint`, `colorSpace: 'rgb'|'oklab'|'oklch'`. Default anchor top-left; natural size = `w`,`h`.
|
|
56
56
|
|
|
@@ -155,7 +155,7 @@ measureText('Hello', style) // → { width, height,
|
|
|
155
155
|
splitText('Hello world', style, { by: 'chars' | 'words' | 'lines', x, y, align: 'left' | 'center' | 'right' })
|
|
156
156
|
// → [{ text, x, y, width, index, line }] — one text layer per piece, same style, anchor top-left
|
|
157
157
|
```
|
|
158
|
-
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, in the same wave; by default BACKWARDS FROM THE LAYER'S END (at + duration, so to be gone at time T set duration = T − at); `out: { preset: 'fade', at: 6 }` starts the exit at composition second 6 instead>, 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.
|
|
158
|
+
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, in the same wave (leave `out` out and the pieces stay until the layer ends); by default BACKWARDS FROM THE LAYER'S END (at + duration, so to be gone at time T set duration = T − at); `out: { preset: 'fade', at: 6 }` starts the exit at composition second 6 instead>, 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.
|
|
159
159
|
|
|
160
160
|
## Assets
|
|
161
161
|
|
|
@@ -123,7 +123,7 @@ A good habit that catches most of these: after `init`, read `window.__logs` (see
|
|
|
123
123
|
93. **A `threeD` layer's shadow budget is four casters per receiver, counted in layer order, not geometry.** Five letters as five `castsShadows` layers over one floor: the fifth casts nothing (the warning names it). Make the word ONE text layer (the light is per pixel, each letter is still lit on its own) or give only the four that matter `castsShadows`. Splitting the floor does not help: each half takes the first four.
|
|
124
124
|
94. **`resolution` below 1 softens crisp lines in a shader.** `fwidth` anti-aliased edges come out a little blurry after the upscale: keep `resolution: 1` for line art and use 0.5 only for soft fields (flowing colour, glow).
|
|
125
125
|
95. **A `color-dodge` leak over a dark picture is a muddy red disc.** Dodge divides each channel of near-black separately, red first. Cut the dodge to the bright subject with a matte (`mask: 'letters'`, a matte-only copy of the word) and put the warm haze in a separate `screen` radial.
|
|
126
|
-
96. **A filter option that is a point (`rgbSplit.red`, `glitch` offsets)
|
|
126
|
+
96. **A filter option that is a point (`rgbSplit.red`, `glitch` offsets) is tweened one leaf at a time** (`'filters.s.red.x'`; the whole point as a value switches, it does not tween), and a scalar too: `set: { 'filters.tear.seed': n }` steps `glitch.seed` (a `set` keyframe works on filter options; no warning, visible tearing). To switch between looks on one subject, put the subject in time-windowed `composition` copies, each carrying the filters of its look.
|
|
127
127
|
97. **A deck inside your own composition loses its `transitions` and `stops`** unless you take them across: `const d = deck({ width: 1920, height: 1080, pages })`, then `{ type: 'composition', sequences: d.composition.sequences, transitions: d.composition.transitions }`. Pass `width` / `height` yourself for a page that is not the movie's own size.
|
|
128
128
|
98. **A matte layer needs a fill.** A named matte (`mask: 'glass'`) that is a shape with no `fillColor` / `fillGradient` (or a text with no fill) cuts everything away and nothing warns: the layers it should show simply vanish. Give the matte a fill (white for `alpha`, a gradient for `luma`).
|
|
129
129
|
99. **In a `music` score a capital letter is a chord and a lowercase one a note.** `G7` is G dominant seventh, `C5` a power chord, `C6` a sixth chord; `g7`, `c5`, `c6` are notes (octave 7 is a very high note: it used to be what `G7` meant, silently). A bare note in capitals (`Bb2`) is refused with the lowercase spelling (`bb2`); inside brackets (`[C4 E4 G4]`) any case is a note. An unknown chord kind suggests the nearest (`Cmin7` → `Cm7`, `Csus` → `Csus4`, `CM7` → `Cmaj7`).
|
|
@@ -134,4 +134,4 @@ A good habit that catches most of these: after `init`, read `window.__logs` (see
|
|
|
134
134
|
104. **A variable moves values, not timing, and it is read when the composition is built.** `at` and `duration` are plain numbers; `vars: { shift }` works in `x: 'W/2 + shift'` and `'{title}'` inside any string, not in `at: 'shift'`. `setVars` builds everything again (about 20 ms, plus the sound only if it changed): for a continuous input call it as often as you like, calls that overlap are joined, but do not use it for something that should move every frame (that is a keyframe).
|
|
135
135
|
105. **Layout that follows the screen is written in `W` / `H` / `GW` / `GH`, never in pixels.** After `movie.resize(w, h)` an expression sees the new size; a literal `x: 960` stays at 960. A string variable cannot be read in an expression (`x: 'title'` is a number context): use `{title}` in a string, or declare the number. A name that is declared but never read, and a `{name}` that was not declared, are both said at `init` (usually a typo). Load a web font BEFORE the first `setVars` that changes a text (see 103): the text is measured again each rebuild.
|
|
136
136
|
106. **A text outline used to spike; it is round-joined now.** `style: { stroke: { color: '#000', width: 12 } }` was drawn with PixiJS's default `miter` join, so the sharp corners of W, V, A, k and 4 grew spikes ("the outline sticks out"). The library now adds `join: 'round'` to an outline that names no join; say `join: 'miter'` or `'bevel'` if you want one. The stroke is centred on the glyph edge, so half of it eats into the letter: keep `width` to about a tenth of `fontSize`, or for a thick outline put a second copy of the text with the stroke UNDER the plain one.
|
|
137
|
-
|
|
137
|
+
107. **`spans` places pieces by measuring, so the font must be loaded first, and a span cannot change size.** A web font that is not loaded yet is measured as the fallback font and the pieces drift apart: `await document.fonts.load('30px "Name"')` before `movie.init`. `fontSize` / `fontFamily` / `lineHeight` / `align` on a span are ignored (warned): they belong to the layer's `style`. `fill`, `text` and `value` keyframes do not reach a spans layer (it is a group of text layers).
|
|
@@ -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.32.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 */
|
|
@@ -37,16 +37,16 @@
|
|
|
37
37
|
"imports": {
|
|
38
38
|
"pixi.js": "https://esm.sh/pixi.js@8.22.0?bundle-deps",
|
|
39
39
|
"mediabunny": "https://esm.sh/mediabunny",
|
|
40
|
-
"pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
41
|
-
"pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
42
|
-
"pixi-effects/present": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
43
|
-
"pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
40
|
+
"pixi-effects": "https://cdn.jsdelivr.net/npm/pixi-effects@0.32.0/dist/index.js",
|
|
41
|
+
"pixi-effects/controller": "https://cdn.jsdelivr.net/npm/pixi-effects@0.32.0/dist/Controller.js",
|
|
42
|
+
"pixi-effects/present": "https://cdn.jsdelivr.net/npm/pixi-effects@0.32.0/dist/present.js",
|
|
43
|
+
"pixi-effects/three": "https://cdn.jsdelivr.net/npm/pixi-effects@0.32.0/dist/three.js",
|
|
44
44
|
"pixi-filters": "https://esm.sh/pixi-filters@6.1.5?external=pixi.js",
|
|
45
45
|
"three": "https://esm.sh/three@0.178.0"
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
</script>
|
|
49
|
-
<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.32.0/dist/index.js" /> <!-- after the import map: the library downloads while the page is parsed -->
|
|
50
50
|
|
|
51
51
|
<script type="module">
|
|
52
52
|
import { Movie, kenBurns, withFade } from 'pixi-effects';
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export { CompositionSequence } from './chunk-BGMCZA36.js';
|
|
2
|
-
import './chunk-MMTESMJ5.js';
|
|
3
|
-
import './chunk-4KB3GUEK.js';
|
|
4
|
-
import './chunk-RCVZX2JP.js';
|
|
5
|
-
import './chunk-BXOCEZ4U.js';
|
|
6
|
-
//# sourceMappingURL=Composition-DQCPJPFE.js.map
|
|
7
|
-
//# sourceMappingURL=Composition-DQCPJPFE.js.map
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var chunkL73KRFXI_cjs = require('./chunk-L73KRFXI.cjs');
|
|
4
|
-
require('./chunk-DJ5B7IKT.cjs');
|
|
5
|
-
require('./chunk-2DXPTKQZ.cjs');
|
|
6
|
-
require('./chunk-LYOKH7KE.cjs');
|
|
7
|
-
require('./chunk-TWZNOUM7.cjs');
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
Object.defineProperty(exports, "CompositionSequence", {
|
|
12
|
-
enumerable: true,
|
|
13
|
-
get: function () { return chunkL73KRFXI_cjs.CompositionSequence; }
|
|
14
|
-
});
|
|
15
|
-
//# sourceMappingURL=Composition-V2XYSW6P.cjs.map
|
|
16
|
-
//# sourceMappingURL=Composition-V2XYSW6P.cjs.map
|