canvas实现刮刮乐,带节流效果,兼容移动端

Posted hans774882968

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了canvas实现刮刮乐,带节流效果,兼容移动端相关的知识,希望对你有一定的参考价值。

CSS布局的思路,是让图片绝对定位,并设置z-index=-1,使得它能被canvas挡住。然后canvas的颜色设为透明,从而让图片显示。

JS部分。对节流函数进行了一些修改。thisArg指定fn的this。节流主要是用在mousemove和touchstart,产生涂抹效果那里。

function throttle(fn, interval, thisArg) {
  let waiting = false
  return function (...args) {
    if (waiting) return
    waiting = true
    setTimeout(() => {
      fn.apply(thisArg, args)
      waiting = false
    }, interval)
  }
}

init函数,因为loadImg是异步方法,索性也给他加上了Promise。

事件绑定的部分,我查到的资料似乎都是在mousedown事件的listener里嵌套mousemove的listener,感觉似乎并没有更好的方案了。

mousemove和touchstart各写一个listener,这是为了兼容移动端。

  1. mousemove可以直接用e.offsetX获得鼠标相对#canvas的坐标。
  2. 但touchstart只能event.touches[0].clientX获得触屏坐标相对于浏览器窗口左上角的坐标,因此还需要减去offsetLeft、offsetTop,来获得相对#canvas的坐标。

用到canvas的一些api如下

  • this.ctx.beginPath(),开始一条路径,或重置当前的路径
  • this.ctx.fillStyle = ‘grey’,用来填充颜色,或者提供渐变
  • this.ctx.fillRect(0, 0, this.w, this.h)
  • this.ctx.arc(x, y, 30, 0, Math.PI * 2),用来画扇形的,半径是30px,起始和结束弧度分别是0和2*pi。
  • this.ctx.fill(),紧跟fillStyle
  • this.ctx.closePath(),可以用来创建闭合路径
  • this.ctx.getImageData(0, 0, this.w, this.h).data,获得像素数据,然后就可以计算有多少点已经透明了。这实现了涂到一定程度就自行消失的效果。

下面的例子展示了canvas画三角形,并设置从黑到白的渐变色。其实我还没完全学懂,代码先放这了

'use strict';

let canvas = $('#canvas')[0]
let ctx = canvas.getContext('2d')
ctx.beginPath()
ctx.moveTo(30, 30)
ctx.lineTo(30, 130)
ctx.lineTo(110, 190)
ctx.closePath()
ctx.stroke()
let colorChange = ctx.createLinearGradient(0, 0, 0, 170)
colorChange.addColorStop(0, "black")
colorChange.addColorStop(1, "white")
ctx.fillStyle = colorChange
ctx.fill()

HTML

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>刮开有奖</title>
    <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
    <link rel="stylesheet" href="index.css">
  </head>
  <body>
    <div class="container">
      <img class="img" src="1.jpg">
      <canvas id="canvas" width="400" height="300"></canvas>
    </div>
  </body>
  <script src="index.js"></script>
</html>

CSS

body{
  margin: 0;
  display: flex;
  justify-content: center;
}

.container{
  margin-top: 100px;
  position: relative;
  user-select: none;
}

.container .img{
  position: absolute;
  top: 0;
  left: 0;
  z-index: -1;
  width: 400px;
  height: 300px;
}

JS

'use strict';

function throttle(fn, interval, thisArg) {
  let waiting = false
  return function (...args) {
    if (waiting) return
    waiting = true
    setTimeout(() => {
      fn.apply(thisArg, args)
      waiting = false
    }, interval)
  }
}

class ScratchCard {
  constructor() {
    this.canvas = $('#canvas')[0]
    this.img = $('.img')[0]
    this.container = $('.container')[0]
    this.ctx = this.canvas.getContext('2d')
    this.w = this.canvas.width
    this.h = this.canvas.height
    this.area = this.w * this.h
  }

  loadImg() {
    return new Promise((resolve) => {
      let paths = ['1.jpg', '2.jpg']
      this.img.onload = resolve
      this.img.src = paths[parseInt(Math.random() * paths.length)]
    })
  }

  init() {
    return new Promise((resolve) => {
      this.ctx.beginPath()
      this.ctx.fillStyle = 'grey'
      this.ctx.fillRect(0, 0, this.w, this.h)
      resolve()
    }).then(this.loadImg.bind(this))
  }

  addEvent() {
    // let device = /android|iphone|ipad|ipod|webos|iemobile|opear mini|linux/i.test(navigator.userAgent.toLowerCase())
    // let startEvtName = device ? 'touchstart' : 'mousedown'
    // let moveEvtName = device ? 'touchmove' : 'mousemove'
    // let endEvtName = device ? 'touchend' : 'mouseup'

    $(this.canvas).bind('mousedown', () => {
      let throttleMove = throttle((e) => {
        let x = e.offsetX, y = e.offsetY
        // console.log(x, y)//
        this.eraseGrey(x, y)
      }, 50, this)
      $(this.canvas).bind('mousemove', (e) => throttleMove(e))
    })

    $(this.canvas).bind('touchstart', () => {
      let throttleMove = throttle((event) => {
        let x = event.touches[0].clientX - this.container.offsetLeft,
          y = event.touches[0].clientY - this.container.offsetTop
        // console.log(x, y)//
        this.eraseGrey(x, y)
      }, 50, this)
      $(this.canvas).bind('touchmove', () => throttleMove(event))
    })

    $(this.canvas).bind('mouseup', () => {
      $(this.canvas).unbind('mousemove')
    })

    $(this.canvas).bind('touchend', () => {
      $(this.canvas).unbind('touchmove')
    })
  }

  eraseGrey(x, y) {
    this.ctx.globalCompositeOperation = 'destination-out'
    this.ctx.beginPath()
    this.ctx.arc(x, y, 30, 0, Math.PI * 2)
    this.ctx.fill()
    this.ctx.closePath()
    if (this.erasedEnough()) {
      this.ctx.clearRect(0, 0, this.w, this.h)
      $(this.canvas).unbind('mousedown')
      $(this.canvas).unbind('mousemove')
      $(this.canvas).unbind('mouseup')
      $(this.canvas).unbind('touchstart')
      $(this.canvas).unbind('touchmove')
      $(this.canvas).unbind('touchend')
    }
  }

  erasedEnough() {
    let data = this.ctx.getImageData(0, 0, this.w, this.h).data
    let s = 0
    for (let i = 3; i < data.length; i += 4) {
      if (!data[i]) ++s
    }
    return s >= 0.8 * this.area
  }
}

let sc = new ScratchCard()
sc.init().then(sc.addEvent.bind(sc))

以上是关于canvas实现刮刮乐,带节流效果,兼容移动端的主要内容,如果未能解决你的问题,请参考以下文章

20行js代码制作网页刮刮乐

Android刮刮乐效果-proterDuffXfermode

canvas实现刮刮乐

canvas之刮刮乐

Html5实现移动端PC端 刮刮卡效果

Html5实现移动端PC端 刮刮卡效果