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 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
- Lightweight sprite sheet animation for **React**, **Vue**, and **Svelte** — one package, subpath imports.
3
+ [![npm version](https://img.shields.io/npm/v/mason-sprite.svg)](https://www.npmjs.com/package/mason-sprite)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./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
- import { Sprite } from 'mason-sprite/svelte';
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
- See the [repository README](https://github.com/FE-HyunSu/sprite-motion) for full docs.
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
- ## Migrating from @sprite-motion/*
138
+ ## Features
23
139
 
24
- This package replaces `@sprite-motion/core`, `@sprite-motion/react`, `@sprite-motion/vue`, and `@sprite-motion/svelte`.
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
- See [MIGRATION.md](https://github.com/FE-HyunSu/sprite-motion/blob/main/MIGRATION.md).
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, width, height) {
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
- canvas.width = width;
66
- canvas.height = height;
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
- width,
77
- height
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 = `${width}px`;
89
- target.style.height = `${height}px`;
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, width, height);
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
@@ -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 in pixels */
14
- width?: number;
15
- /** Display height in pixels */
16
- height?: number;
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 in pixels */
14
- width?: number;
15
- /** Display height in pixels */
16
- height?: number;
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, width, height) {
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
- canvas.width = width;
36
- canvas.height = height;
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
- width,
47
- height
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 = `${width}px`;
59
- target.style.height = `${height}px`;
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, width, height);
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