一花一世界,一树一菩提
canvas事件交互
canvas本质上是一张位图,所有这张图片是支持鼠标的各种事件的,但对于其画布上的内容,并不支持dom事件,和svg这个是最大的区别
常见的dome事件有
onclick, ondbclick, onmouseover, onmouseout, onmouseleave, onmousedown, onmouseup, (还有几个拖拽的事件,这里就不说了)
echarts 所有的事件 const MOUSE_EVENT_NAMES = [ 'click', 'dblclick', 'mouseover', 'mouseout', 'mousemove','mousedown', 'mouseup', 'globalout', 'contextmenu']
下面我以一个简单的列子,演示一下canvas事件交互
实现一个简单的饼图hover效果
根据前面几个文章我们首先画一个简单的饼图
1.画一个简单的饼图,并且记录下每个扇形的数据,包括属性,开始角度,半径,颜色,等等
// hover 为鼠标移动上去是的效果
data() {
return {
canvas: null,
ctx: null,
isFill: false,
isShowHover: false,
hoverstyle: {
left: 300,
top: 300
},
hoverItem: {
item: {}
},
allArcs: [],
r: 150,
count: [
{
name: '脚盆鸡',
num: 30
},
{
name: '棒子',
num: 60
},
{
name: '长鼻象',
num: 50
},
{
name: '小兔子',
num: 99
},
{
name: '鹰酱',
num: 99
},
{
name: '毛熊',
num: 80
}
],
colors: ['#eeeeee', 'HotPink', 'green', 'red', 'blue', '#d4d4d5']
}
},
// 初始化数据
initcanvas() {
if (this.count && this.count.length > 0) {
let allcount = this.count.reduce((a, b) => {
return a + b.num
}, 0)
console.log(allcount)
let newdegree = 0
this.count.forEach((p, index) => {
let jd = (p.num / allcount) * 2 * Math.PI
this.allArcs.push({
item: p,
startdegree: newdegree,
enddegree: jd + newdegree,
fillStyle: this.colors[index]
})
newdegree += jd
})
}
},
- 给该canvas添加鼠标事件
addlistenCanvas() {
// 事件绑定(这里有一个要注意的,我这里用了bind方法,是为了将“mousedownEvent”方法内的this指向切换到Canvas)
this.canvas.addEventListener('mousemove', this.mousehoverEvent.bind(this)) // 点击事件
// 事件绑定(这里有一个要注意的,我这里用了bind方法,是为了将“mousedownEvent”方法内的this指向切换到Canvas)
this.canvas.addEventListener('click', this.mouseClickEvent.bind(this))
},
3.当鼠标hover时从新处理饼图的数据
mousehoverEvent(e) {
let _self = this
let fn = function() {
_self.changeallArcs(e)
_self.drawarc()
}
throttle(fn, 100)
},
changeallArcs(e) {
const _self = this
const x = e.layerX // 相对于画布的x偏移量
const y = e.layerY // 相对于画布的y偏移量
// 判断点的位置是否在饼图里面
console.log(x, y)
let t = (x - 300) * (x - 300) + (y - 300) * (y - 300)
if (t < (_self.r * _self.r)) {
_self.allArcs.forEach(p => {
let degree = Math.atan2((x - 300), -(y - 300))
console.log(degree)
if (degree <= Math.PI / 2) {
degree = (3 * Math.PI) / 2 + degree
} else {
degree = degree - Math.PI / 2
}
if (p.startdegree < degree && degree < p.enddegree) {
p.hover = true
_self.hoverItem = p
_self.isShowHover = true
} else {
p.hover = false
}
})
_self.hoverstyle = {
left: x + 20,
top: y + 10
}
} else {
_self.allArcs.forEach(p => {
p.hover = false
})
_self.hoverItem = {}
_self.isShowHover = false
}
},
4 从新画整个饼图调用的方法
drawarc() {
this.ctx.clearRect(0, 0, 600, 600)
this.allArcs.forEach((p, index) => {
if (p.hover) {
this.ctx.beginPath()
this.ctx.shadowColor = 'rgba(0, 0, 0, 0.55)'
this.ctx.shadowOffsetX = '-5'
this.ctx.shadowOffsetY = '5'
this.ctx.shadowBlur = '10'
this.ctx.moveTo(300, 300)
this.ctx.arc(300, 300, this.r + 6, p.startdegree, p.enddegree, false)
this.ctx.closePath()
this.ctx.fillStyle = p.fillStyle
this.ctx.fill()
} else {
this.ctx.beginPath()
this.ctx.shadowColor = 'none'
this.ctx.shadowOffsetX = '0'
this.ctx.shadowOffsetY = '0'
this.ctx.shadowBlur = '0'
this.ctx.moveTo(300, 300)
this.ctx.arc(300, 300, this.r, p.startdegree, p.enddegree, false)
this.ctx.closePath()
this.ctx.fillStyle = p.fillStyle
this.ctx.fill()
}
})
},
5.完整的代码
<template>
<div class="main-c">
<h1>创建HTML5 canvas事件交互</h1>
<div class="mycanvas">
<canvas id="tutorial" width="600px" height="600px" ref="canvas" style="border: 1px solid #999;"></canvas>
<div class="canvashover" v-if="isShowHover" :style="{ left: '0px', top: '0px', transform: `translate(${hoverstyle.left}px, ${hoverstyle.top}px)` }">
<ul>
<span>名称:</span
>{{
hoverItem.item.name
}}
</ul>
<ul>
<span>战斗力:</span
>{{
hoverItem.item.num
}}
</ul>
</div>
</div>
<br />
</div>
</template>
<script>
import throttle from '../../utils/debounce.js'
/** @type {HTMLCanvasElement} */
export default {
name: 'HTML5canvas',
data() {
return {
canvas: null,
ctx: null,
isFill: false,
isShowHover: false,
hoverstyle: {
left: 300,
top: 300
},
hoverItem: {
item: {}
},
allArcs: [],
r: 150,
count: [
{
name: '脚盆鸡',
num: 30
},
{
name: '棒子',
num: 60
},
{
name: '长鼻象',
num: 50
},
{
name: '小兔子',
num: 99
},
{
name: '鹰酱',
num: 99
},
{
name: '毛熊',
num: 80
}
],
colors: ['#eeeeee', 'HotPink', 'green', 'red', 'blue', '#d4d4d5']
}
},
mounted() {
// 获取canvas元素
this.canvas = document.getElementById('tutorial')
// 获取绘制二维上下文
this.ctx = this.canvas.getContext('2d')
this.initcanvas()
this.drawarc()
this.addlistenCanvas()
},
methods: {
initcanvas() {
if (this.count && this.count.length > 0) {
let allcount = this.count.reduce((a, b) => {
return a + b.num
}, 0)
console.log(allcount)
let newdegree = 0
this.count.forEach((p, index) => {
let jd = (p.num / allcount) * 2 * Math.PI
this.allArcs.push({
item: p,
startdegree: newdegree,
enddegree: jd + newdegree,
fillStyle: this.colors[index]
})
newdegree += jd
})
}
},
drawarc() {
this.ctx.clearRect(0, 0, 600, 600)
this.allArcs.forEach((p, index) => {
if (p.hover) {
this.ctx.beginPath()
this.ctx.shadowColor = 'rgba(0, 0, 0, 0.55)'
this.ctx.shadowOffsetX = '-5'
this.ctx.shadowOffsetY = '5'
this.ctx.shadowBlur = '10'
this.ctx.moveTo(300, 300)
this.ctx.arc(300, 300, this.r + 6, p.startdegree, p.enddegree, false)
this.ctx.closePath()
this.ctx.fillStyle = p.fillStyle
this.ctx.fill()
} else {
this.ctx.beginPath()
this.ctx.shadowColor = 'none'
this.ctx.shadowOffsetX = '0'
this.ctx.shadowOffsetY = '0'
this.ctx.shadowBlur = '0'
this.ctx.moveTo(300, 300)
this.ctx.arc(300, 300, this.r, p.startdegree, p.enddegree, false)
this.ctx.closePath()
this.ctx.fillStyle = p.fillStyle
this.ctx.fill()
}
})
},
addlistenCanvas() {
// 事件绑定(这里有一个要注意的,我这里用了bind方法,是为了将“mousedownEvent”方法内的this指向切换到Canvas)
this.canvas.addEventListener('mousemove', this.mousehoverEvent.bind(this)) // 点击事件
// 事件绑定(这里有一个要注意的,我这里用了bind方法,是为了将“mousedownEvent”方法内的this指向切换到Canvas)
this.canvas.addEventListener('click', this.mouseClickEvent.bind(this))
},
mousehoverEvent(e) {
let _self = this
let fn = function() {
_self.changeallArcs(e)
_self.drawarc()
}
throttle(fn, 100)
},
changeallArcs(e) {
const _self = this
const x = e.layerX // 相对于画布的x偏移量
const y = e.layerY // 相对于画布的y偏移量
// 判断点的位置是否在饼图里面
console.log(x, y)
let t = (x - 300) * (x - 300) + (y - 300) * (y - 300)
if (t < (_self.r * _self.r)) {
_self.allArcs.forEach(p => {
let degree = Math.atan2((x - 300), -(y - 300))
console.log(degree)
if (degree <= Math.PI / 2) {
degree = (3 * Math.PI) / 2 + degree
} else {
degree = degree - Math.PI / 2
}
if (p.startdegree < degree && degree < p.enddegree) {
p.hover = true
_self.hoverItem = p
_self.isShowHover = true
} else {
p.hover = false
}
})
_self.hoverstyle = {
left: x + 20,
top: y + 10
}
} else {
_self.allArcs.forEach(p => {
p.hover = false
})
_self.hoverItem = {}
_self.isShowHover = false
}
},
mouseClickEvent(e) {
const x = e.layerX // 相对于画布的x偏移量
const y = e.layerY // 相对于画布的y偏移量
// 判断点的位置是否在饼图里面
this.changeallArcs(e)
let object = this.allArcs.find(p => p.hover)
console.log(object)
this.$message.info(`当前点击点的坐标是x:${x},y:${y},当前点击的对象是名称:${object.item.name},战斗力:${object.item.num}`)
}
}
}
</script>
<style scoped lang="scss">
.main-c {
padding: 15px;
h1 {
font-size: 18px;
line-height: 36px;
}
h3 {
padding-top: 10px;
}
.tutor {
border: 0px solid #eee;
position: relative;
z-index: 1;
}
h4 {
position: absolute;
top: 23px;
font-size: 28px;
font-weight: bold;
}
#tutorial2 {
border: 1px solid #999;
}
.mycanvas {
position: relative;
.canvashover {
position: absolute;
border: 1px solid #eee;
background-color: #eee;
opacity: 0.9;
box-shadow: 1px 1px 1px 1px 1px;
padding: 10px;
border-radius: 5px;
transition: 1s;
span {
padding: 5px 0px;
}
}
}
}
</style>