diff --git a/public/preview.html b/public/preview.html index 197753d..07a6747 100644 --- a/public/preview.html +++ b/public/preview.html @@ -246,13 +246,27 @@ const pr = vid.play(); if (pr && pr.catch) pr.catch(() => {}); group.userData.vidEl = vid; } else if (image) { + // Animated WebP (the iOS/WebKit fallback). Three.js won't advance an + // animated-image Texture on iOS, so instead we draw the into an + // offscreen canvas every frame and use a CanvasTexture. The browser keeps + // animating the WebP internally; drawImage() samples the current frame, so + // the texture animates and transparency is preserved. const img = document.createElement('img'); img.crossOrigin = 'anonymous'; img.src = image; - const tex = new THREE.Texture(img); - img.onload = () => { tex.needsUpdate = true; }; + const cnv = document.createElement('canvas'); + cnv.width = 256; cnv.height = 256; + const ctx = cnv.getContext('2d'); + const tex = new THREE.CanvasTexture(cnv); + tex.minFilter = THREE.LinearFilter; tex.magFilter = THREE.LinearFilter; tex.generateMipmaps = false; + img.onload = () => { + // size the canvas to the image's aspect once known + cnv.width = img.naturalWidth || 256; + cnv.height = img.naturalHeight || 256; + }; const mat = new THREE.MeshBasicMaterial({ map: tex, transparent: true, side: THREE.DoubleSide }); mesh = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), mat); - group.userData.gifTex = tex; group.userData.gifImg = img; + group.userData.canvasTex = tex; group.userData.canvasCtx = ctx; group.userData.canvasEl = cnv; + group.userData.animImg = img; } else { const geo = new THREE.SphereGeometry(0.4, 24, 24); const mat = new THREE.MeshStandardMaterial({ @@ -311,7 +325,17 @@ if (current) { current.position.y = Math.sin(t * 1.5 + current.userData.bobPhase) * 0.06; current.lookAt(camera.position); + // Desktop WebM-onerror image fallback (rare): refresh its texture. if (current.userData.gifTex && current.userData.gifImg?.complete) current.userData.gifTex.needsUpdate = true; + // Animated WebP path: redraw the current frame into the canvas texture + // so the animation plays (works on iOS, where image textures don't tick). + const ud = current.userData; + if (ud.canvasTex && ud.animImg?.complete && ud.animImg.naturalWidth) { + const c = ud.canvasEl; + ud.canvasCtx.clearRect(0, 0, c.width, c.height); + ud.canvasCtx.drawImage(ud.animImg, 0, 0, c.width, c.height); + ud.canvasTex.needsUpdate = true; + } } renderer.render(scene, camera); }