如何使用canvas绘制多个图形块,并可以拖动。

文章介绍了一种使用HTML5Canvas通过鼠标事件动态绘制并拖动图形的方法。通过构造函数RectFactory,可以创建可拖动的矩形,利用beginPath(),moveTo(),lineTo()等方法绘制图形,并在鼠标按下和移动事件中更新图形位置。此外,还实现了判断鼠标点击是否在图形内的功能,以实现拖动效果。
部署运行你感兴趣的模型镜像

在日常项目开发中,我们或多或少会遇到使用canvas的场景,下面我分享一个使用canvas绘制多个图形,并可以进行拖动的案例,例如:

在这里插入图片描述

通过canvas手册,我们可以获取到绘制图形的方法:ctx.fillRect(x, y, width, height),其中,x, y 分别为绘制的起始坐标,width, height 分别为绘制的宽度和高度,但是这种方法有个弊端,如果我们是通过鼠标来绘制,点击开始,移动,鼠标抬起,绘制完成,那么,对于上述的方法来说,局限性就比较大。

当然,也不是说没有解决办法,canvas中提供了另外的一种绘制图形方式:例如:

var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(75, 50);
ctx.lineTo(100, 75);
ctx.lineTo(100, 25);
ctx.fill();

这种方式使我们的绘制更加灵活,我们可以多次使用ctx.lineTo(x, y) 来绘制我们想要的图形路径,最后进行填充,得到最终图形。

那么现在,我们就通过上述的方法,通过鼠标来绘制不同大小的长方形,并可以进行拖动更换位置。
按照通常惯例,我们需要获取canvas,并设置相关的宽高:

<!--
 * @Author: ChenJinZhu
 * @Date: 2023-04-15 15:55:29
 * @Contact:1483364913@qq.com
 * @FilePath: F:\桌面文件\demo.html
-->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div>
        <canvas id="canvas"></canvas>
    </div>
</body>

<script>
    // 获取canvas
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');

    // 设置宽高
    const w = 1200, h = 800;
    canvas.width = w * devicePixelRatio;
    canvas.height = h * devicePixelRatio;

</script>
</html>

接下来,就是使用canvas提供的方法,来绘制。
因为是需要绘制多个,所以,我定义了一个构造函数,使用这个函数,生成数组对象,通过调用对象内部的绘制方法就能绘制出相关图形:

class RectFactory {
    /**
     * @startX:鼠标 点击时 的坐标轴 X 值
     * @startY:鼠标 点击时 的坐标轴 Y 值
     * @endX:鼠标 抬起时 的坐标轴 X 值
     * @endY:鼠标 抬起时 的坐标轴 Y 值
     * */
    constructor(startX, startY, endX, endY) {
        this.startX = startX;
        this.startY = startY;
        this.endX = endX;
        this.endY = endY;

    }
    // 为了保持不同的鼠标绘制方向都能绘制出图形,对坐标进行处理。
    get minX() {
        return Math.min(this.startX, this.endX);
    }
    get maxX() {
        return Math.max(this.startX, this.endX);
    }
    get minY() {
        return Math.min(this.startY, this.endY);
    }
    get maxY() {
        return Math.max(this.startY, this.endY);
    }

    draw() {
        ctx.beginPath();
        ctx.moveTo(this.minX * devicePixelRatio, this.minY * devicePixelRatio);
        ctx.lineTo(this.maxX * devicePixelRatio, this.minY * devicePixelRatio);
        ctx.lineTo(this.maxX * devicePixelRatio, this.maxY * devicePixelRatio);
        ctx.lineTo(this.minX * devicePixelRatio, this.maxY * devicePixelRatio);
        ctx.lineTo(this.minX * devicePixelRatio, this.minY * devicePixelRatio);
        // 填充颜色
        ctx.fillStyle = '#ffb60f';
        ctx.fill();
        ctx.strokeStyle = '#fff';
        ctx.lineCap = 'square';
        ctx.lineWidth = 1 * devicePixelRatio;
        ctx.stroke();
    }
}

图形的绘制构造函数写完之后,我们只需要对对应的鼠标函数进行完善即可:

canvas.onmousedown = (e) => {
    // 鼠标按下时,需要获取canvas相对浏览器视窗的位置
    const rect = canvas.getBoundingClientRect();
    // 获取鼠标按下时,相对canvas的位置。
    // 因为canvas的坐标系是从左上角开始的,所以需要减去canvas相对浏览器视窗的位置。
    const clickX = e.clientX - rect.left;
    const clickY = e.clientY - rect.top;
    // 执行绘制方法
    drawRect(e, clickX, clickY, rect)
}

const drawRect = (e, clickX, clickY, rect) => {
    // 鼠标按下,立即通过构造函数创建新的对象
    const shape = new RectFactory(clickX, clickY)
    
    // 全局定义的数组,每次鼠标按下,就将创建好的对象推入数组中。
    shapeCollection.push(shape)
    
    // 执行鼠标移动事件,移动时,同时修改当前对象的结束坐标
    window.onmousemove = (evt) => {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        shape.endX = evt.clientX - rect.left;
        shape.endY = evt.clientY - rect.top;
    }
}

最后,我们只需要一直循环 shapeCollection,并调用里面的 draw() 方法,即可:

const draw = () => {
    requestAnimationFrame(draw)
    for (const pp of shapeCollection) {
        pp.draw()
    }
}
draw()

到此,我们就可以随便绘制图形咯。但是,此时功能还未结束,我们还需要拖动的功能,那么,在点击的时候,只需要判断下当前的位置是不是在图形的内部,此判断方法,需要加在构造函数内部,所以,只需要对代码进一步修改即可,以下是次功能的全部代码:

<!--
 * @Author: ChenJinZhu
 * @Date: 2023-04-15 15:55:29
 * @Contact:1483364913@qq.com
 * @FilePath: F:\桌面文件\demo.html
-->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div>
        <canvas id="canvas" style="background-color: #94e8ea"></canvas>
    </div>
</body>

<script>
    // 获取canvas
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');

    // 所有绘制的图形推入数组中。
    let shapeCollection = []

    // 设置宽高
    const w = 1200, h = 800;
    canvas.width = w * devicePixelRatio;
    canvas.height = h * devicePixelRatio;
    class RectFactory {
        /**
         * @startX:鼠标 点击时 的坐标轴 X 值
         * @startY:鼠标 点击时 的坐标轴 Y 值
         * @endX:鼠标 抬起时 的坐标轴 X 值
         * @endY:鼠标 抬起时 的坐标轴 Y 值
         * */
        constructor(startX, startY, endX, endY) {
            this.startX = startX;
            this.startY = startY;
            this.endX = endX;
            this.endY = endY;

        }
        // 为了保持不同的鼠标绘制方向都能绘制出图形,对坐标进行处理。
        get minX() {
            return Math.min(this.startX, this.endX);
        }
        get maxX() {
            return Math.max(this.startX, this.endX);
        }
        get minY() {
            return Math.min(this.startY, this.endY);
        }
        get maxY() {
            return Math.max(this.startY, this.endY);
        }

        draw() {
            ctx.beginPath();
            ctx.moveTo(this.minX * devicePixelRatio, this.minY * devicePixelRatio);
            ctx.lineTo(this.maxX * devicePixelRatio, this.minY * devicePixelRatio);
            ctx.lineTo(this.maxX * devicePixelRatio, this.maxY * devicePixelRatio);
            ctx.lineTo(this.minX * devicePixelRatio, this.maxY * devicePixelRatio);
            ctx.lineTo(this.minX * devicePixelRatio, this.minY * devicePixelRatio);
            ctx.fillStyle = '#ffb60f';
            ctx.fill();
            ctx.strokeStyle = '#fff';
            ctx.lineCap = 'square';
            ctx.lineWidth = 1 * devicePixelRatio;
            ctx.stroke();
        }

        // 判断当前点击位置是否在图形内部
        isInside(x, y) {
            return x >= this.minX && x <= this.maxX && y >= this.minY && y <= this.maxY
        }
    }

    canvas.onmousedown = (e) => {
        const rect = canvas.getBoundingClientRect();
        const clickX = e.clientX - rect.left;
        const clickY = e.clientY - rect.top;
        const shape = getRect(clickX, clickY)

        if (shape) {
            moveRect(e, clickX, clickY, rect, shape)
        } else {
            drawRect(e, clickX, clickY, rect)
        }

    }
    canvas.onmouseup = () => {
        canvas.onmousemove = null;
        window.onmousemove = null;
    };

    // 鼠标点击canvas查看是否点击到了已经绘制的路线,若是,则返回相关线的对象,若否,返回null
    const getRect = (x, y) => {
        for (let i = shapeCollection.length - 1; i >= 0; i--) {
            const element = shapeCollection[i];
            if (element.isInside(x, y)) {
                return element;
            }
        }
        return null
    }

    const drawRect = (e, clickX, clickY, rect) => {
        const shape = new RectFactory(clickX, clickY)
        shapeCollection.push(shape)
        window.onmousemove = (evt) => {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            shape.endX = evt.clientX - rect.left;
            shape.endY = evt.clientY - rect.top;
        }
    }

    const moveRect = (e, clickX, clickY, rect, shape) => {
        const { startX, startY, endX, endY } = shape;
        window.onmousemove = (evt) => {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            const distanceX = evt.clientX - rect.left - clickX;
            const distanceY = evt.clientY - rect.top - clickY;
            shape.startX = startX + distanceX;
            shape.startY = startY + distanceY;
            shape.endX = endX + distanceX;
            shape.endY = endY + distanceY;
        }
    };

    const draw = () => {
        requestAnimationFrame(draw)
        for (const pp of shapeCollection) {
            pp.draw()
        }
    }
    draw()
</script>
</html>

如果此篇文章对您有一定帮助,请给一个赞吧~,您的赞,是对我最大的鼓励 ٩(๑❛ᴗ❛๑)۶

您可能感兴趣的与本文相关的镜像

Llama Factory

Llama Factory

模型微调
LLama-Factory

LLaMA Factory 是一个简单易用且高效的大型语言模型(Large Language Model)训练与微调平台。通过 LLaMA Factory,可以在无需编写任何代码的前提下,在本地完成上百种预训练模型的微调

<think>好的,我现在需要解决用户的问题:如何在Canvas中为绘制图形绑定和处理点击事件。用户之前可能已经了解过Canvas的基本绘图,现在想添加交互功能。 首先,我得回忆一下Canvas的基础知识。Canvas是位图,绘制的内容没有DOM元素,所以不能像普通HTML元素那样直接绑定事件。这点用户可能已经知道,但需要明确的解决方案。 接下来,用户提到引用[3]中的例子,那里有通过JavaScript模拟拖拽和事件处理的部分。比如用mousedown、mousemove等事件来跟踪状态。这可能对处理点击事件有帮助,因为点击事件可以分解为mousedown和mouseup的组合,且需要检测鼠标位置是否在图形内部。 然后,引用[4]中提到了Canvas绘图的基本方法,比如绘制路径、矩形等。要为这些图形添加点击事件,关键点在于判断点击位置是否在图形区域内。这需要数学计算,比如矩形是否包含点,或者点是否在路径内。 可能的步骤是:监听canvas元素的点击事件,获取点击的坐标,然后遍历所有绘制图形,检查坐标是否在某个图形内部。如果是,则触发相应的处理函数。 对于具体图形类型的检测: 1. 矩形:检查坐标是否在矩形的x和y范围内。 2. 圆形:计算点击位置到圆心的距离是否小于半径。 3. 路径(如多边形):可能需要使用isPointInPath方法,但要注意需要在绘制时保存路径数据,或者在点击检测时重新绘制到临时上下文来判断。 另外,引用[3]中的拖拽示例使用了mousedown和mousemove事件的状态跟踪,类似的方法可以用于点击事件的判断,比如记录点击时的位置,在处理时进行检测。 还要考虑性能问题,如果图形很多,每次点击都要遍历所有图形可能会影响性能,这时候可能需要空间划分算法,比如四叉树,来优化检测速度。 用户可能需要具体的代码示例,所以需要提供一个基本的实现,比如绘制一个矩形,绑定点击事件。同时,要提到更复杂的图形如何处理,比如使用isPointInPath或者数学计算。 还需要注意Canvas的坐标转换,比如如果有缩放或平移变换,需要调整坐标点。 总结解决方案步骤: 1. 监听canvas的点击事件。 2. 获取点击坐标,转换为Canvas内部的坐标。 3. 遍历所有图形,检测坐标是否在图形内。 4. 触发对应的处理函数。 需要给出代码示例,比如如何绘制矩形检测点击,以及如何处理圆形的情况。同时,提醒用户保存图形的数据,以便检测时使用。 可能的问题是如何处理动态添加的图形,或者多个图形重叠时的检测顺序。可能需要按绘制顺序反向检测,返回第一个匹配的图形。 最后,确保回答结构清晰,步骤明确,提供代码示例和相关引用支持。</think>在Canvas中为绘制图形添加点击事件需要手动实现坐标检测逻辑,因为Canvas本身不具备DOM元素的层级结构。以下是具体实现方法: ### 一、核心实现步骤 1. **监听Canvas点击事件** 通过`addEventListener`绑定`click`事件,获取点击坐标: ```javascript canvas.addEventListener('click', function(event) { const rect = canvas.getBoundingClientRect(); const x = event.clientX - rect.left; const y = event.clientY - rect.top; checkHit(x, y); // 坐标检测函数 }); ``` 2. **图形数据存储** 需要保存已绘制图形的坐标和尺寸信息用于检测: ```javascript const shapes = [ {type: 'rect', x: 50, y: 50, width: 100, height: 80}, {type: 'circle', x: 200, y: 150, radius: 40} ]; ``` 3. **碰撞检测算法** - **矩形检测**:$x \in [shape.x, shape.x+width]$ 且 $y \in [shape.y, shape.y+height]$ - **圆形检测**:$\sqrt{(x-shape.x)^2 + (y-shape.y)^2} \leq radius$ ### 二、完整代码示例 ```javascript const canvas = document.getElementById('myCanvas'); const ctx = canvas.getContext('2d'); // 绘制图形 ctx.fillStyle = 'blue'; ctx.fillRect(50, 50, 100, 80); // 矩形 ctx.beginPath(); ctx.arc(200, 150, 40, 0, Math.PI*2); // 圆形 ctx.fillStyle = 'red'; ctx.fill(); // 点击检测 canvas.addEventListener('click', function(event) { const rect = canvas.getBoundingClientRect(); const x = event.clientX - rect.left; const y = event.clientY - rect.top; shapes.forEach(shape => { if(shape.type === 'rect' && x >= shape.x && x <= shape.x + shape.width && y >= shape.y && y <= shape.y + shape.height) { alert('点击了矩形'); } if(shape.type === 'circle' && Math.sqrt((x-shape.x)**2 + (y-shape.y)**2) <= shape.radius) { alert('点击了圆形'); } }); }); ``` ### 三、高级优化方案 1. **路径检测**:使用`isPointInPath`方法 ```javascript // 绘制时保存路径 const trianglePath = new Path2D(); trianglePath.moveTo(100,100); trianglePath.lineTo(150,150); trianglePath.lineTo(50,150); trianglePath.closePath(); // 检测时 if(ctx.isPointInPath(trianglePath, x, y)) { alert('点击了三角形'); } ``` 2. **性能优化**:使用空间划分数据结构(如四叉树)来减少检测范围[^4] 3. **事件委托**:通过自定义事件系统实现事件冒泡机制
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值