欢迎光临
我们一直在努力

救命!Canvas居然能做刮刮乐?复制就用,手残党也能拿捏

刮刮乐这玩意儿到底咋实现的?

其实这玩意儿原理简单到离谱 —— 就像你在奶茶杯上贴了层便利贴,底层是 “再来一杯”(中奖内容),上层是便利贴(遮挡层);你用指甲刮便利贴(鼠标 / 触摸),刮掉的地方就露出底下的字,Canvas 只是把 “便利贴” 换成了像素,把 “指甲” 换成了代码而已~

实现原理:
  • 画布分层:创建两个 canvas 元素(或一个 canvas 分两层绘制),底层渲染中奖内容(如 “恭喜中奖”“谢谢参与”),上层绘制遮挡层(灰色 / 自定义图片)。​
  • 交互监听:监听鼠标 mousedown(触摸 touchstart)、mousemove(触摸 touchmove)事件,记录用户滑动轨迹。​
  • 像素清除:在滑动轨迹处,用 clearRect 清除上层遮挡层的像素(或设置 globalCompositeOperation = ‘destination-out’ 实现透明擦除),露出底层内容。
  • 直接抄作业!完整可运行代码(复制 = 拥有)

    <!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>
    *{
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    }
    body {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    background: #f5f5f5;
    font-family: 'Arial', sans-serif;
    }
    h1 {
    color: #333;
    margin-bottom: 20px;
    font-size: 24px;
    }
    .scratch-container {
    position: relative;
    width: 300px;
    height: 150px;
    box-shadow: 0 0 10px rgba(0,0,0,0.2);
    border-radius: 8px;
    overflow: hidden;
    }
    /* 底层:中奖内容画布 */
    #prizeCanvas {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: #fff;
    }
    /* 上层:遮挡层画布(用户刮的层) */
    #scratchCanvas {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    cursor: pointer;
    /* 禁止选择,避免刮动时选中画布 */
    user-select: none;
    -webkit-user-select: none;
    }
    .tip {
    margin-top: 15px;
    color: #666;
    font-size: 14px;
    }
    button {
    margin-top: 20px;
    padding: 8px 20px;
    border: none;
    border-radius: 4px;
    background: #42b983;
    color: #fff;
    font-size: 16px;
    cursor: pointer;
    transition: background 0.3s;
    }
    button:hover {
    background: #359469;
    }
    </style>
    </head>
    <body>
    <h1>Canvas 刮刮乐</h1>
    <div class="scratch-container">
    <!– 底层:中奖内容 –>
    <canvas id="prizeCanvas" width="300" height="150"></canvas>
    <!– 上层:遮挡层 –>
    <canvas id="scratchCanvas" width="300" height="150"></canvas>
    </div>
    <p class="tip">按住鼠标/手指滑动刮开</p>
    <button id="resetBtn">重置刮刮乐</button>

    <script>
    // 1. 获取画布和上下文
    const prizeCanvas = document.getElementById('prizeCanvas');
    const scratchCanvas = document.getElementById('scratchCanvas');
    const prizeCtx = prizeCanvas.getContext('2d');
    const scratchCtx = scratchCanvas.getContext('2d');
    const resetBtn = document.getElementById('resetBtn');

    // 2. 初始化底层中奖内容
    function initPrize() {
    // 清空底层画布
    prizeCtx.clearRect(0, 0, prizeCanvas.width, prizeCanvas.height);
    // 设置中奖文字样式
    prizeCtx.font = 'bold 28px Arial';
    prizeCtx.fillStyle = '#e74c3c';
    prizeCtx.textAlign = 'center';
    prizeCtx.textBaseline = 'middle';
    // 绘制中奖文字(可替换为图片:prizeCtx.drawImage(img, 0, 0))
    prizeCtx.fillText('恭喜中奖 100元', prizeCanvas.width / 2, prizeCanvas.height / 2);
    }

    // 3. 初始化上层遮挡层
    function initScratch() {
    // 重置合成模式为默认值,确保背景填充正常
    scratchCtx.globalCompositeOperation = 'source-over';
    // 清空遮挡层
    scratchCtx.clearRect(0, 0, scratchCanvas.width, scratchCanvas.height);
    // 设置遮挡层背景(灰色,可替换为图片)
    scratchCtx.fillStyle = '#95a5a6';
    scratchCtx.fillRect(0, 0, scratchCanvas.width, scratchCanvas.height);
    // 关键:设置合成模式,后续绘制会清除原有像素(实现“擦除”效果)
    scratchCtx.globalCompositeOperation = 'destination-out';
    // 设置擦除笔触大小(刮开的痕迹宽度)
    scratchCtx.lineWidth = 20;
    scratchCtx.lineCap = 'round'; // 笔触圆润
    scratchCtx.lineJoin = 'round';
    }

    // 4. 交互逻辑:监听鼠标/触摸事件
    let isScratching = false; // 是否正在刮动

    // 鼠标事件
    scratchCanvas.addEventListener('mousedown', startScratch);
    scratchCanvas.addEventListener('mousemove', scratch);
    scratchCanvas.addEventListener('mouseup', endScratch);
    scratchCanvas.addEventListener('mouseleave', endScratch);

    // 触摸事件(适配移动端)
    scratchCanvas.addEventListener('touchstart', (e) => {
    e.preventDefault(); // 阻止默认滚动行为
    startScratch(e.touches[0]);
    });
    scratchCanvas.addEventListener('touchmove', (e) => {
    e.preventDefault();
    scratch(e.touches[0]);
    });
    scratchCanvas.addEventListener('touchend', endScratch);
    scratchCanvas.addEventListener('touchcancel', endScratch);

    // 开始刮动:记录起始位置
    function startScratch(e) {
    isScratching = true;
    const { x, y } = getCanvasPosition(e);
    scratchCtx.beginPath();
    scratchCtx.moveTo(x, y);
    }

    // 刮动中:绘制轨迹(清除像素)
    function scratch(e) {
    if (!isScratching) return;
    const { x, y } = getCanvasPosition(e);
    scratchCtx.lineTo(x, y);
    scratchCtx.stroke(); // 绘制线条,由于合成模式为 destination-out,会清除对应像素
    }

    // 结束刮动
    function endScratch() {
    isScratching = false;
    // 可选:检测刮开面积,达到阈值自动显示全部内容
    checkScratchProgress();
    }

    // 计算鼠标/触摸点在画布上的相对位置(解决 canvas 宽高缩放问题)
    function getCanvasPosition(e) {
    const rect = scratchCanvas.getBoundingClientRect();
    return {
    x: (e.clientX rect.left) * (scratchCanvas.width / rect.width),
    y: (e.clientY rect.top) * (scratchCanvas.height / rect.height)
    };
    }

    // 检测刮开进度(可选功能)
    function checkScratchProgress() {
    const imageData = scratchCtx.getImageData(0, 0, scratchCanvas.width, scratchCanvas.height);
    const pixels = imageData.data;
    let transparentPixels = 0; // 透明像素数(已刮开的)
    const totalPixels = pixels.length / 4; // 总像素数(RGBA 四个通道为一个像素)

    // 遍历所有像素,统计 alpha 通道为 0 的像素(透明)
    for (let i = 3; i < pixels.length; i += 4) {
    if (pixels[i] === 0) {
    transparentPixels++;
    }
    }

    // 刮开面积超过 70% 自动显示全部
    if (transparentPixels / totalPixels > 0.7) {
    scratchCtx.clearRect(0, 0, scratchCanvas.width, scratchCanvas.height);
    }
    }

    // 重置刮刮乐
    resetBtn.addEventListener('click', () => {
    initScratch();
    });

    // 页面加载时初始化
    window.addEventListener('load', () => {
    initPrize();
    initScratch();
    });
    </script>
    </body>
    </html>

    为啥这代码这么牛?(亮点速看)

  • 画布坐标适配:​
    由于 canvas 的 width/height 属性(绘图分辨率)和 CSS 宽高(显示尺寸)可能不一致,必须通过 getBoundingClientRect() 计算鼠标 / 触摸点的相对位置,否则刮动轨迹会偏移。​
  • 合成模式 destination-out:​
    这是实现 “擦除” 的核心!默认合成模式是 source-over(新内容覆盖旧内容),而 destination-out 会让新绘制的内容 “清除” 底层内容(像素变透明),比 clearRect 更灵活(支持任意形状的擦除轨迹)。​
  • 移动端适配:​
    必须监听 touchstart/touchmove/touchend 事件,并通过 e.touches[0] 获取触摸点坐标,同时阻止默认滚动行为(e.preventDefault()),否则移动端会出现滚动冲突。​
  • 性能优化:​
    1)笔触大小 lineWidth 不宜过大(建议 15-30),避免绘制压力;​
    2)检测刮开进度时,遍历像素是耗时操作,可降低检测频率(如每 500ms 检测一次)。​
  • 扩展方向:​
    1)底层内容替换:将文字改为图片(prizeCtx.drawImage(img, 0, 0));​
    2)遮挡层替换:用图片作为遮挡层(scratchCtx.drawImage(coverImg, 0, 0));​
    3)随机中奖内容:通过数组随机选择中奖文案 / 图片;​
    4)动画效果:刮开进度达标时添加淡入动画。
  • 赞(0)
    未经允许不得转载:171主机测评 » 救命!Canvas居然能做刮刮乐?复制就用,手残党也能拿捏
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址