canvas图形交互
在上一篇文章《canvas之旅系列----(二)》所绘制的折线图的基础上(如未看上一篇文章的,可以简单的了解,此章节不在对折线图的绘制做介绍),需要实现的效果为,当鼠标移动到点上则出现对应的交互效果。gitee源码地址、github源码地址
模拟hover效果
要达到如同上图所示的效果,首先需要解决的问题便是节点选取,我们要如何判断当前鼠标点种了节点,此次采用的办法为读取鼠标相对于当前位置相对于canvas的位置,再扫描绘制的点的位置,判断当前位置是否在某个点的附近。
/**
* 初始化图形
* @param {string} id canvas的id
*/
function initChart(id) {
//省略其他代码......
this._drawedImg = []
/**
* hover效果实现
*/
let self = this
function bindHover() {
self.canvas.onmousemove = function(e) {
//鼠标相对canvas的位置
let offsetX = e.clientX - self.canvas.clientLeft
let offsetY = e.clientY - self.canvas.clientTop
let points = self._getPoints().slice()
let flag = true
for (let i in points) {
// 判断鼠标在不在点附近
if (Math.abs(points[i].x - offsetX) < 10 && Math.abs(points[i].y - offsetY) < 10) {
flag = false
// 鼠标移动到数据点上......
}
}
if (flag) {
// 鼠标位置未在数据点上......
}
}
}
bindHover()
}
因为添加hover效果只执行一次切不想被外部访问,所以定义成一个内部函数调用。
绘制hover效果
我们已经能够判断当前鼠标是否在数据点附近了,判断完成之后就是绘制相应的效果。
//绘制横向辅助线
self.context.beginPath()
self.context.moveTo(self.chartZone[0], points[i].y)
self.context.lineTo(points[i].x, points[i].y)
self.context.setLineDash([8,8])
self.context.strokeWidth = 4
self.context.strokeStyle = '#1abc9c'
self.context.stroke()
self.context.setLineDash([])
// 绘制点的hover效果
self.context.beginPath()
self.context.moveTo(points[i].x + 6, points[i].y)
self.context.arc(points[i].x, points[i].y, 6, 0, 2 * Math.PI, false)
self.context.fillStyle = '#ffffff'
self.context.fill()
self.context.strokeStyle = '#1abc9c'
self.context.strokeWidth = 4
self.context.stroke()
//绘制提示文字
let texts = [`x:${self.xAxisLable[i]}`,`y:${self.data[i]}`]
let textWidth = self.context.measureText(texts[0]).width
self.context.fillStyle = 'rgba(0, 0, 0, 0.6)'
self.context.fillRect(
points[i].x + 10,
points[i].y - 20,
textWidth + 30,
44
) //先给文字绘制一个半透明的背景
self.context.font = 'italic 16px blod'
self.context.fillStyle = '#ffffff'
self.context.textAlign = 'left'
texts.forEach(function(text, idx) {
self.context.fillText(text,
points[i].x + 15,
points[i].y + idx * 16)
})
绘制完成后我们就可以得到鼠标移动到数据点上的效果了
离屏canvas技术
在绘制完hover效果后,可能在会遇到这样的情况,在每次鼠标移动到数据点上就出现一个hover效果,hover效果并未清除。
所以在每次鼠标离开数据点后,我们都需要将图像恢复成原来的模样。我们可以选择清空画布,再将图像绘制一次。或者采用离屏canvas技术来恢复图像。
离屏canvas其实非常的简单,就是将canvas的图像保存起来,然后在需要的时候再次在canvas上绘制出保存的图像。
所以我们需要是用toDataURL方法将canvas的图像存储起来,并在鼠标离开数据点附近的时候使用drawImage方法,再次将图像绘制出来。
function bindHover() {
self.canvas.onmousemove = function(e) {
let offsetX = e.clientX - self.canvas.clientLeft
let offsetY = e.clientY - self.canvas.clientTop
let points = self._getPoints().slice()
let flag = true
for (let i in points) {
// 判断鼠标在不在点附近
if (Math.abs(points[i].x - offsetX) < 10 && Math.abs(points[i].y - offsetY) < 10) {
flag = false
// 如够没有保存的图像,则将当前图像保存后再绘制hover效果
if (self._drawedImg.length === 0) {
//保存图像
self._drawedImg.push(self.canvas.toDataURL("image/png"))
// 绘制和over效果......
}
break
}
}
//鼠标不在数据点附近,将存储的图像取出,绘制
if (flag && self._drawedImg.length > 0) {
let _imgURL = self._drawedImg.pop()
let img = new Image()
img.src = _imgURL
img.onload = function() {
self._clear()
self.context.drawImage(img, 0, 0)
}
}
}
}
!!!注意:在重新绘制图像的时候,由于new Image()加载是需要时间的,所以需要将绘制放置在onload事件进行,否则会绘制出一个空的图出来。
至此,我们所想的hover效果基本上完成了。
总结
在制作hover效果的一个重要的点就是确定元素的选取,我们不能像DOM节点那样选取canvas中的元素,所以我们需要采用其他方式来选取对象,比如当对象颜色在画布上唯一时,我们可以采用鼠标点的像素点颜色来确定对象选取,也可以采用鼠标点位置与元素位置对比的方式来确定对象选取,当数据量大了,这些做法都是非常耗时的,有兴趣可以再深入研究下canvas的图像中对象的选取问题。
离屏canvas技术听起来似乎很厉害,其实就是简单的存储读取操作,所以在面对未接触过的技术的时候不能被名字吓到了,要具体了解其本质。