首页
归档
笔记
树洞
搜索
友言

文章详情

Interesting People Record Interesting.

/ JavaScript / 文章详情

canvas刮一刮

Sonder
2022-04-19
5336字
13分钟
浏览 (2.8k)

效果:

5.gif
复制代码
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        body {
            margin: 0;
        }


        img {
            width: 400px;
            height: 300px;
            left: 200px;
            position: absolute;
            z-index: -1;
        }


        canvas {
            margin-left: 200px;
        }
    </style>
</head>


<body>
<!--https://segmentfault.com/a/1190000041087461-->
<img src="https://avatar-static.segmentfault.com/376/580/3765808389-619655a14ff04_huge128" alt="pic" />
<canvas id="canvas" width="400" height="300"></canvas>
<script>
    class Scratch {
        constructor(id, { maskColor = 'grey', cursorRadius = 10, maxEraseArea = 50, text = '',
            firstEraseCbk = () => { }, lastEraseCbk = () => { } } = {}) {
            this.canvasId = id;
            this.canvas = document.getElementById(id);
            this.context = this.canvas.getContext('2d');
            this.width = this.canvas.clientWidth;
            this.height = this.canvas.clientHeight;
            this.maskColor = maskColor; // 涂层颜色
            this.cursorRadius = cursorRadius; // 光标半径
            this.maxEraseArea = maxEraseArea; // 刮开多少后自动清空涂层
            this.text = text;
            this.firstEraseCbk = firstEraseCbk; // 第一次刮的回调函数
            this.lastEraseCbk = lastEraseCbk; // 刮开的回调函数
            this.currPerct = 0; // 当前刮开多少百分比
            this.done = false; // 是否刮完
            this.init();
        }
        init() {
            // 添加涂层
            this.addCoat();
            let bindEarse = this.erase.bind(this);
            this.canvas.addEventListener('mousedown', e => {
                if (this.done) {
                    return;
                }
                this.posX = e.clientX;
                this.posY = e.clientY;
                // 按下左键
                if (e.which === 1 && e.button === 0) {
                    // 擦掉涂层
                    this.canvas.addEventListener('mousemove', bindEarse);
                }
                if (this.currPerct === 0) {
                    this.firstEraseCbk();
                }
            })
            document.addEventListener('mouseup', e => {
                if (this.done) {
                    return;
                }
                if (e.target.id !== this.canvasId) {
                    return;
                }
                if (this.posX === e.clientX && this.posY === e.clientY) {
                    this.erase(e);
                }
                this.canvas.removeEventListener('mousemove', bindEarse);
                this.getScratchedPercentage();
                if (this.currPerct >= this.maxEraseArea) {
                    this.done = true;
                    requestAnimationFrame(this.fadeOut(255));
                    this.lastEraseCbk();
                }
            })
        }
        // 添加涂层
        addCoat() {
            this.context.beginPath();
            this.context.fillStyle = this.maskColor;
            this.context.fillRect(0, 0, this.width, this.height);
            // 绘制涂层上的文字
            if (this.text) {
                this.context.font = 'bold 48px serif';
                this.context.fillStyle = '#fff';
                this.context.textAlign = 'center';
                this.context.textBaseline = 'middle';
                this.context.fillText(this.text, this.width / 2, this.height / 2);
            }
        }
        // 擦除某位置涂层
        erase(e) {
            const x = e.clientX, y = e.clientY;
            this.context.globalCompositeOperation = 'destination-out';
            this.context.beginPath();
            this.context.arc(x - this.width / 2, y, this.cursorRadius, 0, Math.PI * 2);
            this.context.fill();
        }
        // 计算被擦除的部分占全部的百分比
        getScratchedPercentage() {
            const pixels = this.context.getImageData(0, 0, this.width, this.height).data;
            let transparentPixels = 0;
            for (let i = 0; i < pixels.length; i += 4) {
                if (pixels[i + 3] < 128) {
                    transparentPixels++;
                }
            }
            this.currPerct = (transparentPixels / pixels.length * 4 * 100).toFixed(2);
        }
        // 清空涂层时淡出效果
        fadeOut(alpha) {
            return () => {
                this.context.save();
                this.context.globalCompositeOperation = 'source-in';
                this.context.fillStyle = this.context.fillStyle + (alpha -= 1).toString(16);
                this.context.fillRect(0, 0, this.width, this.height);
                this.context.restore();
                // 到210已经看不到涂层了
                if (alpha > 210) {
                    requestAnimationFrame(this.fadeOut(alpha));
                }
            }
        }
    }
    new Scratch('canvas', { text: '刮一刮', maxEraseArea: 10 });
</script>
</body>


</html>
下一篇 / vue.js中封装全局filter

🎯 相关文章

💡 推荐文章

🕵️‍♂️ 评论 (0)