如何在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
相关产品推荐
相关产品推荐

