You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ScratchCard刮刮卡动画异常:清除覆盖层后需缩放才启动

刮刮卡清除后Sprite动画不触发问题排查与修复

问题原因

  1. 全局合成模式未重置:刮除操作时设置了globalCompositeOperation = "destination-out",清除覆盖层后未恢复默认的source-over,导致后续动画的drawImage无法正常渲染内容。
  2. Sprite图片未预加载:SpriteAnimation中的动画图片直接启动绘制流程,未等待图片加载完成,初始调用时图片资源尚未就绪,因此看不到动画;缩放屏幕时触发重绘,此时图片已加载完毕,动画才显示。
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 15:22:49