vue中用canvas 画一个六边形 类似蜂窝形的功能导航主菜单

最近要做一个六边形组成的功能菜单 ,上一个成品图,有用到类似的可以参考一下,基本上属于不规则按钮了
vue中用canvas 画一个六边形 类似蜂窝形的功能导航主菜单_第1张图片

 最开始用div加背景图做按钮,但是因为div有重叠部分,做出来体验并不好,

后俩查资料有这种写法
 

Planets
  
    
  

给一张大图画很多个热点区域作为按钮,前提是需要一整张图片作为背景,感觉并不太好,只是试了试并没有选择这种方式
然后就想起来不常用却经常见到的canvas,


        
        

首先画出来按钮
 

mounted () {
    const that = this
    this.mycanvas = this.$refs.mycanvas // 指定canvas
    this.ctx = this.mycanvas.getContext('2d') // 设置2D渲染区域
    this.canvas = this.$refs.canvas // 指定canvas
    this.hctx = this.canvas.getContext('2d') // 设置2D渲染区域
    let btns1 = [
      {text: 'xx', tag: {}, path: 'Index', icon: require('../assets/img/menu/sy.png'), offset: {x: 0, y: 0}},
      {text: 'xx', tag: {}, path: 'Integrated', icon: require('../assets/img/menu/zzdw.png'), offset: {x: 5, y: 0}}, // 图标原图不是居中,补偿xy
      {text: 'xx', tag: {}, path: 'KeyManagement', icon: require('../assets/img/menu/zdgl.png'), offset: {x: 5, y: 0}},
      {text: 'xx', tag: {}, path: 'Security', icon: require('../assets/img/menu/zagl.png'), offset: {x: 5, y: 0}},
      {text: 'xx', tag: {}, path: 'Contradiction', icon: require('../assets/img/menu/mdpc.png'), offset: {x: 5, y: 0}},
      {text: 'xx', tag: {}, path: 'x', icon: require('../assets/img/menu/pacj.png'), offset: {x: 5, y: 0}},
      {text: 'xx', tag: {}, path: 'x', icon: require('../assets/img/menu/pasq.png'), offset: {x: 5, y: 0}}
    ]
    let btns2 = [
      {text: 'xx', tag: {}, path: 'myProtectionLineX', icon: require('../assets/img/menu/hlhx.png'), offset: {x: 5, y: 0}},
      {text: 'xx', tag: {}, path: 'School', icon: require('../assets/img/menu/xyzb.png'), offset: {x: 5, y: 0}},
      {text: 'xx', tag: {}, path: 'OA', icon: require('../assets/img/menu/bggl.png'), offset: {x: 5, y: 0}},
      {text: 'xx', tag: {}, path: 'x', icon: require('../assets/img/menu/tsgz.png'), offset: {x: 5, y: 0}},
      {text: 'xx', tag: {}, path: 'x', icon: require('../assets/img/menu/zzkh.png'), offset: {x: 5, y: 0}},
      {text: 'xx', tag: {}, path: 'x', icon: require('../assets/img/menu/tsfx.png'), offset: {x: 5, y: 0}}
    ]
    // 1,56  96.5,1   193,96.5   193,162   96.5,221  1,162
    // 96.5, 110.5
    // 画按钮
    for (let i = 0; i < btns1.length; i++) {
      let baseX = 50 + i * 193
      let baseY = 100
      this.btnAdd(baseX, baseY, btns1[i].text, btns1[i].tag, btns1[i].path, btns1[i].icon, btns1[i].offset)
    }
    for (let i = 0; i < btns2.length; i++) {
      let baseX = 50 + 96.5 + i * 193
      let baseY = 270
      this.btnAdd(baseX, baseY, btns2[i].text, btns2[i].tag, btns2[i].path, btns2[i].icon, btns2[i].offset)
    }
    setTimeout(function () {
      that.cIsLoad = true
    }, 1000)
  },
drawImage (x, y, w, h, onSuccess) {
      let _this = this
      // 创建新的图片对象
      let img = new Image()
      // 指定图片的URL
      img.src = imgSrc
      // 浏览器加载图片完毕后再绘制图片
      img.onload = function () {
        // 以Canvas画布上的坐标( x, y)为起始点,绘制图像
        _this.ctx.drawImage(img, x, y, w, h)
        if (onSuccess) {
          onSuccess()
        }
      }
    },
    drawIcon (x, y, maxX, maxH, icon, offset) {
      let _this = this
      // 创建新的图片对象
      let img = new Image()
      // 指定图片的URL
      img.src = icon
      // 浏览器加载图片完毕后再绘制图片
      img.onload = function () {
        // 以Canvas画布上的坐标( x, y)为起始点,绘制图像
        let w = img.width
        let h = img.height
        if (maxX > img.width) {
          x = x + (maxX - img.width) / 2
        } else {
          w = maxX
        }
        if (maxH > img.height) {
          y = y + (maxH - img.height) / 2
        } else {
          h = maxH
        }
        _this.ctx.drawImage(img, x + offset.x, y + offset.y, w, h)
      }
    },
    drawText (x, y, text) {
      let _this = this
      _this.ctx.font = '18px bold 黑体'
      // 设置颜色
      _this.ctx.fillStyle = '#FFFFFF'
      // 设置水平对齐方式
      _this.ctx.textAlign = 'center'
      // 设置垂直对齐方式
      _this.ctx.textBaseline = 'middle'
      // 绘制文字(参数:要写的字,x坐标,y坐标)

      _this.ctx.fillText(text, x, y)
    },
drawPath (x, y, n, r) {
      let i, ang
      ang = Math.PI * 2 / n // 旋转的角度
      this.hctx.save() // 保存状态
      this.hctx.fillStyle = 'rgba(255,255,255,0.1)' // 填充红色,半透明 阴影效果
      this.hctx.strokeStyle = 'rgba(28, 182, 255, .3)' // 填充绿色
      this.hctx.lineWidth = 6 // 设置线宽
      this.hctx.translate(x, y) // 原点移到x,y处,即要画的多边形中心
      this.hctx.moveTo(0, -r) // 据中心r距离处画点
      this.hctx.beginPath()
      for (i = 0; i < n; i++) {
        this.hctx.rotate(ang) // 旋转
        this.hctx.lineTo(0, -r) // 据中心r距离处连线
      }
      this.hctx.closePath()
      this.hctx.stroke()
      this.hctx.fill()
      this.hctx.restore() // 返回原始状态
    },
getBtnObj (e) {
      let offsetX = e.offsetX
      let offsetY = e.offsetY
      if (e.type === 'touchstart' || e.type === 'touchend') {
        // touch 没有 offsetX Y
        let tX = e.changedTouches[0].clientX
        let tY = e.changedTouches[0].clientY
        let rect = this.canvas.getBoundingClientRect()
        offsetX = tX - rect.x
        offsetY = tY - rect.y
      }
      if (e.type !== 'mousemove') {
        console.log('=====修正offsetX=', offsetX)
        console.log('=====修正offsetY=', offsetY)
      }
      let obj = null
      for (let i = 0; i < this.buttons.length; i++) {
        // 判断在哪个个button
        let find = true
        for (let j = 0; j < this.buttons[i].points.length; j++) {
          // 如果不是最后一个,和下一个点组合,如果是最后一个,和第一个组合
          let res = false
          if (j === this.buttons[i].points.length - 1) {
            res = this.judgeIntersect(offsetX, offsetY, this.buttons[i].center.x, this.buttons[i].center.y, this.buttons[i].points[j].x, this.buttons[i].points[j].y, this.buttons[i].points[0].x, this.buttons[i].points[0].y)
          } else {
            res = this.judgeIntersect(offsetX, offsetY, this.buttons[i].center.x, this.buttons[i].center.y, this.buttons[i].points[j].x, this.buttons[i].points[j].y, this.buttons[i].points[j + 1].x, this.buttons[i].points[j + 1].y)
          }
          if (res) {
            find = false
            break
          }
        }
        if (find) {
          // console.log('================' + i)
          obj = this.buttons[i]
          break
        }
      }
      return obj
    },
    // 判断两条线是否相交
    judgeIntersect (x1, y1, x2, y2, x3, y3, x4, y4) {
      if (!(Math.min(x1, x2) <= Math.max(x3, x4) && Math.min(y3, y4) <= Math.max(y1, y2) && Math.min(x3, x4) <= Math.max(x1, x2) && Math.min(y1, y2) <= Math.max(y3, y4))) {
        return false
      }
      let u, v, w, z
      u = (x3 - x1) * (y2 - y1) - (x2 - x1) * (y3 - y1)
      v = (x4 - x1) * (y2 - y1) - (x2 - x1) * (y4 - y1)
      w = (x1 - x3) * (y4 - y3) - (x4 - x3) * (y1 - y3)
      z = (x2 - x3) * (y4 - y3) - (x4 - x3) * (y2 - y3)
      return (u * v <= 0.00000001 && w * z <= 0.00000001)
    },
    btnAdd (baseX, baseY, text, tag, path, icon, offset) {
      let _this = this
      this.drawImage(baseX, baseY, 193, 221, function () {
        _this.drawIcon(baseX, baseY, 193, 180, icon, offset)
        _this.drawText(baseX + 96.5, baseY + 160, text)
      })
      this.buttons.push({
        text: text,
        tag: tag,
        path: path,
        basePoint: {x: baseX, y: baseY},
        center: {x: baseX + 96.5, y: baseY + 110.5},
        points: [{x: baseX + 1, y: baseY + 56}, {x: baseX + 96.5, y: baseY + 1}, {x: baseX + 193, y: baseY + 96.5}, {x: baseX + 193, y: baseY + 162}, {x: baseX + 96.5, y: baseY + 221}, {x: baseX + 1, y: baseY + 162}]
      })
    }
// 鼠标移动时绘制
    canvasMove (e) {
      // console.log('=======canvasMove=======', e)
      // client是基于整个页面的坐标,offset是cavas距离pictureDetail顶部以及左边的距离
      // let canvasX = e.clientX - e.target.parentNode.offsetLeft
      // let canvasY = e.clientY - e.target.parentNode.offsetTop
      // this.ctx.stroke()
      if (this.cIsLoad) {
        let btn = this.getBtnObj(e)
        if (btn) {
          this.isBtn = true
          // 缓存图片前如果有已经缓存的图片先加载,避免光圈不能消失
          // if (this.cContentImg) {
          // this.hctx.clearRect(0, 0, this.cWidth, this.cHeight)
          // this.drawCanvasImage()
          // this.ctx.putImageData(this.cContentImg, 0, 0)
          // }
          // 缓存图片
          // this.cContentImg = this.ctx.getImageData(0, 0, this.cWidth, this.cHeight) // this.mycanvas.toDataURL('image/png') //
          this.hctx.clearRect(0, 0, this.cWidth, this.cHeight)
          this.drawPath(btn.center.x, btn.center.y, 6, 102) // 画一个半径为40的六边形
        } else {
          this.isBtn = false
          this.hctx.clearRect(0, 0, this.cWidth, this.cHeight)
          // if (this.cContentImg) {
          // this.ctx.clearRect(0, 0, this.cWidth, this.cHeight)
          // this.drawCanvasImage()
          // this.ctx.putImageData(this.cContentImg, 0, 0)
          // }
        }
      }
    },
// 鼠标抬起
    canvasUp (e) {
      // console.log('=======canvasUp=======', e)
      let btn = this.getBtnObj(e)
      if (btn) {
        this.menuClick(btn.text, btn.path)
      }
      // console.log('=======canvasUp=======', btn)
      this.isMoveDown = false
    },

可以看到最初我用的是一个canvas做的,用this.cContentImg = this.ctx.getImageData(0, 0, this.cWidth, this.cHeight)缓存图片数据,但是这种方法在vue打包本地非服务状态下并不行,好像是浏览器本地存储图片数据有跨域问题,然后就换了一种方式实现

应该注意的是在getBtnObj 方法里,因为触屏的设备触发的是touch事件,没有鼠标的offsetxy,这里处理了一下 

在写的过程中有了解到canvas面向对象的写法,应该会简单很多,有时间试一试

你可能感兴趣的:(vue,canvas,不规则按钮,六边形按钮,支持触屏)