mason-sprite 0.1.1 → 0.1.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/LICENSE +21 -0
- package/README.md +145 -6
- package/dist/index.cjs +57 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -5
- package/dist/index.d.ts +12 -5
- package/dist/index.js +55 -10
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +68 -16
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +6 -4
- package/dist/react/index.d.ts +6 -4
- package/dist/react/index.js +68 -16
- package/dist/react/index.js.map +1 -1
- package/dist/svelte/Sprite.svelte +3 -2
- package/dist/svelte/Sprite.svelte.d.ts.map +1 -1
- package/dist/vue/Sprite.vue.d.ts +2 -2
- package/dist/vue/Sprite.vue.d.ts.map +1 -1
- package/dist/vue/index.cjs +1 -1
- package/dist/vue/index.js +89 -69
- package/package.json +37 -26
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 mason
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,6 +1,41 @@
|
|
|
1
1
|
# mason-sprite
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/mason-sprite)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
|
|
6
|
+
**v0.1.2** — Lightweight sprite sheet animation for **React**, **Vue**, and **Svelte** — one package, subpath imports.
|
|
7
|
+
|
|
8
|
+
Drop in a PNG or WebP sprite sheet, set `rows`, `cols`, and `fps` — and you're done. No Lottie, no timeline editor. Just a simple **CSS** or **Canvas** sprite player.
|
|
9
|
+
|
|
10
|
+
**Demo & docs:** [mason-sprite.com](https://mason-sprite.com)
|
|
11
|
+
|
|
12
|
+
## Preview
|
|
13
|
+
|
|
14
|
+
One sprite sheet, a few props — animation on screen.
|
|
15
|
+
|
|
16
|
+
<table>
|
|
17
|
+
<tr>
|
|
18
|
+
<td align="center" width="50%">
|
|
19
|
+
<strong>Sprite sheet</strong><br />
|
|
20
|
+
<code>img-cat-run.webp</code> · 2 rows × 5 cols
|
|
21
|
+
<br /><br />
|
|
22
|
+
<img src="./docs/assets/readme/img-cat-run.webp" alt="Cat run sprite sheet — 2 rows, 5 columns" width="420" />
|
|
23
|
+
</td>
|
|
24
|
+
<td align="center" width="50%">
|
|
25
|
+
<strong>Rendered with mason-sprite</strong><br />
|
|
26
|
+
<code>rows={2}</code> · <code>cols={5}</code> · <code>fps={10}</code>
|
|
27
|
+
<br /><br />
|
|
28
|
+
<img src="./docs/assets/readme/img-cat-run.gif" alt="Cat run animation rendered by mason-sprite" width="140" />
|
|
29
|
+
</td>
|
|
30
|
+
</tr>
|
|
31
|
+
</table>
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
img-cat-run.webp → rows × cols → looping animation
|
|
35
|
+
(WebP sheet) (2 × 5) (CSS or Canvas)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Try it live on **[mason-sprite.com](https://mason-sprite.com)**.
|
|
4
39
|
|
|
5
40
|
## Install
|
|
6
41
|
|
|
@@ -8,22 +43,126 @@ Lightweight sprite sheet animation for **React**, **Vue**, and **Svelte** — on
|
|
|
8
43
|
npm install mason-sprite
|
|
9
44
|
```
|
|
10
45
|
|
|
46
|
+
Peer dependencies (install only what you use):
|
|
47
|
+
|
|
48
|
+
| Framework | Peers |
|
|
49
|
+
|-----------|-------|
|
|
50
|
+
| React | `react`, `react-dom` |
|
|
51
|
+
| Vue 3 | `vue` |
|
|
52
|
+
| Svelte | `svelte` |
|
|
53
|
+
|
|
11
54
|
## Usage
|
|
12
55
|
|
|
56
|
+
### Core engine (vanilla JS)
|
|
57
|
+
|
|
13
58
|
```ts
|
|
14
59
|
import { SpriteAnimator } from 'mason-sprite';
|
|
60
|
+
|
|
61
|
+
const animator = new SpriteAnimator({
|
|
62
|
+
src: '/sprites/cat-run.webp',
|
|
63
|
+
rows: 2,
|
|
64
|
+
cols: 5,
|
|
65
|
+
fps: 10,
|
|
66
|
+
loop: true,
|
|
67
|
+
width: '8rem',
|
|
68
|
+
height: '8rem',
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
animator.attach(document.getElementById('sprite')!);
|
|
72
|
+
animator.play();
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### React
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
15
78
|
import { Sprite } from 'mason-sprite/react';
|
|
79
|
+
|
|
80
|
+
<Sprite
|
|
81
|
+
src="/sprites/cat-run.webp"
|
|
82
|
+
rows={2}
|
|
83
|
+
cols={5}
|
|
84
|
+
fps={10}
|
|
85
|
+
loop
|
|
86
|
+
width="8rem"
|
|
87
|
+
height="8rem"
|
|
88
|
+
/>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Vue 3
|
|
92
|
+
|
|
93
|
+
```vue
|
|
94
|
+
<script setup>
|
|
16
95
|
import { Sprite } from 'mason-sprite/vue';
|
|
17
|
-
|
|
96
|
+
</script>
|
|
97
|
+
|
|
98
|
+
<template>
|
|
99
|
+
<Sprite
|
|
100
|
+
src="/sprites/cat-run.webp"
|
|
101
|
+
:rows="2"
|
|
102
|
+
:cols="5"
|
|
103
|
+
:fps="10"
|
|
104
|
+
:loop="true"
|
|
105
|
+
width="8rem"
|
|
106
|
+
height="8rem"
|
|
107
|
+
/>
|
|
108
|
+
</template>
|
|
18
109
|
```
|
|
19
110
|
|
|
20
|
-
|
|
111
|
+
### Svelte
|
|
112
|
+
|
|
113
|
+
```svelte
|
|
114
|
+
<script>
|
|
115
|
+
import { Sprite } from 'mason-sprite/svelte';
|
|
116
|
+
</script>
|
|
117
|
+
|
|
118
|
+
<Sprite
|
|
119
|
+
src="/sprites/cat-run.webp"
|
|
120
|
+
rows={2}
|
|
121
|
+
cols={5}
|
|
122
|
+
fps={10}
|
|
123
|
+
loop
|
|
124
|
+
width="8rem"
|
|
125
|
+
height="8rem"
|
|
126
|
+
/>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Exports
|
|
130
|
+
|
|
131
|
+
| Import path | Contents |
|
|
132
|
+
|-------------|----------|
|
|
133
|
+
| `mason-sprite` | `SpriteAnimator`, types, utilities |
|
|
134
|
+
| `mason-sprite/react` | `Sprite`, `useSprite` |
|
|
135
|
+
| `mason-sprite/vue` | `Sprite` component |
|
|
136
|
+
| `mason-sprite/svelte` | `Sprite` component |
|
|
21
137
|
|
|
22
|
-
##
|
|
138
|
+
## Features
|
|
23
139
|
|
|
24
|
-
|
|
140
|
+
- PNG / WebP sprite sheet support
|
|
141
|
+
- CSS or Canvas rendering
|
|
142
|
+
- Responsive sizing — `width` / `height` accept CSS lengths (`rem`, `em`, `%`, `vw`, etc.)
|
|
143
|
+
- Canvas mode uses `ResizeObserver` and `devicePixelRatio` for sharp rendering
|
|
144
|
+
- `play`, `pause`, `stop`, `goToFrame` controls
|
|
145
|
+
- Works with any uniform grid sprite sheet (`rows × cols`)
|
|
25
146
|
|
|
26
|
-
|
|
147
|
+
## Sprite Sheet Requirements
|
|
148
|
+
|
|
149
|
+
- Uniform grid — every frame is the same size
|
|
150
|
+
- PNG or WebP format
|
|
151
|
+
- `rows × cols` = total frame count
|
|
152
|
+
|
|
153
|
+
## Development
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
pnpm install
|
|
157
|
+
pnpm build
|
|
158
|
+
pnpm typecheck
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Publish
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
npm publish
|
|
165
|
+
```
|
|
27
166
|
|
|
28
167
|
## License
|
|
29
168
|
|
package/dist/index.cjs
CHANGED
|
@@ -24,7 +24,8 @@ __export(core_exports, {
|
|
|
24
24
|
SpriteAnimator: () => SpriteAnimator,
|
|
25
25
|
getBackgroundPositionPercent: () => getBackgroundPositionPercent,
|
|
26
26
|
getFramePosition: () => getFramePosition,
|
|
27
|
-
getTotalFrames: () => getTotalFrames
|
|
27
|
+
getTotalFrames: () => getTotalFrames,
|
|
28
|
+
toCssLength: () => toCssLength
|
|
28
29
|
});
|
|
29
30
|
module.exports = __toCommonJS(core_exports);
|
|
30
31
|
|
|
@@ -39,6 +40,9 @@ var SPRITE_ANIMATION_DEFAULTS = {
|
|
|
39
40
|
};
|
|
40
41
|
|
|
41
42
|
// src/core/utils.ts
|
|
43
|
+
function toCssLength(size) {
|
|
44
|
+
return typeof size === "number" ? `${size}px` : size;
|
|
45
|
+
}
|
|
42
46
|
function getTotalFrames(rows, cols) {
|
|
43
47
|
return rows * cols;
|
|
44
48
|
}
|
|
@@ -56,15 +60,24 @@ function getBackgroundPositionPercent(frameIndex, rows, cols) {
|
|
|
56
60
|
}
|
|
57
61
|
|
|
58
62
|
// src/core/canvas-renderer.ts
|
|
59
|
-
function drawCanvasFrame(canvas, image, frameIndex, rows, cols
|
|
63
|
+
function drawCanvasFrame(canvas, image, frameIndex, rows, cols) {
|
|
60
64
|
const ctx = canvas.getContext("2d");
|
|
61
65
|
if (!ctx) return;
|
|
66
|
+
const displayWidth = canvas.clientWidth;
|
|
67
|
+
const displayHeight = canvas.clientHeight;
|
|
68
|
+
if (displayWidth === 0 || displayHeight === 0) return;
|
|
69
|
+
const dpr = window.devicePixelRatio || 1;
|
|
70
|
+
const pixelWidth = Math.round(displayWidth * dpr);
|
|
71
|
+
const pixelHeight = Math.round(displayHeight * dpr);
|
|
72
|
+
if (canvas.width !== pixelWidth || canvas.height !== pixelHeight) {
|
|
73
|
+
canvas.width = pixelWidth;
|
|
74
|
+
canvas.height = pixelHeight;
|
|
75
|
+
}
|
|
62
76
|
const frameWidth = image.naturalWidth / cols;
|
|
63
77
|
const frameHeight = image.naturalHeight / rows;
|
|
64
78
|
const { row, col } = getFramePosition(frameIndex, cols);
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
ctx.clearRect(0, 0, width, height);
|
|
79
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
80
|
+
ctx.clearRect(0, 0, displayWidth, displayHeight);
|
|
68
81
|
ctx.drawImage(
|
|
69
82
|
image,
|
|
70
83
|
col * frameWidth,
|
|
@@ -73,8 +86,8 @@ function drawCanvasFrame(canvas, image, frameIndex, rows, cols, width, height) {
|
|
|
73
86
|
frameHeight,
|
|
74
87
|
0,
|
|
75
88
|
0,
|
|
76
|
-
|
|
77
|
-
|
|
89
|
+
displayWidth,
|
|
90
|
+
displayHeight
|
|
78
91
|
);
|
|
79
92
|
}
|
|
80
93
|
|
|
@@ -85,8 +98,8 @@ function applyCssFrame(target, src, frameIndex, rows, cols, width, height) {
|
|
|
85
98
|
target.style.backgroundRepeat = "no-repeat";
|
|
86
99
|
target.style.backgroundSize = `${cols * 100}% ${rows * 100}%`;
|
|
87
100
|
target.style.backgroundPosition = `${x}% ${y}%`;
|
|
88
|
-
target.style.width =
|
|
89
|
-
target.style.height =
|
|
101
|
+
target.style.width = toCssLength(width);
|
|
102
|
+
target.style.height = toCssLength(height);
|
|
90
103
|
target.style.display = "inline-block";
|
|
91
104
|
}
|
|
92
105
|
function resetCssRenderer(target) {
|
|
@@ -106,6 +119,7 @@ var SpriteAnimator = class {
|
|
|
106
119
|
this.target = null;
|
|
107
120
|
this.listeners = /* @__PURE__ */ new Set();
|
|
108
121
|
this.destroyed = false;
|
|
122
|
+
this.resizeObserver = null;
|
|
109
123
|
this.tick = (timestamp) => {
|
|
110
124
|
if (!this.isPlaying || this.destroyed) return;
|
|
111
125
|
if (this.lastTimestamp === 0) {
|
|
@@ -129,6 +143,8 @@ var SpriteAnimator = class {
|
|
|
129
143
|
}
|
|
130
144
|
attach(target) {
|
|
131
145
|
this.target = target;
|
|
146
|
+
this.applyCanvasDisplaySize();
|
|
147
|
+
this.setupResizeObserver();
|
|
132
148
|
if (this.isLoaded) {
|
|
133
149
|
this.render();
|
|
134
150
|
}
|
|
@@ -182,6 +198,7 @@ var SpriteAnimator = class {
|
|
|
182
198
|
updateOptions(partial) {
|
|
183
199
|
const prevSrc = this.options.src;
|
|
184
200
|
const prevFps = this.options.fps;
|
|
201
|
+
const prevRenderer = this.options.renderer;
|
|
185
202
|
this.options = { ...this.options, ...partial };
|
|
186
203
|
if (partial.src !== void 0 && partial.src !== prevSrc) {
|
|
187
204
|
this.loadImage();
|
|
@@ -191,10 +208,18 @@ var SpriteAnimator = class {
|
|
|
191
208
|
if (partial.fps !== void 0 && partial.fps !== prevFps) {
|
|
192
209
|
this.accumulatedTime = 0;
|
|
193
210
|
}
|
|
211
|
+
if (partial.width !== void 0 || partial.height !== void 0) {
|
|
212
|
+
this.applyCanvasDisplaySize();
|
|
213
|
+
}
|
|
214
|
+
if (partial.renderer !== void 0 && partial.renderer !== prevRenderer) {
|
|
215
|
+
this.setupResizeObserver();
|
|
216
|
+
}
|
|
194
217
|
}
|
|
195
218
|
destroy() {
|
|
196
219
|
this.destroyed = true;
|
|
197
220
|
this.pause();
|
|
221
|
+
this.resizeObserver?.disconnect();
|
|
222
|
+
this.resizeObserver = null;
|
|
198
223
|
this.listeners.clear();
|
|
199
224
|
if (this.target && this.options.renderer === "css") {
|
|
200
225
|
resetCssRenderer(this.target);
|
|
@@ -252,7 +277,7 @@ var SpriteAnimator = class {
|
|
|
252
277
|
if (!this.target || !this.isLoaded) return;
|
|
253
278
|
const { src, rows, cols, width, height, renderer } = this.options;
|
|
254
279
|
if (renderer === "canvas" && this.target instanceof HTMLCanvasElement && this.image) {
|
|
255
|
-
drawCanvasFrame(this.target, this.image, this.currentFrame, rows, cols
|
|
280
|
+
drawCanvasFrame(this.target, this.image, this.currentFrame, rows, cols);
|
|
256
281
|
} else if (renderer === "css" && this.target instanceof HTMLElement) {
|
|
257
282
|
applyCssFrame(this.target, src, this.currentFrame, rows, cols, width, height);
|
|
258
283
|
}
|
|
@@ -261,6 +286,26 @@ var SpriteAnimator = class {
|
|
|
261
286
|
const state = this.getState();
|
|
262
287
|
this.listeners.forEach((listener) => listener(state));
|
|
263
288
|
}
|
|
289
|
+
applyCanvasDisplaySize() {
|
|
290
|
+
if (this.options.renderer !== "canvas" || !(this.target instanceof HTMLCanvasElement)) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
this.target.style.width = toCssLength(this.options.width);
|
|
294
|
+
this.target.style.height = toCssLength(this.options.height);
|
|
295
|
+
}
|
|
296
|
+
setupResizeObserver() {
|
|
297
|
+
this.resizeObserver?.disconnect();
|
|
298
|
+
this.resizeObserver = null;
|
|
299
|
+
if (this.options.renderer !== "canvas" || !(this.target instanceof HTMLCanvasElement) || typeof ResizeObserver === "undefined") {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
303
|
+
if (this.isLoaded) {
|
|
304
|
+
this.render();
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
this.resizeObserver.observe(this.target);
|
|
308
|
+
}
|
|
264
309
|
};
|
|
265
310
|
// Annotate the CommonJS export names for ESM import in node:
|
|
266
311
|
0 && (module.exports = {
|
|
@@ -268,6 +313,7 @@ var SpriteAnimator = class {
|
|
|
268
313
|
SpriteAnimator,
|
|
269
314
|
getBackgroundPositionPercent,
|
|
270
315
|
getFramePosition,
|
|
271
|
-
getTotalFrames
|
|
316
|
+
getTotalFrames,
|
|
317
|
+
toCssLength
|
|
272
318
|
});
|
|
273
319
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/index.ts","../src/core/constants.ts","../src/core/utils.ts","../src/core/canvas-renderer.ts","../src/core/css-renderer.ts","../src/core/sprite-animator.ts"],"sourcesContent":["export { SPRITE_ANIMATION_DEFAULTS } from './constants.js';\nexport { SpriteAnimator } from './sprite-animator.js';\nexport type {\n FramePosition,\n RendererMode,\n SpriteAnimationOptions,\n SpriteAnimationState,\n} from './types.js';\nexport {\n getBackgroundPositionPercent,\n getFramePosition,\n getTotalFrames,\n} from './utils.js';\n","import type { RendererMode } from './types.js';\n\nexport const SPRITE_ANIMATION_DEFAULTS = {\n fps: 12,\n loop: true,\n width: 128,\n height: 128,\n autoPlay: true,\n renderer: 'css' as RendererMode,\n} as const;\n","import type { FramePosition } from './types.js';\n\nexport function getTotalFrames(rows: number, cols: number): number {\n return rows * cols;\n}\n\nexport function getFramePosition(frameIndex: number, cols: number): FramePosition {\n return {\n row: Math.floor(frameIndex / cols),\n col: frameIndex % cols,\n };\n}\n\nexport function getBackgroundPositionPercent(\n frameIndex: number,\n rows: number,\n cols: number,\n): { x: number; y: number } {\n const { row, col } = getFramePosition(frameIndex, cols);\n const x = cols <= 1 ? 0 : (col / (cols - 1)) * 100;\n const y = rows <= 1 ? 0 : (row / (rows - 1)) * 100;\n return { x, y };\n}\n","import { getFramePosition } from './utils.js';\n\nexport function drawCanvasFrame(\n canvas: HTMLCanvasElement,\n image: HTMLImageElement,\n frameIndex: number,\n rows: number,\n cols: number,\n width: number,\n height: number,\n): void {\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n const frameWidth = image.naturalWidth / cols;\n const frameHeight = image.naturalHeight / rows;\n const { row, col } = getFramePosition(frameIndex, cols);\n\n canvas.width = width;\n canvas.height = height;\n\n ctx.clearRect(0, 0, width, height);\n ctx.drawImage(\n image,\n col * frameWidth,\n row * frameHeight,\n frameWidth,\n frameHeight,\n 0,\n 0,\n width,\n height,\n );\n}\n","import { getBackgroundPositionPercent } from './utils.js';\n\nexport interface CssRendererTarget {\n style: CSSStyleDeclaration;\n}\n\nexport function applyCssFrame(\n target: CssRendererTarget,\n src: string,\n frameIndex: number,\n rows: number,\n cols: number,\n width: number,\n height: number,\n): void {\n const { x, y } = getBackgroundPositionPercent(frameIndex, rows, cols);\n\n target.style.backgroundImage = `url(\"${src}\")`;\n target.style.backgroundRepeat = 'no-repeat';\n target.style.backgroundSize = `${cols * 100}% ${rows * 100}%`;\n target.style.backgroundPosition = `${x}% ${y}%`;\n target.style.width = `${width}px`;\n target.style.height = `${height}px`;\n target.style.display = 'inline-block';\n}\n\nexport function resetCssRenderer(target: CssRendererTarget): void {\n target.style.backgroundImage = '';\n}\n","import { SPRITE_ANIMATION_DEFAULTS } from './constants.js';\nimport { drawCanvasFrame } from './canvas-renderer.js';\nimport { applyCssFrame, resetCssRenderer } from './css-renderer.js';\nimport type { SpriteAnimationOptions, SpriteAnimationState } from './types.js';\nimport { getTotalFrames } from './utils.js';\n\ntype StateListener = (state: SpriteAnimationState) => void;\n\ntype ResolvedSpriteAnimationOptions = Required<\n Pick<SpriteAnimationOptions, 'src' | 'rows' | 'cols'>\n> &\n Required<Pick<SpriteAnimationOptions, 'fps' | 'loop' | 'width' | 'height' | 'autoPlay' | 'renderer'>> &\n Pick<SpriteAnimationOptions, 'onComplete' | 'onFrameChange'>;\n\nexport class SpriteAnimator {\n private options: ResolvedSpriteAnimationOptions;\n private currentFrame = 0;\n private isPlaying = false;\n private isLoaded = false;\n private rafId: number | null = null;\n private lastTimestamp = 0;\n private accumulatedTime = 0;\n private image: HTMLImageElement | null = null;\n private target: HTMLElement | HTMLCanvasElement | null = null;\n private listeners = new Set<StateListener>();\n private destroyed = false;\n\n constructor(options: SpriteAnimationOptions) {\n this.options = {\n ...SPRITE_ANIMATION_DEFAULTS,\n ...options,\n };\n this.loadImage();\n }\n\n attach(target: HTMLElement | HTMLCanvasElement): void {\n this.target = target;\n if (this.isLoaded) {\n this.render();\n }\n if (this.options.autoPlay) {\n this.play();\n }\n }\n\n play(): void {\n if (this.destroyed || this.isPlaying) return;\n this.isPlaying = true;\n this.lastTimestamp = 0;\n this.accumulatedTime = 0;\n this.rafId = requestAnimationFrame(this.tick);\n this.notify();\n }\n\n pause(): void {\n if (!this.isPlaying) return;\n this.isPlaying = false;\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n this.notify();\n }\n\n stop(): void {\n this.pause();\n this.currentFrame = 0;\n this.render();\n this.notify();\n }\n\n goToFrame(frame: number): void {\n const total = this.getTotalFrames();\n this.currentFrame = Math.max(0, Math.min(frame, total - 1));\n this.render();\n this.options.onFrameChange?.(this.currentFrame);\n this.notify();\n }\n\n getState(): SpriteAnimationState {\n return {\n currentFrame: this.currentFrame,\n totalFrames: this.getTotalFrames(),\n isPlaying: this.isPlaying,\n isLoaded: this.isLoaded,\n };\n }\n\n subscribe(listener: StateListener): () => void {\n this.listeners.add(listener);\n listener(this.getState());\n return () => this.listeners.delete(listener);\n }\n\n updateOptions(partial: Partial<SpriteAnimationOptions>): void {\n const prevSrc = this.options.src;\n const prevFps = this.options.fps;\n this.options = { ...this.options, ...partial };\n\n if (partial.src !== undefined && partial.src !== prevSrc) {\n this.loadImage();\n } else if (this.isLoaded) {\n this.render();\n }\n\n if (partial.fps !== undefined && partial.fps !== prevFps) {\n this.accumulatedTime = 0;\n }\n }\n\n destroy(): void {\n this.destroyed = true;\n this.pause();\n this.listeners.clear();\n if (this.target && this.options.renderer === 'css') {\n resetCssRenderer(this.target);\n }\n this.target = null;\n this.image = null;\n }\n\n private getTotalFrames(): number {\n return getTotalFrames(this.options.rows, this.options.cols);\n }\n\n private loadImage(): void {\n this.isLoaded = false;\n const img = new Image();\n img.crossOrigin = 'anonymous';\n img.onload = () => {\n if (this.destroyed) return;\n this.image = img;\n this.isLoaded = true;\n\n if (!this.options.width || !this.options.height) {\n const frameWidth = img.naturalWidth / this.options.cols;\n const frameHeight = img.naturalHeight / this.options.rows;\n this.options.width = frameWidth;\n this.options.height = frameHeight;\n }\n\n this.render();\n this.notify();\n\n if (this.options.autoPlay && this.target) {\n this.play();\n }\n };\n img.onerror = () => {\n console.error(`[SpriteAnimator] Failed to load image: ${this.options.src}`);\n };\n img.src = this.options.src;\n }\n\n private tick = (timestamp: number): void => {\n if (!this.isPlaying || this.destroyed) return;\n\n if (this.lastTimestamp === 0) {\n this.lastTimestamp = timestamp;\n }\n\n const delta = timestamp - this.lastTimestamp;\n this.lastTimestamp = timestamp;\n this.accumulatedTime += delta;\n\n const frameDuration = 1000 / this.options.fps;\n while (this.accumulatedTime >= frameDuration) {\n this.accumulatedTime -= frameDuration;\n this.advanceFrame();\n }\n\n this.rafId = requestAnimationFrame(this.tick);\n };\n\n private advanceFrame(): void {\n const total = this.getTotalFrames();\n const next = this.currentFrame + 1;\n\n if (next >= total) {\n if (this.options.loop) {\n this.currentFrame = 0;\n } else {\n this.currentFrame = total - 1;\n this.pause();\n this.options.onComplete?.();\n }\n } else {\n this.currentFrame = next;\n }\n\n this.render();\n this.options.onFrameChange?.(this.currentFrame);\n this.notify();\n }\n\n private render(): void {\n if (!this.target || !this.isLoaded) return;\n\n const { src, rows, cols, width, height, renderer } = this.options;\n\n if (renderer === 'canvas' && this.target instanceof HTMLCanvasElement && this.image) {\n drawCanvasFrame(this.target, this.image, this.currentFrame, rows, cols, width, height);\n } else if (renderer === 'css' && this.target instanceof HTMLElement) {\n applyCssFrame(this.target, src, this.currentFrame, rows, cols, width, height);\n }\n }\n\n private notify(): void {\n const state = this.getState();\n this.listeners.forEach((listener) => listener(state));\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/core/index.ts","../src/core/constants.ts","../src/core/utils.ts","../src/core/canvas-renderer.ts","../src/core/css-renderer.ts","../src/core/sprite-animator.ts"],"sourcesContent":["export { SPRITE_ANIMATION_DEFAULTS } from './constants.js';\nexport { SpriteAnimator } from './sprite-animator.js';\nexport type {\n FramePosition,\n RendererMode,\n SpriteAnimationOptions,\n SpriteAnimationState,\n SpriteSize,\n} from './types.js';\nexport {\n getBackgroundPositionPercent,\n getFramePosition,\n getTotalFrames,\n toCssLength,\n} from './utils.js';\n","import type { RendererMode } from './types.js';\n\nexport const SPRITE_ANIMATION_DEFAULTS = {\n fps: 12,\n loop: true,\n width: 128,\n height: 128,\n autoPlay: true,\n renderer: 'css' as RendererMode,\n} as const;\n","import type { FramePosition, SpriteSize } from './types.js';\n\n/** Converts a SpriteSize to a CSS length string. Numbers become `px`; strings pass through. */\nexport function toCssLength(size: SpriteSize): string {\n return typeof size === 'number' ? `${size}px` : size;\n}\n\nexport function getTotalFrames(rows: number, cols: number): number {\n return rows * cols;\n}\n\nexport function getFramePosition(frameIndex: number, cols: number): FramePosition {\n return {\n row: Math.floor(frameIndex / cols),\n col: frameIndex % cols,\n };\n}\n\nexport function getBackgroundPositionPercent(\n frameIndex: number,\n rows: number,\n cols: number,\n): { x: number; y: number } {\n const { row, col } = getFramePosition(frameIndex, cols);\n const x = cols <= 1 ? 0 : (col / (cols - 1)) * 100;\n const y = rows <= 1 ? 0 : (row / (rows - 1)) * 100;\n return { x, y };\n}\n","import { getFramePosition } from './utils.js';\n\nexport function drawCanvasFrame(\n canvas: HTMLCanvasElement,\n image: HTMLImageElement,\n frameIndex: number,\n rows: number,\n cols: number,\n): void {\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n const displayWidth = canvas.clientWidth;\n const displayHeight = canvas.clientHeight;\n if (displayWidth === 0 || displayHeight === 0) return;\n\n const dpr = window.devicePixelRatio || 1;\n const pixelWidth = Math.round(displayWidth * dpr);\n const pixelHeight = Math.round(displayHeight * dpr);\n\n if (canvas.width !== pixelWidth || canvas.height !== pixelHeight) {\n canvas.width = pixelWidth;\n canvas.height = pixelHeight;\n }\n\n const frameWidth = image.naturalWidth / cols;\n const frameHeight = image.naturalHeight / rows;\n const { row, col } = getFramePosition(frameIndex, cols);\n\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n ctx.clearRect(0, 0, displayWidth, displayHeight);\n ctx.drawImage(\n image,\n col * frameWidth,\n row * frameHeight,\n frameWidth,\n frameHeight,\n 0,\n 0,\n displayWidth,\n displayHeight,\n );\n}\n","import type { SpriteSize } from './types.js';\nimport { getBackgroundPositionPercent, toCssLength } from './utils.js';\n\nexport interface CssRendererTarget {\n style: CSSStyleDeclaration;\n}\n\nexport function applyCssFrame(\n target: CssRendererTarget,\n src: string,\n frameIndex: number,\n rows: number,\n cols: number,\n width: SpriteSize,\n height: SpriteSize,\n): void {\n const { x, y } = getBackgroundPositionPercent(frameIndex, rows, cols);\n\n target.style.backgroundImage = `url(\"${src}\")`;\n target.style.backgroundRepeat = 'no-repeat';\n target.style.backgroundSize = `${cols * 100}% ${rows * 100}%`;\n target.style.backgroundPosition = `${x}% ${y}%`;\n target.style.width = toCssLength(width);\n target.style.height = toCssLength(height);\n target.style.display = 'inline-block';\n}\n\nexport function resetCssRenderer(target: CssRendererTarget): void {\n target.style.backgroundImage = '';\n}\n","import { SPRITE_ANIMATION_DEFAULTS } from './constants.js';\nimport { drawCanvasFrame } from './canvas-renderer.js';\nimport { applyCssFrame, resetCssRenderer } from './css-renderer.js';\nimport type { SpriteAnimationOptions, SpriteAnimationState } from './types.js';\nimport { getTotalFrames, toCssLength } from './utils.js';\n\ntype StateListener = (state: SpriteAnimationState) => void;\n\ntype ResolvedSpriteAnimationOptions = Required<\n Pick<SpriteAnimationOptions, 'src' | 'rows' | 'cols'>\n> &\n Required<Pick<SpriteAnimationOptions, 'fps' | 'loop' | 'width' | 'height' | 'autoPlay' | 'renderer'>> &\n Pick<SpriteAnimationOptions, 'onComplete' | 'onFrameChange'>;\n\nexport class SpriteAnimator {\n private options: ResolvedSpriteAnimationOptions;\n private currentFrame = 0;\n private isPlaying = false;\n private isLoaded = false;\n private rafId: number | null = null;\n private lastTimestamp = 0;\n private accumulatedTime = 0;\n private image: HTMLImageElement | null = null;\n private target: HTMLElement | HTMLCanvasElement | null = null;\n private listeners = new Set<StateListener>();\n private destroyed = false;\n private resizeObserver: ResizeObserver | null = null;\n\n constructor(options: SpriteAnimationOptions) {\n this.options = {\n ...SPRITE_ANIMATION_DEFAULTS,\n ...options,\n };\n this.loadImage();\n }\n\n attach(target: HTMLElement | HTMLCanvasElement): void {\n this.target = target;\n this.applyCanvasDisplaySize();\n this.setupResizeObserver();\n if (this.isLoaded) {\n this.render();\n }\n if (this.options.autoPlay) {\n this.play();\n }\n }\n\n play(): void {\n if (this.destroyed || this.isPlaying) return;\n this.isPlaying = true;\n this.lastTimestamp = 0;\n this.accumulatedTime = 0;\n this.rafId = requestAnimationFrame(this.tick);\n this.notify();\n }\n\n pause(): void {\n if (!this.isPlaying) return;\n this.isPlaying = false;\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n this.notify();\n }\n\n stop(): void {\n this.pause();\n this.currentFrame = 0;\n this.render();\n this.notify();\n }\n\n goToFrame(frame: number): void {\n const total = this.getTotalFrames();\n this.currentFrame = Math.max(0, Math.min(frame, total - 1));\n this.render();\n this.options.onFrameChange?.(this.currentFrame);\n this.notify();\n }\n\n getState(): SpriteAnimationState {\n return {\n currentFrame: this.currentFrame,\n totalFrames: this.getTotalFrames(),\n isPlaying: this.isPlaying,\n isLoaded: this.isLoaded,\n };\n }\n\n subscribe(listener: StateListener): () => void {\n this.listeners.add(listener);\n listener(this.getState());\n return () => this.listeners.delete(listener);\n }\n\n updateOptions(partial: Partial<SpriteAnimationOptions>): void {\n const prevSrc = this.options.src;\n const prevFps = this.options.fps;\n const prevRenderer = this.options.renderer;\n this.options = { ...this.options, ...partial };\n\n if (partial.src !== undefined && partial.src !== prevSrc) {\n this.loadImage();\n } else if (this.isLoaded) {\n this.render();\n }\n\n if (partial.fps !== undefined && partial.fps !== prevFps) {\n this.accumulatedTime = 0;\n }\n\n if (partial.width !== undefined || partial.height !== undefined) {\n this.applyCanvasDisplaySize();\n }\n\n if (partial.renderer !== undefined && partial.renderer !== prevRenderer) {\n this.setupResizeObserver();\n }\n }\n\n destroy(): void {\n this.destroyed = true;\n this.pause();\n this.resizeObserver?.disconnect();\n this.resizeObserver = null;\n this.listeners.clear();\n if (this.target && this.options.renderer === 'css') {\n resetCssRenderer(this.target);\n }\n this.target = null;\n this.image = null;\n }\n\n private getTotalFrames(): number {\n return getTotalFrames(this.options.rows, this.options.cols);\n }\n\n private loadImage(): void {\n this.isLoaded = false;\n const img = new Image();\n img.crossOrigin = 'anonymous';\n img.onload = () => {\n if (this.destroyed) return;\n this.image = img;\n this.isLoaded = true;\n\n if (!this.options.width || !this.options.height) {\n const frameWidth = img.naturalWidth / this.options.cols;\n const frameHeight = img.naturalHeight / this.options.rows;\n this.options.width = frameWidth;\n this.options.height = frameHeight;\n }\n\n this.render();\n this.notify();\n\n if (this.options.autoPlay && this.target) {\n this.play();\n }\n };\n img.onerror = () => {\n console.error(`[SpriteAnimator] Failed to load image: ${this.options.src}`);\n };\n img.src = this.options.src;\n }\n\n private tick = (timestamp: number): void => {\n if (!this.isPlaying || this.destroyed) return;\n\n if (this.lastTimestamp === 0) {\n this.lastTimestamp = timestamp;\n }\n\n const delta = timestamp - this.lastTimestamp;\n this.lastTimestamp = timestamp;\n this.accumulatedTime += delta;\n\n const frameDuration = 1000 / this.options.fps;\n while (this.accumulatedTime >= frameDuration) {\n this.accumulatedTime -= frameDuration;\n this.advanceFrame();\n }\n\n this.rafId = requestAnimationFrame(this.tick);\n };\n\n private advanceFrame(): void {\n const total = this.getTotalFrames();\n const next = this.currentFrame + 1;\n\n if (next >= total) {\n if (this.options.loop) {\n this.currentFrame = 0;\n } else {\n this.currentFrame = total - 1;\n this.pause();\n this.options.onComplete?.();\n }\n } else {\n this.currentFrame = next;\n }\n\n this.render();\n this.options.onFrameChange?.(this.currentFrame);\n this.notify();\n }\n\n private render(): void {\n if (!this.target || !this.isLoaded) return;\n\n const { src, rows, cols, width, height, renderer } = this.options;\n\n if (renderer === 'canvas' && this.target instanceof HTMLCanvasElement && this.image) {\n drawCanvasFrame(this.target, this.image, this.currentFrame, rows, cols);\n } else if (renderer === 'css' && this.target instanceof HTMLElement) {\n applyCssFrame(this.target, src, this.currentFrame, rows, cols, width, height);\n }\n }\n\n private notify(): void {\n const state = this.getState();\n this.listeners.forEach((listener) => listener(state));\n }\n\n private applyCanvasDisplaySize(): void {\n if (this.options.renderer !== 'canvas' || !(this.target instanceof HTMLCanvasElement)) {\n return;\n }\n this.target.style.width = toCssLength(this.options.width);\n this.target.style.height = toCssLength(this.options.height);\n }\n\n private setupResizeObserver(): void {\n this.resizeObserver?.disconnect();\n this.resizeObserver = null;\n\n if (\n this.options.renderer !== 'canvas' ||\n !(this.target instanceof HTMLCanvasElement) ||\n typeof ResizeObserver === 'undefined'\n ) {\n return;\n }\n\n this.resizeObserver = new ResizeObserver(() => {\n if (this.isLoaded) {\n this.render();\n }\n });\n this.resizeObserver.observe(this.target);\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
type RendererMode = 'css' | 'canvas';
|
|
2
|
+
/** CSS length (e.g. `128`, `'8rem'`, `'50%'`, `'10vw'`) */
|
|
3
|
+
type SpriteSize = number | string;
|
|
2
4
|
interface SpriteAnimationOptions {
|
|
3
5
|
/** Sprite sheet image URL (PNG, WebP, etc.) */
|
|
4
6
|
src: string;
|
|
@@ -10,10 +12,10 @@ interface SpriteAnimationOptions {
|
|
|
10
12
|
fps?: number;
|
|
11
13
|
/** Whether to loop the animation (default: true) */
|
|
12
14
|
loop?: boolean;
|
|
13
|
-
/** Display width
|
|
14
|
-
width?:
|
|
15
|
-
/** Display height
|
|
16
|
-
height?:
|
|
15
|
+
/** Display width — number (px) or any CSS length (`rem`, `em`, `%`, `vw`, etc.) */
|
|
16
|
+
width?: SpriteSize;
|
|
17
|
+
/** Display height — number (px) or any CSS length (`rem`, `em`, `%`, `vw`, etc.) */
|
|
18
|
+
height?: SpriteSize;
|
|
17
19
|
/** Start playing automatically (default: true) */
|
|
18
20
|
autoPlay?: boolean;
|
|
19
21
|
/** Rendering mode: CSS background-position or Canvas (default: 'css') */
|
|
@@ -56,6 +58,7 @@ declare class SpriteAnimator {
|
|
|
56
58
|
private target;
|
|
57
59
|
private listeners;
|
|
58
60
|
private destroyed;
|
|
61
|
+
private resizeObserver;
|
|
59
62
|
constructor(options: SpriteAnimationOptions);
|
|
60
63
|
attach(target: HTMLElement | HTMLCanvasElement): void;
|
|
61
64
|
play(): void;
|
|
@@ -72,8 +75,12 @@ declare class SpriteAnimator {
|
|
|
72
75
|
private advanceFrame;
|
|
73
76
|
private render;
|
|
74
77
|
private notify;
|
|
78
|
+
private applyCanvasDisplaySize;
|
|
79
|
+
private setupResizeObserver;
|
|
75
80
|
}
|
|
76
81
|
|
|
82
|
+
/** Converts a SpriteSize to a CSS length string. Numbers become `px`; strings pass through. */
|
|
83
|
+
declare function toCssLength(size: SpriteSize): string;
|
|
77
84
|
declare function getTotalFrames(rows: number, cols: number): number;
|
|
78
85
|
declare function getFramePosition(frameIndex: number, cols: number): FramePosition;
|
|
79
86
|
declare function getBackgroundPositionPercent(frameIndex: number, rows: number, cols: number): {
|
|
@@ -81,4 +88,4 @@ declare function getBackgroundPositionPercent(frameIndex: number, rows: number,
|
|
|
81
88
|
y: number;
|
|
82
89
|
};
|
|
83
90
|
|
|
84
|
-
export { type FramePosition, type RendererMode, SPRITE_ANIMATION_DEFAULTS, type SpriteAnimationOptions, type SpriteAnimationState, SpriteAnimator, getBackgroundPositionPercent, getFramePosition, getTotalFrames };
|
|
91
|
+
export { type FramePosition, type RendererMode, SPRITE_ANIMATION_DEFAULTS, type SpriteAnimationOptions, type SpriteAnimationState, SpriteAnimator, type SpriteSize, getBackgroundPositionPercent, getFramePosition, getTotalFrames, toCssLength };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
type RendererMode = 'css' | 'canvas';
|
|
2
|
+
/** CSS length (e.g. `128`, `'8rem'`, `'50%'`, `'10vw'`) */
|
|
3
|
+
type SpriteSize = number | string;
|
|
2
4
|
interface SpriteAnimationOptions {
|
|
3
5
|
/** Sprite sheet image URL (PNG, WebP, etc.) */
|
|
4
6
|
src: string;
|
|
@@ -10,10 +12,10 @@ interface SpriteAnimationOptions {
|
|
|
10
12
|
fps?: number;
|
|
11
13
|
/** Whether to loop the animation (default: true) */
|
|
12
14
|
loop?: boolean;
|
|
13
|
-
/** Display width
|
|
14
|
-
width?:
|
|
15
|
-
/** Display height
|
|
16
|
-
height?:
|
|
15
|
+
/** Display width — number (px) or any CSS length (`rem`, `em`, `%`, `vw`, etc.) */
|
|
16
|
+
width?: SpriteSize;
|
|
17
|
+
/** Display height — number (px) or any CSS length (`rem`, `em`, `%`, `vw`, etc.) */
|
|
18
|
+
height?: SpriteSize;
|
|
17
19
|
/** Start playing automatically (default: true) */
|
|
18
20
|
autoPlay?: boolean;
|
|
19
21
|
/** Rendering mode: CSS background-position or Canvas (default: 'css') */
|
|
@@ -56,6 +58,7 @@ declare class SpriteAnimator {
|
|
|
56
58
|
private target;
|
|
57
59
|
private listeners;
|
|
58
60
|
private destroyed;
|
|
61
|
+
private resizeObserver;
|
|
59
62
|
constructor(options: SpriteAnimationOptions);
|
|
60
63
|
attach(target: HTMLElement | HTMLCanvasElement): void;
|
|
61
64
|
play(): void;
|
|
@@ -72,8 +75,12 @@ declare class SpriteAnimator {
|
|
|
72
75
|
private advanceFrame;
|
|
73
76
|
private render;
|
|
74
77
|
private notify;
|
|
78
|
+
private applyCanvasDisplaySize;
|
|
79
|
+
private setupResizeObserver;
|
|
75
80
|
}
|
|
76
81
|
|
|
82
|
+
/** Converts a SpriteSize to a CSS length string. Numbers become `px`; strings pass through. */
|
|
83
|
+
declare function toCssLength(size: SpriteSize): string;
|
|
77
84
|
declare function getTotalFrames(rows: number, cols: number): number;
|
|
78
85
|
declare function getFramePosition(frameIndex: number, cols: number): FramePosition;
|
|
79
86
|
declare function getBackgroundPositionPercent(frameIndex: number, rows: number, cols: number): {
|
|
@@ -81,4 +88,4 @@ declare function getBackgroundPositionPercent(frameIndex: number, rows: number,
|
|
|
81
88
|
y: number;
|
|
82
89
|
};
|
|
83
90
|
|
|
84
|
-
export { type FramePosition, type RendererMode, SPRITE_ANIMATION_DEFAULTS, type SpriteAnimationOptions, type SpriteAnimationState, SpriteAnimator, getBackgroundPositionPercent, getFramePosition, getTotalFrames };
|
|
91
|
+
export { type FramePosition, type RendererMode, SPRITE_ANIMATION_DEFAULTS, type SpriteAnimationOptions, type SpriteAnimationState, SpriteAnimator, type SpriteSize, getBackgroundPositionPercent, getFramePosition, getTotalFrames, toCssLength };
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,9 @@ var SPRITE_ANIMATION_DEFAULTS = {
|
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
// src/core/utils.ts
|
|
12
|
+
function toCssLength(size) {
|
|
13
|
+
return typeof size === "number" ? `${size}px` : size;
|
|
14
|
+
}
|
|
12
15
|
function getTotalFrames(rows, cols) {
|
|
13
16
|
return rows * cols;
|
|
14
17
|
}
|
|
@@ -26,15 +29,24 @@ function getBackgroundPositionPercent(frameIndex, rows, cols) {
|
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
// src/core/canvas-renderer.ts
|
|
29
|
-
function drawCanvasFrame(canvas, image, frameIndex, rows, cols
|
|
32
|
+
function drawCanvasFrame(canvas, image, frameIndex, rows, cols) {
|
|
30
33
|
const ctx = canvas.getContext("2d");
|
|
31
34
|
if (!ctx) return;
|
|
35
|
+
const displayWidth = canvas.clientWidth;
|
|
36
|
+
const displayHeight = canvas.clientHeight;
|
|
37
|
+
if (displayWidth === 0 || displayHeight === 0) return;
|
|
38
|
+
const dpr = window.devicePixelRatio || 1;
|
|
39
|
+
const pixelWidth = Math.round(displayWidth * dpr);
|
|
40
|
+
const pixelHeight = Math.round(displayHeight * dpr);
|
|
41
|
+
if (canvas.width !== pixelWidth || canvas.height !== pixelHeight) {
|
|
42
|
+
canvas.width = pixelWidth;
|
|
43
|
+
canvas.height = pixelHeight;
|
|
44
|
+
}
|
|
32
45
|
const frameWidth = image.naturalWidth / cols;
|
|
33
46
|
const frameHeight = image.naturalHeight / rows;
|
|
34
47
|
const { row, col } = getFramePosition(frameIndex, cols);
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
ctx.clearRect(0, 0, width, height);
|
|
48
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
49
|
+
ctx.clearRect(0, 0, displayWidth, displayHeight);
|
|
38
50
|
ctx.drawImage(
|
|
39
51
|
image,
|
|
40
52
|
col * frameWidth,
|
|
@@ -43,8 +55,8 @@ function drawCanvasFrame(canvas, image, frameIndex, rows, cols, width, height) {
|
|
|
43
55
|
frameHeight,
|
|
44
56
|
0,
|
|
45
57
|
0,
|
|
46
|
-
|
|
47
|
-
|
|
58
|
+
displayWidth,
|
|
59
|
+
displayHeight
|
|
48
60
|
);
|
|
49
61
|
}
|
|
50
62
|
|
|
@@ -55,8 +67,8 @@ function applyCssFrame(target, src, frameIndex, rows, cols, width, height) {
|
|
|
55
67
|
target.style.backgroundRepeat = "no-repeat";
|
|
56
68
|
target.style.backgroundSize = `${cols * 100}% ${rows * 100}%`;
|
|
57
69
|
target.style.backgroundPosition = `${x}% ${y}%`;
|
|
58
|
-
target.style.width =
|
|
59
|
-
target.style.height =
|
|
70
|
+
target.style.width = toCssLength(width);
|
|
71
|
+
target.style.height = toCssLength(height);
|
|
60
72
|
target.style.display = "inline-block";
|
|
61
73
|
}
|
|
62
74
|
function resetCssRenderer(target) {
|
|
@@ -76,6 +88,7 @@ var SpriteAnimator = class {
|
|
|
76
88
|
this.target = null;
|
|
77
89
|
this.listeners = /* @__PURE__ */ new Set();
|
|
78
90
|
this.destroyed = false;
|
|
91
|
+
this.resizeObserver = null;
|
|
79
92
|
this.tick = (timestamp) => {
|
|
80
93
|
if (!this.isPlaying || this.destroyed) return;
|
|
81
94
|
if (this.lastTimestamp === 0) {
|
|
@@ -99,6 +112,8 @@ var SpriteAnimator = class {
|
|
|
99
112
|
}
|
|
100
113
|
attach(target) {
|
|
101
114
|
this.target = target;
|
|
115
|
+
this.applyCanvasDisplaySize();
|
|
116
|
+
this.setupResizeObserver();
|
|
102
117
|
if (this.isLoaded) {
|
|
103
118
|
this.render();
|
|
104
119
|
}
|
|
@@ -152,6 +167,7 @@ var SpriteAnimator = class {
|
|
|
152
167
|
updateOptions(partial) {
|
|
153
168
|
const prevSrc = this.options.src;
|
|
154
169
|
const prevFps = this.options.fps;
|
|
170
|
+
const prevRenderer = this.options.renderer;
|
|
155
171
|
this.options = { ...this.options, ...partial };
|
|
156
172
|
if (partial.src !== void 0 && partial.src !== prevSrc) {
|
|
157
173
|
this.loadImage();
|
|
@@ -161,10 +177,18 @@ var SpriteAnimator = class {
|
|
|
161
177
|
if (partial.fps !== void 0 && partial.fps !== prevFps) {
|
|
162
178
|
this.accumulatedTime = 0;
|
|
163
179
|
}
|
|
180
|
+
if (partial.width !== void 0 || partial.height !== void 0) {
|
|
181
|
+
this.applyCanvasDisplaySize();
|
|
182
|
+
}
|
|
183
|
+
if (partial.renderer !== void 0 && partial.renderer !== prevRenderer) {
|
|
184
|
+
this.setupResizeObserver();
|
|
185
|
+
}
|
|
164
186
|
}
|
|
165
187
|
destroy() {
|
|
166
188
|
this.destroyed = true;
|
|
167
189
|
this.pause();
|
|
190
|
+
this.resizeObserver?.disconnect();
|
|
191
|
+
this.resizeObserver = null;
|
|
168
192
|
this.listeners.clear();
|
|
169
193
|
if (this.target && this.options.renderer === "css") {
|
|
170
194
|
resetCssRenderer(this.target);
|
|
@@ -222,7 +246,7 @@ var SpriteAnimator = class {
|
|
|
222
246
|
if (!this.target || !this.isLoaded) return;
|
|
223
247
|
const { src, rows, cols, width, height, renderer } = this.options;
|
|
224
248
|
if (renderer === "canvas" && this.target instanceof HTMLCanvasElement && this.image) {
|
|
225
|
-
drawCanvasFrame(this.target, this.image, this.currentFrame, rows, cols
|
|
249
|
+
drawCanvasFrame(this.target, this.image, this.currentFrame, rows, cols);
|
|
226
250
|
} else if (renderer === "css" && this.target instanceof HTMLElement) {
|
|
227
251
|
applyCssFrame(this.target, src, this.currentFrame, rows, cols, width, height);
|
|
228
252
|
}
|
|
@@ -231,12 +255,33 @@ var SpriteAnimator = class {
|
|
|
231
255
|
const state = this.getState();
|
|
232
256
|
this.listeners.forEach((listener) => listener(state));
|
|
233
257
|
}
|
|
258
|
+
applyCanvasDisplaySize() {
|
|
259
|
+
if (this.options.renderer !== "canvas" || !(this.target instanceof HTMLCanvasElement)) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
this.target.style.width = toCssLength(this.options.width);
|
|
263
|
+
this.target.style.height = toCssLength(this.options.height);
|
|
264
|
+
}
|
|
265
|
+
setupResizeObserver() {
|
|
266
|
+
this.resizeObserver?.disconnect();
|
|
267
|
+
this.resizeObserver = null;
|
|
268
|
+
if (this.options.renderer !== "canvas" || !(this.target instanceof HTMLCanvasElement) || typeof ResizeObserver === "undefined") {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
272
|
+
if (this.isLoaded) {
|
|
273
|
+
this.render();
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
this.resizeObserver.observe(this.target);
|
|
277
|
+
}
|
|
234
278
|
};
|
|
235
279
|
export {
|
|
236
280
|
SPRITE_ANIMATION_DEFAULTS,
|
|
237
281
|
SpriteAnimator,
|
|
238
282
|
getBackgroundPositionPercent,
|
|
239
283
|
getFramePosition,
|
|
240
|
-
getTotalFrames
|
|
284
|
+
getTotalFrames,
|
|
285
|
+
toCssLength
|
|
241
286
|
};
|
|
242
287
|
//# sourceMappingURL=index.js.map
|