最近要做一个六边形组成的功能菜单 ,上一个成品图,有用到类似的可以参考一下,基本上属于不规则按钮了
最开始用div加背景图做按钮,但是因为div有重叠部分,做出来体验并不好,
后俩查资料有这种写法
给一张大图画很多个热点区域作为按钮,前提是需要一整张图片作为背景,感觉并不太好,只是试了试并没有选择这种方式
然后就想起来不常用却经常见到的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面向对象的写法,应该会简单很多,有时间试一试