众所周知,canvas只是个画布,对于画布内的图形,并没有对应的鼠标事件,所以,如果我们希望鼠标和画布内的图形产生交互,就需要自己手动添加鼠标事件。有些canvas库对图形进行了封装,使得我们可以很方便的给图形添加鼠标事件,比如fabric.js,不过作为学习者,我们也需要理解其中的原理。所以,这里主要介绍如何用原生canvas提供的api来完成图形的鼠标事件。
接下来我将用我的方法,来实现一个鼠标hover到图形上,鼠标样式变成pointer的效果,效果如下:
可以看到,这里我绘制了一个空心圆和一条运动的曲线,当鼠标放到圆的边线和曲线上时,鼠标变成了小手,移开后,又回到了默认状态。
1.Path2D,canvas提供的2D图形对象,允许用户在 canvas 中根据需要创建可以保留并重用的路径。
2.isPointInStroke和isPointInPath,本章最重要的api,用于检测某个点是否位于当前图形的路径上。前者用于判断点是否在路径的描边上,后者用于判断点是否在路径的封闭图形内。
1.首先,要在画布上绘制这两个图形。如何绘制一条运动的曲线不是本章重点,如果需要了解的可以参考这篇文章:https://dtstack.yuque.com/easyv/yfuwfr/dk2iez5u5b5s8bcw?singleDoc# 《用canvas绘制2D飞线的方法》
这里我们先来实现图形的初始化方法,借助与Path2D,我们可以在正式绘制图形之前,先创建好所有的路径对象。
//初始化所有需要绘制的路径对象 function initPathObj(){ let res = []; let curve = drawQuadratic(100,100,400,100,400,400); res.push({ path:curve, lineDash:[100,len-100] }); let circle = strokeCircle(200,200,50); res.push({ path:circle, lineDash:[] }); return res; } //绘制贝塞尔曲线 function drawQuadratic(x0,y0,x1,y1,x2,y2){ let path = new Path2D(); path.moveTo(x0,y0); path.quadraticCurveTo(x1,y1,x2,y2); return path; } //绘制圆 function strokeCircle(x,y,r){ let path = new Path2D(); path.arc(x,y,r,0,Math.PI*2); return path; } initPathObj();
2.在每一帧的渲染中,我们需将路径对象绘制到画布上,这里需要用到requestAnimationFrame。
const canvas = document.getElementById("canvas"); const ctx = canvas.getContext("2d"); let offset = 0; //虚线偏移值 let len = 150*Math.PI; //曲线长度 ctx.lineWidth=5; const animation = ()=>{ ctx.clearRect(0,0,500,500); ctx.save(); ctx.lineDashOffset = offset; ctx.strokeStyle="red"; //批量绘制图形 paths.forEach(p=>{ ctx.setLineDash(p.lineDash); ctx.stroke(p.path); //ctx.stroke()方法可以传入Path2D对象来进行绘制 }); ctx.restore(); canvas.style.cursor=cursor; offset=(offset-0.3)%len; //这里每一帧改变offset,就是让曲线动起来的原因 requestAnimationFrame(animation); } animation();
3.接下来我们给canvas添加鼠标移动事件,并用一个变量记录鼠标位置(这里我用的是offsetX和offsetY)
let pixel={x:0,y:0}; canvas.addEventListener("mousemove",(e)=>{ pixel={ x:e.offsetX, y:e.offsetY } });
4.最后,我们只需要在每次绘制路径时,都对pixel检测一下,是否位于某个图形的路径内。如果是,则通过canvas.style.cursor改变鼠标样式,否则继续用默认鼠标样式。
const animation = ()=>{ let cursor = "default"; //每一帧先重置为default状态。 ... paths.forEach(p=>{ ctx.setLineDash(p.lineDash); ctx.stroke(p.path); //只有cursor==default时才检测,一旦鼠标位于某个图形上了,后续图形就不需要检测了。 if(cursor=="default"){ cursor = checkCursor(p.path,true); } }); ctx.restore(); canvas.style.cursor=cursor; //修改鼠标样式 ... requestAnimationFrame(animation); } animation(); //检测鼠标是否处在对应的路径上,onlyStroke=true时,只对描边进行检测,不检测图形封闭区域 function checkCursor(path, onlyStroke){ if(ctx.isPointInStroke(path,pixel.x,pixel.y) || (onlyStroke?false:ctx.isPointInPath(path,pixel.x, pixel.y))){ return "pointer"; } return "default"; }
以上就是用isPointInStroke和isPointInPath这两个api做canvas图形鼠标交互的方法,这里只是简单修改了鼠标的样式,实际上,我们可以以此为基础,做更多的鼠标事件,比如存储下当前hover的图形信息,在点击时,触发一个该图形的点击事件。更进一步,我们还可以通过这种方式,为每一个图形封装一个自定义事件绑定函数,在每一帧中,只对绑定了鼠标事件的图形进行监听,总之,后续的功能迭代可以有很多中,优化方案也有很多,大家有空了可以自己研究研究。
demo:
文章
3.1K人气
8粉丝
1关注
©Copyrights 2016-2022 杭州易知微科技有限公司 浙ICP备2021017017号-3 浙公网安备33011002011932号
互联网信息服务业务 合字B2-20220090