david2tdw / blog

学习记录
1 stars 1 forks source link

[canvas] canvas 刮刮乐 #185

Open david2tdw opened 4 years ago

david2tdw commented 4 years ago

本地文件夹下放3个图片: zhongjiang.png weizhongjiang.png bg.png

图片跨域问题: 解决canvas图片getImageData,toDataURL跨域问题

david2tdw commented 4 years ago

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>实现一个“刮刮乐”游戏</title>
    <style>
      .demo {
        width: 320px;
        margin: 10px auto 20px auto;
        min-height: 300px;
      }
      .msg {
        text-align: center;
        height: 32px;
        line-height: 32px;
        font-weight: bold;
        margin-top: 50px;
      }
    </style>
  </head>
  <body>
    <div id="main">
      <div class="msg">刮刮下面图片看看什么效果哈哈哈, <a href="javascript:void(0)" id="try_again">再来一次</a></div>
      <div class="demo">
        <canvas id="canvas"></canvas>
      </div>
    </div>
    <script src="./scrapAward.js"></script>
    <script>
      function bgImgUrl() {
        var imgs = ['./zhongjiang.png', './weizhongjiang.png']
        var num = Math.floor(Math.random() * 2)
        return imgs[num]
      }
      window.onload = function () {
        // new class的时候会执行构造函数,调用init方法。
        let scrapAward = new ScrapAward({
          height: 570,
          backgroundImageUrl: bgImgUrl(),
          coverImage: {
            url: './bg.png',
            width: 428,
            height: 570,
          },
          callback: () => {
            alert('刮奖结束')
          },
        })

        document.getElementById('try_again').addEventListener('click', function (e) {
          scrapAward.init({
            backgroundImageUrl: bgImgUrl(),
          })
        })
      }
    </script>
  </body>
</html>
david2tdw commented 4 years ago

scrapAward.js

function extend(o, n, override) {
  for (var p in n) {
    if (n.hasOwnProperty(p) && (!o.hasOwnProperty(p) || override)) {
      o[p] = n[p]
    }
  }
}

class ScrapAward {
  constructor(userOption) {
    this.option = {
      canvasId: 'canvas',
      backgroundImageUrl: '',
      width: 320,
      height: 160,
      backgroundSize: '100% 100%',
      coverImage: {
        url: '',
        width: 320,
        height: 160,
        color: '',
      },
      callback: () => {},
    }
    this.context = null
    this.init(userOption)
  }
  init(userOption) {
    // 合并用户配置
    if (Object.assign) {
      Object.assign(this.option, userOption)
    } else {
      extend(this.option, userOption, true)
    }
    // 定义一系列变量
    let that = this,
      img = (this.img = new Image()),
      imgLoaded = false,
      canvas = (this.canvas = document.querySelector(`#${this.option.canvasId}`)),
      hastouch = 'ontouchstart' in window ? true : false,
      tapstart = hastouch ? 'touchstart' : 'mousedown',
      tapmove = hastouch ? 'touchmove' : 'mousemove',
      tapend = hastouch ? 'touchend' : 'mouseup',
      coverImage = (this.coverImage = new Image()),
      hasDone = false,
      coverImgLoad = false

    that.mousedown = false

    // 事件监听移除
    if (this.canvas) {
      this.canvas.removeEventListener(tapstart, eventDown.bind(this))
      this.canvas.removeEventListener(tapend, eventUp.bind(this))
      this.canvas.removeEventListener(tapmove, eventMove.bind(this))
    }
    // crossOrigin一定要写在前面
    coverImage.crossOrigin = 'Anonymous' // 解决一些跨域问题
    coverImage.src = this.option.coverImage.url

    img.crossOrigin = '' // 解决一些跨域问题
    img.src = this.option.backgroundImageUrl

    var w = (img.width = this.option.width),
      h = (img.height = this.option.height)

    this.canvasOffsetX = canvas.offsetLeft
    this.canvasOffsetY = canvas.offsetTop

    canvas.width = w
    canvas.height = h

    this.context = canvas.getContext('2d')
    let context = this.context

    this.img.addEventListener('load', backgroundImageLoaded)
    this.option.coverImage.url && this.coverImage.addEventListener('load', coverImageLoaded)

    // 背景图片加载完成后
    function backgroundImageLoaded(event) {
      imgLoaded = true
      fillCanvas()
      canvas.style.background = 'url(' + img.src + ') no-repeat'
      canvas.style.backgroundSize = that.option.backgroundSize || 'contain'
    }

    // 覆蓋图片加载完成后
    function coverImageLoaded() {
      coverImgLoad = true
      fillCanvas()
      canvas.style.background = 'url(' + img.src + ') no-repeat'
      canvas.style.backgroundSize = that.option.backgroundSize || 'contain'
    }
    // 绘制canvas
    function fillCanvas() {
      if (that.option.coverImage.url && (!imgLoaded || !coverImgLoad)) {
        return
      }
      if (!that.option.coverImage.url) {
        context.fillStyle = that.option.coverImage.color || 'gray'
        context.fillRect(0, 0, w, h)
      } else {
        context.drawImage(coverImage, 0, 0, that.option.coverImage.width, that.option.coverImage.height)
      }
      // globalCompositeOperation 属性设置或返回如何将一个源(新的)图像绘制到目标(已有)的图像上。
      // https://www.w3school.com.cn/tags/canvas_globalcompositeoperation.asp
      context.globalCompositeOperation = 'destination-out'

      canvas.addEventListener(tapstart, eventDown)
      canvas.addEventListener(tapend, eventUp)
      canvas.addEventListener(tapmove, eventMove)
    }

    // 点击开始事件
    function eventDown(e) {
      e.preventDefault()
      that.mousedown = true
    }
    // 点击结束事件
    function eventUp(e) {
      e.preventDefault()
      that.mousedown = false
    }

    // 刮奖事件
    function eventMove(e) {
      // 刮奖结束则return
      if (hasDone) {
        return
      }
      let context = that.context
      e.preventDefault()
      if (that.mousedown) {
        if (e.changedTouches) {
          e = e.changedTouches[0]
        }
        var x = (e.clientX + document.body.scrollLeft || e.pageX) - that.canvasOffsetX || 0
        var y = (e.clientY + document.body.scrollTop || e.pageY) - that.canvasOffsetY || 0

        context.beginPath()
        context.arc(x, y, 20, 0, Math.PI * 2)
        context.fill()
      }
      // 每当移动的时候重新计算已刮区域
      handleFilledPercentage(getFilledPercentage())
    }

    // 计算已经刮过的区域占整个区域的百分比
    function getFilledPercentage() {
      let imgData = that.context.getImageData(0, 0, w, h)
      // imgData.data是个数组,存储着指定区域每个像素点的信息,数组中4个元素表示一个像素点的rgba值
      let pixels = imgData.data
      let transPixels = []
      for (let i = 0; i < pixels.length; i += 4) {
        // 严格上来说,判断像素点是否透明需要判断该像素点的a值是否等于0,
        // 为了提高计算效率,这儿设置当a值小于128,也就是半透明状态时就可以了
        if (pixels[i + 3] < 128) {
          transPixels.push(pixels[i + 3])
        }
      }
      return ((transPixels.length / (pixels.length / 4)) * 100).toFixed(2) + '%'
    }

    // 设置阈值,去除灰色涂层
    function handleFilledPercentage(percentage) {
      percentage = percentage || 0
      if (parseInt(percentage) > 50) {
        // 当像素点的个数超过  50% 时,清空画布,显示底图
        context.clearRect(0, 0, w, h)
        hasDone = true
        that.option.callback()
      }
    }
  }
  restart(userOption) {
    if (userOption) {
      this.init(userOption)
    } else {
      this.init({})
    }
  }
}

Canvas 进阶(四)实现一个“刮刮乐”游戏