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

如何在Canvas实现点击底部栏产品触发开门动画并放大展示商品细节

在Canvas中实现点击产品触发开门动画与商品放大展示的方案

核心思路拆解

  • 分阶段执行动画:先完成开门过渡,再启动商品陈列区展示+细节放大效果
  • 依赖Canvas状态管理(保存/恢复)与帧动画API(requestAnimationFrame)控制动画流程
  • 绑定底部产品点击事件,触发预设的动画序列

具体实现步骤

1. 初始化Canvas基础配置

先搭建画布环境,定义动画进度、元素参数等核心变量:

const canvas = document.getElementById('shopCanvas');
const ctx = canvas.getContext('2d');

// 动画进度控制(0为初始状态,1为完成状态)
let doorOpenProgress = 0;
let productShowProgress = 0;
let currentProduct = null; // 存储选中商品的图片数据

// 门的尺寸与初始位置
const doorWidth = 200;
const doorHeight = 400;
const doorCenterX = 300;

// 商品陈列区域参数
const displayArea = { x: 250, y: 100, width: 300, height: 350 };

2. 绑定底部产品点击事件

给底部栏的产品元素绑定点击逻辑,触发动画序列:

// 假设底部产品元素带有product-item类名,且通过data属性存储图片地址
document.querySelectorAll('.product-item').forEach(item => {
  item.addEventListener('click', () => {
    currentProduct = {
      mainImg: new Image(),
      detailImg: new Image()
    };
    currentProduct.mainImg.src = item.dataset.mainImg;
    currentProduct.detailImg.src = item.dataset.detailImg;
    
    // 重置动画进度并启动序列
    doorOpenProgress = 0;
    productShowProgress = 0;
    startAnimationSequence();
  });
});

3. 实现开门动画逻辑

通过逐帧更新门的位置,模拟左右门向两侧打开的效果:

function animateDoor() {
  if (doorOpenProgress >= 1) return;
  
  // 控制动画速度,数值越大动画越快
  doorOpenProgress += 0.02;
  // 清除门所在区域的画布内容
  ctx.clearRect(doorCenterX - doorWidth, 0, doorWidth * 2, doorHeight);
  
  // 绘制左侧门
  ctx.fillStyle = '#8B4513';
  ctx.fillRect(doorCenterX - doorWidth * doorOpenProgress, 0, doorWidth, doorHeight);
  // 绘制右侧门
  ctx.fillRect(doorCenterX + doorWidth * (1 - doorOpenProgress), 0, doorWidth, doorHeight);
  
  requestAnimationFrame(animateDoor);
}

4. 实现商品展示与细节放大动画

开门完成后,执行商品的淡入+缩放放大效果,叠加细节图:

function animateProduct() {
  if (productShowProgress >= 1) return;
  
  productShowProgress += 0.015;
  // 清除陈列区画布内容
  ctx.clearRect(displayArea.x, displayArea.y, displayArea.width, displayArea.height);
  
  // 计算商品缩放比例(从0.5到1.2,实现放大效果)
  const scale = 0.5 + productShowProgress * 0.7;
  const productX = displayArea.x + (displayArea.width - currentProduct.mainImg.width * scale) / 2;
  const productY = displayArea.y + (displayArea.height - currentProduct.mainImg.height * scale) / 2;
  
  // 保存画布状态,应用淡入与缩放
  ctx.save();
  ctx.globalAlpha = productShowProgress;
  ctx.scale(scale, scale);
  ctx.drawImage(currentProduct.mainImg, productX / scale, productY / scale);
  ctx.restore();
  
  // 叠加细节放大图(动画过半后开始显示)
  if (productShowProgress > 0.5) {
    const detailScale = 1 + (productShowProgress - 0.5) * 0.5;
    const detailX = displayArea.x + displayArea.width - 150;
    const detailY = displayArea.y + 50;
    
    ctx.save();
    ctx.globalAlpha = (productShowProgress - 0.5) * 2;
    ctx.scale(detailScale, detailScale);
    ctx.drawImage(currentProduct.detailImg, detailX / detailScale, detailY / detailScale);
    ctx.restore();
  }
  
  requestAnimationFrame(animateProduct);
}

5. 串联动画序列

确保开门动画完成后再启动商品展示动画:

function startAnimationSequence() {
  animateDoor();
  
  // 监听开门动画完成状态,启动商品动画
  const checkDoorStatus = setInterval(() => {
    if (doorOpenProgress >= 1) {
      clearInterval(checkDoorStatus);
      animateProduct();
    }
  }, 16); // 匹配60帧每秒的间隔
}

6. 优化建议

  • 预加载图片:页面初始化时预加载所有商品主图与细节图,避免动画卡顿
    document.querySelectorAll('.product-item').forEach(item => {
      const mainImg = new Image();
      mainImg.src = item.dataset.mainImg;
      const detailImg = new Image();
      detailImg.src = item.dataset.detailImg;
    });
    
  • 添加缓动函数:用缓动公式优化动画曲线,让过渡更自然
    function easeOutQuad(t) {
      return t * (2 - t);
    }
    // 使用时替换进度值:const easedProgress = easeOutQuad(doorOpenProgress);
    

内容的提问来源于stack exchange,提问作者Muhtasim Jeet

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 04:35:25