@efxlab/motion-canvas-player 4.0.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/dist/main.js ADDED
@@ -0,0 +1,299 @@
1
+ var y = Object.defineProperty;
2
+ var g = (l, n, t) => n in l ? y(l, n, { enumerable: !0, configurable: !0, writable: !0, value: t }) : l[n] = t;
3
+ var s = (l, n, t) => (g(l, typeof n != "symbol" ? n + "" : n, t), t);
4
+ import { Stage as b, Player as v, Vector2 as f } from "@efxlab/motion-canvas-core";
5
+ const m = `.initial{display:none}.state-initial .initial{display:block}.loading{display:none}.state-loading .loading{display:block}.ready{display:none}.state-ready .ready{display:block}.error{display:none}.state-error .error{display:block}:host{position:relative;display:block;overflow:hidden}:host[data-fullscreen]{position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:9999}.overlay{position:absolute;left:0;right:0;top:0;bottom:0;display:flex;align-items:center;justify-content:center;opacity:0;background-color:#0000008a;transition:opacity .1s}.overlay.state-ready:not(.auto){cursor:pointer}.overlay.state-ready:not(.playing):not(.auto){opacity:1}.overlay.state-ready:not(.playing):not(.auto) .button{scale:1;transition:scale .1s ease-out}.overlay.state-loading,.overlay.state-error{opacity:1;transition:opacity 1s}.overlay.auto{opacity:0;pointer-events:none}.button{width:50%;max-width:96px;aspect-ratio:1;scale:.5;transition:scale .1s ease-in,opacity .1s;background-color:transparent;border:none;background-size:100% 100%;background-repeat:no-repeat;opacity:.54;cursor:inherit;background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjRweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMjRweCIgZmlsbD0iI2ZmZmZmZiI+PHBhdGggZD0iTTAgMGgyNHYyNEgwVjB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTEyIDJDNi40OCAyIDIgNi40OCAyIDEyczQuNDggMTAgMTAgMTAgMTAtNC40OCAxMC0xMFMxNy41MiAyIDEyIDJ6bS0yIDE0LjV2LTlsNiA0LjUtNiA0LjV6Ii8+PC9zdmc+)}.playing .button{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDI0IDI0IiBoZWlnaHQ9IjI0cHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjI0cHgiIGZpbGw9IiNmZmZmZmYiPjxnPjxyZWN0IGZpbGw9Im5vbmUiIGhlaWdodD0iMjQiIHdpZHRoPSIyNCIvPjxyZWN0IGZpbGw9Im5vbmUiIGhlaWdodD0iMjQiIHdpZHRoPSIyNCIvPjxyZWN0IGZpbGw9Im5vbmUiIGhlaWdodD0iMjQiIHdpZHRoPSIyNCIvPjwvZz48Zz48Zy8+PHBhdGggZD0iTTEyLDJDNi40OCwyLDIsNi40OCwyLDEyczQuNDgsMTAsMTAsMTBzMTAtNC40OCwxMC0xMFMxNy41MiwyLDEyLDJ6IE0xMSwxNkg5VjhoMlYxNnogTTE1LDE2aC0yVjhoMlYxNnoiLz48L2c+PC9zdmc+)}.button:focus,.overlay:hover .button{opacity:.87}.auto .button{display:none}.fullscreen-button{position:absolute;bottom:16px;right:16px;width:40px;height:40px;padding:8px;background:rgba(0,0,0,.54);border:none;border-radius:4px;cursor:pointer;opacity:0;transition:opacity .2s;display:none}.fullscreen-button svg{width:100%;height:100%;fill:#fff}:host([data-fullscreen-enabled]) .fullscreen-button{display:block}.overlay:hover .fullscreen-button,.fullscreen-button:focus{opacity:1}.fullscreen-button:hover{background:rgba(0,0,0,.87)}.canvas{width:100%;display:block;opacity:0;transition:opacity .1s}.canvas.state-ready{opacity:1}.message{font-family:JetBrains Mono,sans-serif;text-align:center;font-size:20px;padding:8px 16px;margin:16px;border-radius:4px;color:#fff9;background-color:#000000de}.loader{width:50%;max-width:96px;display:none;rotate:-90deg;animation:stroke 2s cubic-bezier(.5,0,.5,1) infinite,rotate 2s linear infinite}@keyframes stroke{0%{stroke-dasharray:5.6548667765px 50.8938009883px;stroke-dashoffset:2.8274333882px}50%{stroke-dasharray:50.8938009883px 5.6548667765px;stroke-dashoffset:-2.8274333882px}to{stroke-dasharray:5.6548667765px 50.8938009883px;stroke-dashoffset:-53.7212343766px}}@keyframes rotate{0%{rotate:-110deg}to{rotate:250deg}}
6
+ `, k = `<div class="overlay" part="overlay">
7
+ <button
8
+ part="play-button"
9
+ title="Play / Pause"
10
+ class="button ready"
11
+ tabindex="0"
12
+ ></button>
13
+ <button
14
+ part="fullscreen-button"
15
+ title="Toggle Fullscreen"
16
+ class="fullscreen-button"
17
+ tabindex="0"
18
+ >
19
+ <svg viewBox="0 0 24 24" fill="currentColor">
20
+ <path
21
+ d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"
22
+ />
23
+ </svg>
24
+ </button>
25
+ <div part="message" class="message error">
26
+ An error occurred while loading the animation.
27
+ </div>
28
+ <svg
29
+ part="loader"
30
+ class="loader loading"
31
+ viewBox="0 0 24 24"
32
+ stroke="#ffffff"
33
+ stroke-width="2"
34
+ fill="transparent"
35
+ >
36
+ <circle cx="12" cy="12" r="9" />
37
+ </svg>
38
+ </div>
39
+ `, x = `<style>${m}</style>${k}`, d = "motion-canvas-player";
40
+ class M extends HTMLElement {
41
+ constructor() {
42
+ super();
43
+ s(this, "root");
44
+ s(this, "canvas");
45
+ s(this, "overlay");
46
+ s(this, "button");
47
+ s(this, "fullscreenButton");
48
+ s(this, "state", "initial");
49
+ s(this, "project", null);
50
+ s(this, "player", null);
51
+ s(this, "defaultSettings");
52
+ s(this, "abortController", null);
53
+ s(this, "finished", !1);
54
+ s(this, "playing", !1);
55
+ s(this, "connected", !1);
56
+ s(this, "stage", new b());
57
+ s(this, "resizeObserver", null);
58
+ s(this, "currentRatio", "16x9");
59
+ // Removed handleMouseMove and handleMouseLeave - no hover effects
60
+ s(this, "handleMouseMove", () => {
61
+ });
62
+ s(this, "handleMouseLeave", () => {
63
+ });
64
+ s(this, "handleMouseDown", (t) => {
65
+ t.preventDefault();
66
+ });
67
+ s(this, "handleFullscreenClick", (t) => {
68
+ t.stopPropagation(), this.toggleFullscreen();
69
+ });
70
+ s(this, "handleClick", () => {
71
+ this.auto || this.setPlaying(!this.playing);
72
+ });
73
+ s(this, "render", async () => {
74
+ this.player && await this.stage.render(
75
+ this.player.playback.currentScene,
76
+ this.player.playback.previousScene
77
+ );
78
+ });
79
+ this.root = this.attachShadow({ mode: "open" }), this.root.innerHTML = x, this.overlay = this.root.querySelector(".overlay"), this.button = this.root.querySelector(".button"), this.fullscreenButton = this.root.querySelector(".fullscreen-button"), this.canvas = this.stage.finalBuffer, this.canvas.classList.add("canvas"), this.root.prepend(this.canvas), this.overlay.addEventListener("click", this.handleClick), this.button.addEventListener("mousedown", this.handleMouseDown), this.fullscreenButton.addEventListener("click", this.handleFullscreenClick), this.updateResponsiveMode(), this.updateFullscreenMode(), this.updateControlsVisibility(), this.setState(
80
+ "initial"
81
+ /* Initial */
82
+ );
83
+ }
84
+ static get observedAttributes() {
85
+ return [
86
+ "src",
87
+ "quality",
88
+ "width",
89
+ "height",
90
+ "auto",
91
+ "variables",
92
+ "responsive",
93
+ "aspect-ratio",
94
+ "fullscreen",
95
+ "no-controls",
96
+ "background",
97
+ "timescale",
98
+ "ratio"
99
+ ];
100
+ }
101
+ get auto() {
102
+ return this.hasAttribute("auto");
103
+ }
104
+ get quality() {
105
+ const t = this.getAttribute("quality");
106
+ return t ? parseFloat(t) : this.defaultSettings.resolutionScale;
107
+ }
108
+ get width() {
109
+ const t = this.getAttribute("width");
110
+ return t ? parseFloat(t) : this.defaultSettings.size.width;
111
+ }
112
+ get height() {
113
+ const t = this.getAttribute("height");
114
+ return t ? parseFloat(t) : this.defaultSettings.size.height;
115
+ }
116
+ get variables() {
117
+ try {
118
+ const t = this.getAttribute("variables");
119
+ return t ? JSON.parse(t) : {};
120
+ } catch {
121
+ return this.project.logger.warn("Project variables could not be parsed."), {};
122
+ }
123
+ }
124
+ get responsive() {
125
+ return this.hasAttribute("responsive");
126
+ }
127
+ get aspectRatio() {
128
+ const t = this.getAttribute("aspect-ratio");
129
+ if (!t)
130
+ return null;
131
+ const [e, i] = t.split(":").map(Number);
132
+ return e && i ? e / i : null;
133
+ }
134
+ get fullscreen() {
135
+ return this.hasAttribute("fullscreen");
136
+ }
137
+ get noControls() {
138
+ return this.hasAttribute("no-controls");
139
+ }
140
+ get background() {
141
+ return this.getAttribute("background") || "#222222";
142
+ }
143
+ get timescale() {
144
+ const t = this.getAttribute("timescale");
145
+ return t ? parseFloat(t) : 1;
146
+ }
147
+ get ratio() {
148
+ return this.getAttribute("ratio") || "16x9";
149
+ }
150
+ // Public API for external play/pause control (bypasses auto logic)
151
+ play() {
152
+ this.state === "ready" && this.player && (this.player.togglePlayback(!0), this.playing = !0, this.updateClass());
153
+ }
154
+ pause() {
155
+ this.state === "ready" && this.player && (this.player.togglePlayback(!1), this.playing = !1, this.updateClass());
156
+ }
157
+ get isPlaying() {
158
+ return this.playing;
159
+ }
160
+ setRatio(t) {
161
+ this.currentRatio = t, this.setAttribute("ratio", t), this.handleRatioChange();
162
+ }
163
+ getRatio() {
164
+ return this.currentRatio;
165
+ }
166
+ handleRatioChange() {
167
+ this.player && this.responsive && this.updateSettings();
168
+ }
169
+ toggleFullscreen() {
170
+ document.fullscreenElement ? document.exitFullscreen() : this.requestFullscreen().catch((t) => {
171
+ console.error(`Error attempting to enable fullscreen: ${t.message}`);
172
+ });
173
+ }
174
+ updateResponsiveMode() {
175
+ var t;
176
+ this.responsive ? (this.dataset.responsive = "", this.connected && !this.resizeObserver && (this.resizeObserver = new ResizeObserver(() => {
177
+ this.updateSettings();
178
+ }), this.resizeObserver.observe(this))) : (delete this.dataset.responsive, (t = this.resizeObserver) == null || t.disconnect(), this.resizeObserver = null);
179
+ }
180
+ updateFullscreenMode() {
181
+ this.fullscreen ? this.dataset.fullscreenEnabled = "" : delete this.dataset.fullscreenEnabled;
182
+ }
183
+ updateControlsVisibility() {
184
+ this.noControls ? (this.button.style.display = "none", this.fullscreenButton.style.display = "none", this.overlay.style.pointerEvents = "none", this.overlay.style.opacity = "0") : (this.button.style.display = "", this.fullscreenButton.style.display = "", this.overlay.style.pointerEvents = "", this.overlay.style.opacity = "");
185
+ }
186
+ updateBackground() {
187
+ this.canvas.style.backgroundColor = this.background;
188
+ }
189
+ updateTimescale() {
190
+ this.player && (this.player.playback.speed = this.timescale);
191
+ }
192
+ setState(t) {
193
+ this.state = t, this.setPlaying(this.playing);
194
+ }
195
+ setPlaying(t) {
196
+ var e, i;
197
+ this.state === "ready" && (t || this.auto) ? ((e = this.player) == null || e.togglePlayback(!0), this.playing = !0) : ((i = this.player) == null || i.togglePlayback(!1), this.playing = !1), this.updateClass();
198
+ }
199
+ updateClass() {
200
+ this.overlay.className = `overlay state-${this.state}`, this.canvas.className = `canvas state-${this.state}`, this.overlay.classList.toggle("playing", this.playing), this.overlay.classList.toggle("auto", this.auto), this.connected && (this.playing ? delete this.dataset.overlay : this.dataset.overlay = "");
201
+ }
202
+ async updateSource(t) {
203
+ var o, a, r, c;
204
+ this.setState(
205
+ "initial"
206
+ /* Initial */
207
+ ), (o = this.abortController) == null || o.abort(), this.abortController = new AbortController();
208
+ let e;
209
+ try {
210
+ const h = import(
211
+ /* webpackIgnore: true */
212
+ /* @vite-ignore */
213
+ t
214
+ ), u = new Promise((p) => setTimeout(p, 200));
215
+ await Promise.any([u, h]), this.setState(
216
+ "loading"
217
+ /* Loading */
218
+ ), e = (await h).default;
219
+ } catch (h) {
220
+ console.error(h), this.setState(
221
+ "error"
222
+ /* Error */
223
+ );
224
+ return;
225
+ }
226
+ this.defaultSettings = e.meta.getFullRenderingSettings();
227
+ const i = new v(e);
228
+ i.setVariables(this.variables), this.finished = !1, (a = this.player) == null || a.onRender.unsubscribe(this.render), (r = this.player) == null || r.togglePlayback(!1), (c = this.player) == null || c.deactivate(), this.project = e, this.player = i, this.updateSettings(), this.updateBackground(), this.updateTimescale(), this.player.onRender.subscribe(this.render), this.auto && (this.playing = !0), this.player.togglePlayback(this.playing), this.setState(
229
+ "ready"
230
+ /* Ready */
231
+ );
232
+ }
233
+ attributeChangedCallback(t, e, i) {
234
+ var o;
235
+ switch (t) {
236
+ case "auto":
237
+ this.setPlaying(this.playing);
238
+ break;
239
+ case "src":
240
+ this.updateSource(i);
241
+ break;
242
+ case "quality":
243
+ case "width":
244
+ case "height":
245
+ this.updateSettings();
246
+ break;
247
+ case "variables":
248
+ (o = this.player) == null || o.setVariables(this.variables);
249
+ break;
250
+ case "responsive":
251
+ this.updateResponsiveMode();
252
+ break;
253
+ case "aspect-ratio":
254
+ this.updateSettings();
255
+ break;
256
+ case "fullscreen":
257
+ this.updateFullscreenMode();
258
+ break;
259
+ case "no-controls":
260
+ this.updateControlsVisibility();
261
+ break;
262
+ case "background":
263
+ this.updateBackground();
264
+ break;
265
+ case "timescale":
266
+ this.updateTimescale();
267
+ break;
268
+ case "ratio":
269
+ this.currentRatio = i || "16x9", this.handleRatioChange();
270
+ break;
271
+ }
272
+ }
273
+ disconnectedCallback() {
274
+ var t, e, i;
275
+ this.connected = !1, (t = this.player) == null || t.deactivate(), (e = this.player) == null || e.onRender.unsubscribe(this.render), (i = this.resizeObserver) == null || i.disconnect(), this.resizeObserver = null;
276
+ }
277
+ connectedCallback() {
278
+ var t, e;
279
+ this.connected = !0, (t = this.player) == null || t.activate(), (e = this.player) == null || e.onRender.subscribe(this.render), this.responsive && !this.resizeObserver && (this.resizeObserver = new ResizeObserver(() => {
280
+ this.updateSettings();
281
+ }), this.resizeObserver.observe(this));
282
+ }
283
+ updateSettings() {
284
+ if (!this.player || !this.defaultSettings)
285
+ return;
286
+ let t = this.width, e = this.height;
287
+ if (this.responsive) {
288
+ const o = this.getBoundingClientRect(), a = o.width, r = o.height;
289
+ this.aspectRatio && a > 0 && r > 0 ? a / r > this.aspectRatio ? (e = Math.round(r), t = Math.round(r * this.aspectRatio)) : (t = Math.round(a), e = Math.round(a / this.aspectRatio)) : a > 0 && r > 0 && (t = Math.round(a), e = Math.round(r)), this.canvas.style.width = `${t}px`, this.canvas.style.height = `${e}px`, this.canvas.style.position = "absolute", this.canvas.style.left = "50%", this.canvas.style.top = "50%", this.canvas.style.transform = "translate(-50%, -50%)";
290
+ }
291
+ const i = {
292
+ ...this.defaultSettings,
293
+ size: new f(t, e),
294
+ resolutionScale: this.quality
295
+ };
296
+ this.stage.configure(i), this.player.configure(i);
297
+ }
298
+ }
299
+ customElements.get(d) || customElements.define(d, M);
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@efxlab/motion-canvas-player",
3
+ "version": "4.0.0",
4
+ "description": "A custom element for displaying animations made with Motion Canvas",
5
+ "main": "dist/main.js",
6
+ "types": "types/main.d.ts",
7
+ "author": "motion-canvas",
8
+ "homepage": "https://motioncanvas.io/",
9
+ "bugs": "https://github.com/motion-canvas/motion-canvas/issues",
10
+ "license": "MIT",
11
+ "type": "module",
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
15
+ "scripts": {
16
+ "dev": "vite",
17
+ "build": "tsc && vite build"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/electroheadfx/efx-motion-canvas.git"
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "types"
26
+ ],
27
+ "devDependencies": {
28
+ "@efxlab/motion-canvas-core": "workspace:*",
29
+ "@types/node": "^18.19.130",
30
+ "sass": "^1.58.0",
31
+ "terser": "^5.16.1",
32
+ "typescript": "^5.9.3",
33
+ "vite": "^4.5.14"
34
+ }
35
+ }
@@ -0,0 +1,8 @@
1
+ export interface MotionCanvasPlayerProps {
2
+ src: string;
3
+ width?: number;
4
+ height?: number;
5
+ auto?: boolean;
6
+ quality?: number;
7
+ variables?: string;
8
+ }