谁还不想自己做一张刮刮乐呢 原生 JS 实现刮刮乐

1,100 阅读3分钟

我正在参加中秋创意投稿大赛,详情请看:中秋创意投稿大赛

前言

上文说自己也写一段代码试试,突然想试着实现一个刮刮乐,正巧碰到活动了,想着中秋节店家抽奖也一定用得上,那就顺便参加活动,一起做出来

成品展示

重点分析

  • 绝对定位覆盖内容
  • <canvas></canvas> 可以实现绘画以及擦除
  • 扩展性:覆盖图片、覆盖文字
  • CSS 画月亮

代码解读

我还是觉得写成模板化的形式挺好的,但是不太熟练,所以还是抄了一个骨架

 class Something {
     constructor() {
         
     }
 ​
     render() {
     
     }
     ...
 }

大概就是这样!

先想,一个刮刮乐可以拥有什么属性:

  • id
  • 内容
  • 长宽
  • ……

所以在 constructor() 中我也设置了这些属性。

再想,这个刮刮乐应该使用啥 HTML 结构来表示:

  • div.scratch-content 表示内容

    注意点:文字和图片使用了两种标签,所以实际标签应该是这样的

    • div.scratch-content > img
    • div.scratch-content > div
  • <canvas width=${this.width} height=${this.height}></canvas> 表示刮层

    注意点:

    • 为啥我写的是<canvas></canvas>捏?

      因为 canvas 必须有关闭标签,同样的还有 <script></script>,相关链接

    • 为啥我要把 widthheight 写到标签里捏?

      因为 CSS 的 width 属性会造成缩放,导致 e.offsetXe.offsetY 不能准确定位,相关链接

所以 render() 中我要返回如下结构的 HTML 代码

 render() {
     if (this.content.img !== undefined)
       return `
         <div class="scratch-content"><img alt="" src="${this.content.img}"></div>
         <canvas title="" id="cover-layer" width="${this.width}" height="${this.height}"></canvas>
       `;
     return `
       <div class="scratch-content"><div>${this.content.text}</div></div>
       <canvas title="" id="cover-layer"  width="${this.width}" height="${this.height}"></canvas>
     `;
 }

此时, constructor() 可以生成结构了

现在的构造函数应该长这样

 constructor(id, content = { img: "", text: "" }, width = 300, height = 150) {
     this.container = document.getElementById(id);
     this.content = content;
     this.width = width;
     this.height = height;
     this.mousedown = false;
     this.container.innerHTML = this.render();
     this.canvas = document.getElementById("cover-layer");
     this.ctx = this.canvas.getContext("2d");
 }

紧接着,我们可以绘制刮层了

刮层也很容易实现,因为我们在 constructor() 已经声明 canvas 上下文了,可以直接使用,代码如下:

 cover() {
     this.ctx.fillStyle = "#555";
     this.ctx.fillRect(0, 0, this.width, this.height);
 }

那么,如何实现擦除呢?

 erase(x, y) {
     this.ctx.globalCompositeOperation = "destination-out";
     this.ctx.beginPath();
     this.ctx.arc(x, y, 15, 0, 2 * Math.PI);
     this.ctx.fill();
     this.ctx.closePath();
 }

这里使用了globalCompositeOperation,说实话,我实在不太会用汉语描述一遍这到底是干嘛的,所以看链接

这里还有一个注意点:this.ctx.beginPath() 如果不加这句,擦除会有索套的效果,可以删除试一下!

arc的参数分别是:x,y,半径,起始角度,结束角度,fill()当然就是填充啦!

当然,我们需要一个初始化函数!

初始化做啥捏?事实上我们生成 HTML 后就要绘制图层啦,还有我们需要绑定鼠标经过移动事件等等

大概大概结构就是这样的捏:

 init() {
     this.cover();
     // touch 移动端 mouse PC端
     this.canvas.ontouchstart = this.container.onmousedown = () => {
       this.mousedown = true;
     };
     this.canvas.ontouchcancel = this.container.onmouseup = () => {
       this.mousedown = false;
     };
     this.canvas.addEventListener("mousemove", (e) => {
       if (this.mousedown) {
         this.erase(e.offsetX, e.offsetY);
       }
     });
     this.canvas.addEventListener("touchmove", (e) => {
       if (this.mousedown) {
         this.erase(e.changedTouches[0].clientX, e.changedTouches[0].clientY);
       }
     });
 }

好啦!我们写完测试一下!

按照我们写的构造函数 new 一个新的刮刮乐!

 const scratchCard__img = new ScratchCard(
   "scratchCard__img",
   {
     img:
       "https://sf1-ttcdn-tos.pstatp.com/obj/larkcloud-file-storage/baas/qcnp4x/f0d1c4f16359d534_1631099749185.jpg"
   },
   450,
   240
 );

呐呐呐!如果是文字版的就可以这样

 const scratchCard__text = new ScratchCard(
   "scratchCard__text",
   {
     text:
       "233333"
   },
   450,
   240
 );

最最后,我们画一个月亮!

核心代码是这样的!使用伪元素、给宽高、给bgc、给边框半径,然后!一个月亮就出来啦!

.header::before {
  content: " ";
  background-color: #ccdd00;
  width: 90px;
  height: 90px;
  border-radius: 50%;
}

结束语

恩~大家中秋节快乐啦! 实在没啥好活,给各位献丑了!欢迎讨论还有点赞

相关链接

\