Animate WebP fallback on iOS via per-frame CanvasTexture
Three.js doesn't advance an animated-image Texture on iOS, so the WebP showed as a static frame. Draw the animating <img> into an offscreen canvas each frame and sample it through a CanvasTexture, keeping the ghost in 3D (bob/scale/lookAt) and animated, with transparency preserved.
This commit is contained in:
+27
-3
@@ -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 <img> 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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user