Pixel-art layer: animate multi-frame designs when embedded

A multi-frame pixel-art design imported as a layer into another
canvas (a cover, slide, custom-art, etc.) was rendering only its
first frame as a static image. Now it animates at its stored fps
+ loop settings, matching what the standalone preview already
does.

renderPixelArtIntoElement now branches on frames.length > 1:

- Multi-frame: pre-render every frame to a native-size canvas
  (cheap reuse of renderPixelGridToCanvas), then a RAF loop
  blits the current frame onto a display canvas, advancing by
  ts deltas to honour the source fps regardless of monitor
  refresh rate.
- The RAF self-stops when the display canvas leaves the DOM
  (cv.isConnected === false), so a re-render of the editor /
  preview pane doesn't leak loops. Same pattern as the
  standalone-design animation.
- Single-frame: unchanged static render.

Applies everywhere pixel-art-as-layer surfaces: the live editor
free-layer overlay, the preview pane in the community browser,
and any host design that picks up the layer.
This commit is contained in:
thePR0M3TH3AN committed 2026-05-31 09:18:41 -04:00
1 parent 95a39cc077
commit 1c205b3ea8
1 file changed
+50 -3
+50 -3
View File
@@ -11204,10 +11204,57 @@
// Render a pixel-art layer into its freeLayer element (crisp scaling).
function renderPixelArtIntoElement(el, layer) {
el.innerHTML = '';
const pa = layer.pixelArt || {};
try {
const cv = renderPixelGridToCanvas(layer.pixelArt || {});
cv.style.cssText = 'width:100%;height:100%;display:block;image-rendering:pixelated;image-rendering:crisp-edges;pointer-events:none';
el.appendChild(cv);
const frames = Array.isArray(pa.frames) ? pa.frames : [];
const animate = frames.length > 1;
if (animate) {
// Multi-frame pixel-art layer: pre-render every frame to its
// own native-size canvas, then animate by blitting onto a
// display canvas. Mirrors the standalone-preview animation
// path (renderPixelArtPreviewDOM). The RAF self-stops when
// the host canvas leaves the DOM (cv.isConnected === false)
// so a re-render of the editor doesn't leak loops.
const fps = Math.max(1, Math.min(60, (pa.fps | 0) || 8));
const loop = pa.loop !== false;
const w = (pa.width | 0) || 32;
const h = (pa.height | 0) || 32;
const cached = frames.map((_, i) => renderPixelGridToCanvas(pa, i));
const cv = document.createElement('canvas');
cv.width = w; cv.height = h;
cv.style.cssText = 'width:100%;height:100%;display:block;image-rendering:pixelated;image-rendering:crisp-edges;pointer-events:none';
const ctx = cv.getContext('2d');
ctx.drawImage(cached[0], 0, 0);
let frameIdx = 0;
let lastTs = 0, acc = 0;
const tick = (ts) => {
if (!cv.isConnected) return;
const dt = lastTs ? (ts - lastTs) : 0;
lastTs = ts;
acc += dt;
const msPerFrame = 1000 / fps;
let ended = false;
while (acc >= msPerFrame) {
acc -= msPerFrame;
const next = frameIdx + 1;
if (next >= cached.length) {
if (loop) frameIdx = 0;
else { frameIdx = cached.length - 1; ended = true; break; }
} else {
frameIdx = next;
}
}
ctx.clearRect(0, 0, w, h);
ctx.drawImage(cached[frameIdx], 0, 0);
if (!ended) requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
el.appendChild(cv);
} else {
const cv = renderPixelGridToCanvas(pa);
cv.style.cssText = 'width:100%;height:100%;display:block;image-rendering:pixelated;image-rendering:crisp-edges;pointer-events:none';
el.appendChild(cv);
}
} catch (e) {}
}