1.刮刮乐(橡皮擦)成果的外围api

ctx.globalCompositeOperation = type;

设置要在绘制新形态时利用的合成操作的类型。
咱们这里须要用到的类型是 destination-out

此属性的详细信息:MDN文档

2.根底版刮刮乐性能

canvs 笼罩在图片上

<style>    body {      margin: 0;    }    img {      width: 400px;      height: 300px;      left: 200px;      position: absolute;      z-index: -1;    }    canvas {      margin-left: 200px;    }  </style>    <img src="./test.jpg" alt="pic"/>  <canvas id="canvas" width="400" height="300"></canvas>
<script>   let canvas = document.querySelector('#canvas');   let context = canvas.getContext('2d');   // 绘制涂层   context.beginPath();   context.fillStyle = 'grey';   context.fillRect(0, 0, 400, 300);   // 监听鼠标挪动事件   canvas.addEventListener('mousemove', (e) => {    // 当鼠标左键按下&&挪动鼠标时,革除鼠标左近涂层    if (e.which === 1 && e.button === 0) {      const x = e.clientX, y = e.clientY;      context.globalCompositeOperation = 'destination-out';      context.beginPath();      // 革除以鼠标地位为圆心,半径为10px的圆的范畴      context.arc(x - 200, y, 10, 0, Math.PI * 2);      context.fill();    }   })  </script>

3.进阶版刮刮乐性能

进阶性能:
点击时,以以后地位为圆心刮开一部分区域;
刮开x百分比(能够自定义)后后显示全副,并且应用动画逐步变淡;
调用第一次刮的回调办法和刮完的回调办法,能够传入或不传;
涂层下面能够显示自定义文字;

首先改为class模式,不便屡次创立刮刮乐。

class Scratch {      constructor(id, { maskColor = 'grey', cursorRadius = 10 } = {}) {        this.canvas = document.getElementById('canvas');        this.context = this.canvas.getContext('2d');        this.width = this.canvas.clientWidth;        this.height = this.canvas.clientHeight;        this.maskColor = maskColor; // 涂层色彩        this.cursorRadius = cursorRadius; // 光标半径        this.init();      }      init() {        // 增加涂层        this.addCoat();        let bindEarse = this.erase.bind(this);        this.canvas.addEventListener('mousedown', (e) => {          // 按下左键          if (e.which === 1 && e.button === 0) {            // 擦掉涂层            this.canvas.addEventListener('mousemove', bindEarse);          }        })        document.addEventListener('mouseup', () => {          this.canvas.removeEventListener('mousemove', bindEarse);        })      }      addCoat() {        this.context.beginPath();        this.context.fillStyle = this.maskColor;        this.context.fillRect(0, 0, this.width, this.height);      }      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();      }    }    new Scratch('canvas');

而后,记录鼠标地位,mouseup时判断是点击还是点击&挪动鼠标,如果是点击则以以后地位为圆心刮开一部分区域;

this.canvas.addEventListener('mousedown', (e) => {      this.posX = e.clientX;      this.posY = e.clientY;      ...}) document.addEventListener('mouseup', (e) => {    if (this.posX === e.clientX && this.posY === e.clientY) {      this.erase(e);    }     ...})

而后,判断刮开面积是否超过一半,如果是清空涂层;

ImageData ctx.getImageData(sx, sy, sw, sh);

sx:将要被提取的图像数据矩形区域的左上角 x 坐标。
sy:将要被提取的图像数据矩形区域的左上角 y 坐标。
sw:将要被提取的图像数据矩形区域的宽度。
sh:将要被提取的图像数据矩形区域的高度。

ImageData 对象,蕴含canvas给定的矩形图像数据。能够用来判断是否被刮开。
每4个元素示意一个像素点的rgba值,所以能够判断第4个的值是否小于256的一半即128,如果小于128即可视为通明(被刮开)。

清空指定区域内容:

void ctx.clearRect(x, y, width, height);
document.addEventListener('mouseup', (e) => {   this.getScratchedPercentage();    if (this.currPerct >= this.maxEraseArea) {        this.context.clearRect(0, 0, this.width, this.height);    }})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);}

而后,设置第一次刮的回调办法和刮完的回调办法;

constructor(id, { maskColor = 'grey', cursorRadius = 10, maxEraseArea = 50,    firstEraseCbk = () => { }, lastEraseCbk = () => { } } = {}) {    ...    this.firstEraseCbk = firstEraseCbk; // 第一次刮的回调函数    this.lastEraseCbk = lastEraseCbk; // 刮开的回调函数}this.canvas.addEventListener('mousedown', (e) => {     if (this.currPerct === 0) {        this.firstEraseCbk();    }})document.addEventListener('mouseup', (e) => {    if (this.currPerct >= this.maxEraseArea) {        this.context.clearRect(0, 0, this.width, this.height);        this.lastEraseCbk();    }})

而后,刮开全副时缓缓清空涂层,设置背景色突变成果;
requestAnimationFrame 做进去的动画更晦涩
回调函数用闭包模式能够给回调函数传参

document.addEventListener('mouseup', (e) => {    if (this.currPerct >= this.maxEraseArea) {        this.done = true;        requestAnimationFrame(this.fadeOut(255));         this.lastEraseCbk();    }})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));          }     }}

而后,初始化涂层的时候,再涂层上显示自定义文字;

addCoat() {        ...        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);        }}

残缺代码

<!DOCTYPE html><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>  <img src="./test.jpg" 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>