提问 发文

canvas如何进行鼠标事件的监听

赵炎飞

| 2023-07-09 18:30 575 0 0

前言:

众所周知,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:

📎index.html

收藏 0
分享
分享方式
微信

评论

游客

全部 0条评论

轻松设计高效搭建,减少3倍设计改稿与开发运维工作量

开始免费试用 预约演示

扫一扫关注公众号 扫一扫联系客服

©Copyrights 2016-2022 杭州易知微科技有限公司 浙ICP备2021017017号-3 浙公网安备33011002011932号

互联网信息服务业务 合字B2-20220090

400-8505-905 复制
免费试用
微信社区
易知微-数据可视化
微信扫一扫入群