ScratchCard刮刮卡动画异常:清除覆盖层后需缩放才启动
刮刮卡清除后Sprite动画不触发问题排查与修复
问题原因
- 全局合成模式未重置:刮除操作时设置了
globalCompositeOperation = "destination-out",清除覆盖层后未恢复默认的source-over,导致后续动画的drawImage无法正常渲染内容。 - Sprite图片未预加载:
SpriteAnimation中的动画图片直接启动绘制流程,未等待图片加载完成,初始调用时图片资源尚未就绪,因此看不到动画;缩放屏幕时触发重绘,此时图片已加载完毕,动画才显示。 - Canvas边界值静态化:初始化时获取的
bounds是固定值,若元素位置或窗口大小变化,刮除坐标会出现偏差,间接影响触发逻辑的准确性。
解决方案
1. 重置Canvas合成模式
在清除覆盖层并启动动画前,将合成模式恢复为默认值:
checkIfRevealed() { const imageData = globalCtx.getImageData(0, 0, globalCanvas.width, globalCanvas.height); const pixels = imageData.data; let transparentPixels = 0; for (let i = 3; i < pixels.length; i += 4) { if (pixels[i] === 0) { transparentPixels++; } } const totalPixels = globalCanvas.width * globalCanvas.height; const revealPercentage = transparentPixels / totalPixels; if (revealPercentage >= this.revealThreshold && !this.overlayCleared) { this.overlayCleared = true; this.clearCanvas(); // 重置合成模式为默认 globalCtx.globalCompositeOperation = "source-over"; startSpriteAnimation(); } }
2. 等待Sprite图片加载完成再启动动画
修改SpriteAnimation类,添加图片加载完成判断:
class SpriteAnimation { constructor(canvas, ctx) { this.canvas = canvas; this.ctx = ctx; this.spriteWidth = 207; this.spriteHeight = 200; this.offsetX = (this.canvas.width - this.spriteWidth) / 2; this.offsetY = (this.canvas.height - this.spriteHeight) / 2; this.frame = 0; this.timer = 0; this.markedForDeletion = false; this.animationFrameId = null; this.ballAnimation = new Image(); this.ballAnimation.src = "./loading_animation.png"; this.isImageLoaded = false; // 监听图片加载完成事件 this.ballAnimation.onload = () => { this.isImageLoaded = true; }; } startAnimation() { this.animationFrameId = requestAnimationFrame(this.animate.bind(this)); } animate() { // 图片未加载完成时跳过绘制,继续请求帧 if (!this.isImageLoaded) { this.animationFrameId = requestAnimationFrame(this.animate.bind(this)); return; } this.timer++; if (this.timer % 5 === 0) { this.frame++; } if (this.frame > 8) { this.frame = 0; } this.draw(); this.animationFrameId = requestAnimationFrame(this.animate.bind(this)); } draw() { this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); this.ctx.drawImage( this.ballAnimation, this.frame * this.spriteWidth, 0, this.spriteWidth, this.spriteHeight, this.offsetX, this.offsetY, this.spriteWidth, this.spriteHeight ); } stopAnimation() { if (this.animationFrameId) { cancelAnimationFrame(this.animationFrameId); } } }
3. 实时更新Canvas边界值
在鼠标/触摸移动事件中实时获取元素边界,避免坐标偏移:
// 修改mousemove事件 globalCanvas.addEventListener("mousemove", (e) => { // 实时更新元素边界 this.bounds = globalCanvas.getBoundingClientRect(); const x = e.clientX - this.bounds.left; const y = e.clientY - this.bounds.top; if (this.isDrawing) { this.clearOverlay(e); } this.checkIfRevealed(); }); // 修改touchmove事件 globalCanvas.addEventListener("touchmove", (e) => { if (this.isDrawing) { // 实时更新元素边界 this.bounds = globalCanvas.getBoundingClientRect(); const touch = e.touches[0]; this.clearOverlay(touch); } this.checkIfRevealed(); });
内容的提问来源于stack exchange,提问作者georgio bejjani
相关产品推荐
相关产品推荐

