温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

JS如何实现canvas仿ps橡皮擦刮卡效果

发布时间:2021-11-22 12:27:18 来源:亿速云 阅读:312 作者:小新 栏目:开发技术
# JS如何实现canvas仿PS橡皮擦刮卡效果 ## 一、效果概述与实现原理 ### 1.1 什么是刮卡效果 刮卡效果是一种模拟现实世界中刮奖卡的交互体验,用户通过鼠标或触摸操作"刮开"表层涂层,露出下方隐藏内容。在Web开发中,这种效果常见于营销活动、游戏验证等场景。 ### 1.2 核心实现原理 Canvas实现刮卡效果主要依赖以下技术点: - 使用`globalCompositeOperation`设置混合模式 - 通过鼠标/触摸事件获取绘制路径 - 利用`clip`或`clearRect`实现擦除效果 - 性能优化处理大面积擦除情况 ### 1.3 与传统PS橡皮擦的异同 | 特性 | PS橡皮擦 | Canvas橡皮擦 | |------------|-----------------------|-----------------------| | 实现方式 | 像素级修改 | 路径绘制+混合模式 | | 精度控制 | 可精细调节 | 依赖绘制路径密度 | | 撤销功能 | 完整历史记录 | 需手动实现状态管理 | | 性能影响 | 局部重绘 | 全图层重绘 | ## 二、基础实现步骤 ### 2.1 初始化Canvas环境 ```html <canvas id="scratchCanvas" width="500" height="300"></canvas> 
const canvas = document.getElementById('scratchCanvas'); const ctx = canvas.getContext('2d'); // 设置涂层和底图 function initCanvas() { // 绘制底层内容(奖品信息) drawPrize(); // 绘制覆盖层 drawCover(); } function drawPrize() { ctx.fillStyle = '#f5f5f5'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.font = '24px Arial'; ctx.fillStyle = '#333'; ctx.textAlign = 'center'; ctx.fillText('恭喜获得一等奖!', canvas.width/2, canvas.height/2); } function drawCover() { ctx.fillStyle = '#999'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.font = '16px Arial'; ctx.fillStyle = '#fff'; ctx.fillText('刮开涂层查看奖品', canvas.width/2, canvas.height/2 + 30); } 

2.2 实现擦除功能

let isDrawing = false; // 鼠标事件监听 canvas.addEventListener('mousedown', startDrawing); canvas.addEventListener('mousemove', draw); canvas.addEventListener('mouseup', stopDrawing); canvas.addEventListener('mouseout', stopDrawing); function startDrawing(e) { isDrawing = true; draw(e); } function draw(e) { if (!isDrawing) return; const rect = canvas.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; // 设置混合模式为destination-out ctx.globalCompositeOperation = 'destination-out'; ctx.beginPath(); ctx.arc(x, y, 15, 0, Math.PI * 2); ctx.fill(); } function stopDrawing() { isDrawing = false; } 

2.3 触摸屏适配

// 触摸事件支持 canvas.addEventListener('touchstart', handleTouch); canvas.addEventListener('touchmove', handleTouch); function handleTouch(e) { e.preventDefault(); const touch = e.touches[0]; const mouseEvent = new MouseEvent( e.type === 'touchstart' ? 'mousedown' : 'mousemove', { clientX: touch.clientX, clientY: touch.clientY } ); canvas.dispatchEvent(mouseEvent); } 

三、高级优化技巧

3.1 使用路径绘制提高性能

let lastX = 0; let lastY = 0; function draw(e) { if (!isDrawing) return; const rect = canvas.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; ctx.globalCompositeOperation = 'destination-out'; ctx.lineWidth = 30; ctx.lineCap = 'round'; ctx.lineJoin = 'round'; ctx.beginPath(); ctx.moveTo(lastX, lastY); ctx.lineTo(x, y); ctx.stroke(); lastX = x; lastY = y; } 

3.2 添加刮卡百分比计算

function calculateScratchedPercentage() { const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const pixels = imageData.data; let transparentPixels = 0; for (let i = 3; i < pixels.length; i += 4) { if (pixels[i] === 0) { transparentPixels++; } } return (transparentPixels / (canvas.width * canvas.height)) * 100; } // 在draw函数中调用 if (calculateScratchedPercentage() > 60) { ctx.clearRect(0, 0, canvas.width, canvas.height); drawPrize(); } 

3.3 使用离屏Canvas优化

const offscreenCanvas = document.createElement('canvas'); offscreenCanvas.width = canvas.width; offscreenCanvas.height = canvas.height; const offscreenCtx = offscreenCanvas.getContext('2d'); // 初始化时绘制到离屏Canvas function initCanvas() { drawPrize(); offscreenCtx.fillStyle = '#999'; offscreenCtx.fillRect(0, 0, canvas.width, canvas.height); } // 修改draw函数 function draw(e) { // ...获取坐标逻辑不变 // 在离屏Canvas上绘制 offscreenCtx.globalCompositeOperation = 'destination-out'; offscreenCtx.beginPath(); offscreenCtx.arc(x, y, 15, 0, Math.PI * 2); offscreenCtx.fill(); // 将离屏内容绘制到主Canvas ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(offscreenCanvas, 0, 0); } 

四、视觉效果增强

4.1 添加纹理效果

function drawCover() { // 创建纹理 const patternCanvas = document.createElement('canvas'); patternCanvas.width = 20; patternCanvas.height = 20; const patternCtx = patternCanvas.getContext('2d'); patternCtx.fillStyle = '#888'; patternCtx.fillRect(0, 0, 20, 20); patternCtx.fillStyle = '#aaa'; for (let i = 0; i < 20; i += 4) { patternCtx.fillRect(i, 0, 2, 20); } const pattern = ctx.createPattern(patternCanvas, 'repeat'); ctx.fillStyle = pattern; ctx.fillRect(0, 0, canvas.width, canvas.height); } 

4.2 实现粒子飞溅效果

class Particle { constructor(x, y) { this.x = x; this.y = y; this.size = Math.random() * 3 + 2; this.speedX = Math.random() * 4 - 2; this.speedY = Math.random() * 4 - 2; this.alpha = 1; } update() { this.x += this.speedX; this.y += this.speedY; this.alpha -= 0.03; } draw() { ctx.save(); ctx.globalAlpha = this.alpha; ctx.fillStyle = '#999'; ctx.beginPath(); ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); ctx.fill(); ctx.restore(); } } let particles = []; function createParticles(x, y, count) { for (let i = 0; i < count; i++) { particles.push(new Particle(x, y)); } } function animateParticles() { for (let i = 0; i < particles.length; i++) { particles[i].update(); particles[i].draw(); if (particles[i].alpha <= 0) { particles.splice(i, 1); i--; } } if (particles.length > 0) { requestAnimationFrame(animateParticles); } } // 修改draw函数 function draw(e) { // ...原有逻辑 // 添加粒子效果 createParticles(x, y, 5); if (particles.length === 5) { animateParticles(); } } 

五、完整实现代码

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Canvas刮卡效果</title> <style> body { display: flex; justify-content: center; align-items: center; height: 100vh; background-color: #f0f0f0; font-family: Arial, sans-serif; } #scratchCanvas { box-shadow: 0 4px 8px rgba(0,0,0,0.2); border-radius: 8px; cursor: crosshair; } .container { text-align: center; } .info { margin-top: 20px; color: #666; } </style> </head> <body> <div class="container"> <canvas id="scratchCanvas" width="400" height="200"></canvas> <p class="info">按住鼠标拖动刮开涂层</p> </div> <script> const canvas = document.getElementById('scratchCanvas'); const ctx = canvas.getContext('2d'); let isDrawing = false; let lastX = 0; let lastY = 0; const particles = []; class Particle { constructor(x, y) { this.x = x; this.y = y; this.size = Math.random() * 3 + 2; this.speedX = Math.random() * 4 - 2; this.speedY = Math.random() * 4 - 2; this.alpha = 1; } update() { this.x += this.speedX; this.y += this.speedY; this.alpha -= 0.03; } draw() { ctx.save(); ctx.globalAlpha = this.alpha; ctx.fillStyle = '#999'; ctx.beginPath(); ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); ctx.fill(); ctx.restore(); } } function initCanvas() { // 绘制底层内容 ctx.fillStyle = '#f5f5f5'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.font = '24px Arial'; ctx.fillStyle = '#e74c3c'; ctx.textAlign = 'center'; ctx.fillText('恭喜获得50元优惠券!', canvas.width/2, canvas.height/2 - 10); ctx.font = '16px Arial'; ctx.fillStyle = '#7f8c8d'; ctx.fillText('有效期至2023-12-31', canvas.width/2, canvas.height/2 + 20); // 绘制覆盖层 drawCover(); } function drawCover() { // 创建纹理 const patternCanvas = document.createElement('canvas'); patternCanvas.width = 20; patternCanvas.height = 20; const patternCtx = patternCanvas.getContext('2d'); patternCtx.fillStyle = '#95a5a6'; patternCtx.fillRect(0, 0, 20, 20); patternCtx.fillStyle = '#bdc3c7'; for (let i = 0; i < 20; i += 4) { patternCtx.fillRect(i, 0, 2, 20); } const pattern = ctx.createPattern(patternCanvas, 'repeat'); ctx.fillStyle = pattern; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.font = '18px Arial'; ctx.fillStyle = '#fff'; ctx.textAlign = 'center'; ctx.fillText('刮开此处查看奖品', canvas.width/2, canvas.height/2); } function createParticles(x, y, count) { for (let i = 0; i < count; i++) { particles.push(new Particle(x, y)); } } function animateParticles() { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(offscreenCanvas, 0, 0); for (let i = 0; i < particles.length; i++) { particles[i].update(); particles[i].draw(); if (particles[i].alpha <= 0) { particles.splice(i, 1); i--; } } if (particles.length > 0) { requestAnimationFrame(animateParticles); } } const offscreenCanvas = document.createElement('canvas'); offscreenCanvas.width = canvas.width; offscreenCanvas.height = canvas.height; const offscreenCtx = offscreenCanvas.getContext('2d'); // 初始化离屏Canvas function initOffscreenCanvas() { offscreenCtx.fillStyle = '#f5f5f5'; offscreenCtx.fillRect(0, 0, canvas.width, canvas.height); offscreenCtx.font = '24px Arial'; offscreenCtx.fillStyle = '#e74c3c'; offscreenCtx.textAlign = 'center'; offscreenCtx.fillText('恭喜获得50元优惠券!', canvas.width/2, canvas.height/2 - 10); offscreenCtx.font = '16px Arial'; offscreenCtx.fillStyle = '#7f8c8d'; offscreenCtx.fillText('有效期至2023-12-31', canvas.width/2, canvas.height/2 + 20); drawCover(); } function startDrawing(e) { isDrawing = true; const rect = canvas.getBoundingClientRect(); lastX = e.clientX - rect.left; lastY = e.clientY - rect.top; } function draw(e) { if (!isDrawing) return; const rect = canvas.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; offscreenCtx.globalCompositeOperation = 'destination-out'; offscreenCtx.lineWidth = 20; offscreenCtx.lineCap = 'round'; offscreenCtx.lineJoin = 'round'; offscreenCtx.beginPath(); offscreenCtx.moveTo(lastX, lastY); offscreenCtx.lineTo(x, y); offscreenCtx.stroke(); ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(offscreenCanvas, 0, 0); // 添加粒子效果 createParticles(x, y, 3); if (particles.length === 3) { animateParticles(); } lastX = x; lastY = y; // 检查刮开比例 if (calculateScratchedPercentage() > 60) { ctx.clearRect(0, 0, canvas.width, canvas.height); offscreenCtx.fillStyle = '#f5f5f5'; offscreenCtx.fillRect(0, 0, canvas.width, canvas.height); offscreenCtx.font = '24px Arial'; offscreenCtx.fillStyle = '#e74c3c'; offscreenCtx.textAlign = 'center'; offscreenCtx.fillText('恭喜获得50元优惠券!', canvas.width/2, canvas.height/2 - 10); offscreenCtx.font = '16px Arial'; offscreenCtx.fillStyle = '#7f8c8d'; offscreenCtx.fillText('有效期至2023-12-31', canvas.width/2, canvas.height/2 + 20); ctx.drawImage(offscreenCanvas, 0, 0); } } function calculateScratchedPercentage() { const imageData = offscreenCtx.getImageData(0, 0, canvas.width, canvas.height); const pixels = imageData.data; let transparentPixels = 0; for (let i = 3; i < pixels.length; i += 4) { if (pixels[i] === 0) { transparentPixels++; } } return (transparentPixels / (canvas.width * canvas.height)) * 100; } function stopDrawing() { isDrawing = false; } // 触摸事件支持 canvas.addEventListener('touchstart', function(e) { e.preventDefault(); const touch = e.touches[0]; const mouseEvent = new MouseEvent('mousedown', { clientX: touch.clientX, clientY: touch.clientY }); canvas.dispatchEvent(mouseEvent); }); canvas.addEventListener('touchmove', function(e) { e.preventDefault(); const touch = e.touches[0]; const mouseEvent = new MouseEvent('mousemove', { clientX: touch.clientX, clientY: touch.clientY }); canvas.dispatchEvent(mouseEvent); }); // 鼠标事件监听 canvas.addEventListener('mousedown', startDrawing); canvas.addEventListener('mousemove', draw); canvas.addEventListener('mouseup', stopDrawing); canvas.addEventListener('mouseout', stopDrawing); // 初始化 initOffscreenCanvas(); initCanvas(); </script> </body> </html> 

六、性能优化与兼容

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI