Canvas 画布:图形的选中和移动 (下)

本文会带大家使用 TypeScript 继续封装构造函数 CanvasContainer。以实现 同时 选中移动 多个 直线、矩形、多边形的功能。

一、Canvas 画布:拖动和缩放 👉👉👉 代码演示

二、Canvas 画布:绘制直线、矩形、多边形 👉👉👉 代码演示

三、Canvas 画布:图形的选中和移动 (上) 👉👉👉 代码演示

四、Canvas 画布:图形的选中和移动 (下) 👉👉👉 代码演示

一、优化图形的数据结构

第一步:首先我们需要对图形的数据结构进行扩展,并定义几个用到的变量。

ts 复制代码
class CanvasContainer {
    /** 是否移动多个图形 */
    public isMoveGraphs = false;
    /** 选中多个图形的框 */
    public boxGraph?: GraphInter = undefined;
    /** 当前已经被框选中的图形 */
    public selectGraphs?: GraphInter[] = undefined;
    /** 记录鼠标按下时所有已经被框选中的图形的位置信息 */
    private mouseDownSelectGraphsInfo?: GraphInter[] = undefined;
    /** 记录鼠标按下时选中框的位置信息 */
    private mouseDownBoxGraphInfo?: GraphInter = undefined;
}

二、多个图形的选中

第二步:我们要实现以下效果:当鼠标按下后,鼠标移动会绘制一个蓝色的 矩形 多选框,如果图形有一个端点在蓝色的多选框内,则表示该图形被选中。

1. 绘制蓝色的矩形

改造 renderActive 函数,新增绘制蓝色多选框的功能。

ts 复制代码
const boxGraph = this.boxGraph?.points || undefined;
if (boxGraph && boxGraph.length) {
    this.ctx.save();
    this.ctx.translate(this.offsetX, this.offsetY);
    this.ctx.scale(this.scale, this.scale);
    // 一个边框是 `rgb(5, 199, 243)`,背景色是 `rgba(5, 199, 243, 0.1)` 的矩形
    this.ctx.strokeStyle = 'rgb(5, 199, 243)';
    this.ctx.fillStyle = 'rgba(5, 199, 243, 0.1)';
    this.ctx.beginPath();
    this.ctx.rect(boxGraph[0].x, boxGraph[0].y, boxGraph[1].x - boxGraph[0].x, boxGraph[1].y - boxGraph[0].y);
    this.ctx.closePath();
    this.ctx.fill();
    this.ctx.stroke();
    this.ctx.restore();
}

2. 计算多选框的位置

ts 复制代码
// 矩形开始的坐标:( 鼠标按下的位置 - 画布的偏移量 ) / 画布的缩放比
const x = (this.mouseDownOffsetX - this.offsetX) / this.scale;
const y = (this.mouseDownOffsetY - this.offsetY) / this.scale;
// 矩形的宽高:( 鼠标当前的位置 - 画布的偏移量 ) / 画布的缩放比
const width = (event.x - this.offsetX) / this.scale - x;
const height = (event.y - this.offsetY) / this.scale - y;
this.boxGraph = {
    id: -1, // 多选框的 id 恒定为 -1,且唯一
    type: 'rect',
    points: [
        { x: x, y: y },
        { x: x + width, y: y + height },
    ],
    select: true,
    area: width * height,
};

3. 判断图形是否被选中

ts 复制代码
// 将上次选中列表的图形置空
this.selectGraphs = [];
const point = this.normalizationPoint('rect', this.boxGraph?.points as PointsType);
this.graphs.forEach((graph) => {
    // 取消选中状态
    graph.select = false;
    const points = this.normalizationPoint(graph.type, graph.points);
    for(let i = 0; i < points.length; i++) {
        // 判断图形是否有端点在多选框内
        if (this.isPointInGraph(points[i], point)) {
            // 如果有在将其设置为选中状态
            graph.select = true;
            // 将其添加到选中列表
            this.selectGraphs?.push(graph);
            // 如果有一个端点被选中,则结束此次循环,节省性能
            break;
        }
    }
});
// 更新画布
this.renderActive(this.selectGraphs);

4. 优化 renderActive 函数

在上个版本中,我们只能选中一个图形,现在我们可以选中多个图形了,所以 renderActive 函数需要有渲染多个图形的能力,我们进行一下优化。

ts 复制代码
// 判断 selectGraph 是不是数组,如果是数组,遍历里面的选中图形,逐个渲染
if (Array.isArray(selectGraph)) {
    selectGraph.forEach(graph => {
        this.drawGraph(this.ctx, graph);
    })
} else {
    this.drawGraph(this.ctx, selectGraph);
}

三、多个图形的移动

第三步:上面我们已经实现了选中图形的功能,现在我们来实现:移动蓝色的多选框,同时移动蓝色多选框已经选中的所有图形。

1. 移动前的准备

在移动图形前,鼠标按下的时候,我们要判断按下的位置上是什么:是蓝色的多选框,还是图形,或者什么也没有。在不同的情况下,我们要做不同的事情。

  • 优化 pointInGraph 函数,这个函数用来判断当前鼠标按下的位置是在那个图形内。此时它还没有判断是不是在蓝色的矩形框内,我们要新增这个功能。

    ts 复制代码
    if (
        this.boxGraph?.points &&
        this.isPointInGraph(
            point,
            this.normalizationPoint('rect', this.boxGraph?.points as PointsType)
        )
    ) {
        return this.boxGraph as GraphInter;
    }
  • 当前位置是蓝色多选框时,我们要记录蓝色多选框和它所选中的图形的位置信息。

    ts 复制代码
    if (this.selectGraph?.id === -1 && this.selectGraphs?.length) {
        if (this.selectGraphs && this.selectGraphs.length > 0) {
            // 移动多个图形
            this.isMoveGraphs = true;
            // 拷贝信息,如果数量多,不建议使用 JSON.parse 和 JSON.stringify
            this.mouseDownSelectGraphsInfo = JSON.parse(JSON.stringify(this.selectGraphs));
            this.mouseDownBoxGraphInfo = JSON.parse(JSON.stringify(this.selectGraph));
        }
    }
  • 当前位置是图形时,我们要记录此图形的位置信息。

    ts 复制代码
    if(this.selectGraph) {
        this.boxGraph = undefined;
        this.selectGraphs = undefined;
        this.isMoveGraphs = false;
        this.graphs.forEach(item => item.select = false);
        this.selectGraph.select = true;
        this.mouseDownSelectGraphInfo = JSON.parse(JSON.stringify(this.selectGraph));
    }
  • 当前位置什么也没时,我们要初始化所有信息。

    ts 复制代码
    this.isMoveGraphs = false;
    this.boxGraph = undefined;
    this.selectGraph = undefined;
    this.selectGraphs = undefined;
    this.graphs.forEach(item => item.select = false);

2. 移动图形

在鼠标按下时,如果选中了蓝色的多选框,那在鼠标移动的时候,我们就要来修改蓝色多选框和选中图形的每一个端点,来移动他们。

ts 复制代码
if (this.isMoveGraphs) {
    // 修改蓝色多选框各个端点的位置
    this.boxGraph = {
        ...this.mouseDownBoxGraphInfo,
        points: this.mouseDownBoxGraphInfo?.points?.map(point => {
            // 新端点的位置 = 鼠标按下时端点的位置 + 鼠标移动的距离
            // 鼠标移动的距离 = ( 鼠标当前的位置 - 鼠标按下时的位置 ) / 画布的缩放比例
            return {
                x: point.x + (event.x - this.mouseDownOffsetX) / this.scale,
                y: point.y + (event.y - this.mouseDownOffsetY) / this.scale,
            }
        })
    } as GraphInter;
    // 修改选中图形各个端点的位置
    this.selectGraphs?.forEach((graph, graphIndex) => {
        graph.points = graph.points.map((_point, pointIndex) => {
            // 鼠标按下时,选中图形的各个端点的位置
            const x = this.mouseDownSelectGraphsInfo?.[graphIndex]?.points?.[pointIndex]?.x as number;
            const y = this.mouseDownSelectGraphsInfo?.[graphIndex]?.points?.[pointIndex]?.y as number;
            return {
                x: x + (event.x - this.mouseDownOffsetX) / this.scale,
                y: y + (event.y - this.mouseDownOffsetY) / this.scale,
            }
        })
    })
    // 重新渲染画布
    this.renderActive(this.selectGraphs);
}
相关推荐
余生H2 天前
即时可玩web小游戏(二):打砖块(支持移动端版) - 集成InsCode快来阅读并即时体验吧~
前端·javascript·inscode·canvas·h5游戏
普兰店拉马努金9 天前
【Canvas与图标】牛皮纸文件袋图标
canvas·图标·文件袋·牛皮纸
德育处主任11 天前
前端啊,拿Lottie炫个动画吧
前端·svg·canvas
GDAL12 天前
深入剖析Canvas的getBoundingClientRect:精准定位与交互事件实现
canvas
剑亦未配妥14 天前
使用js和canvas、html实现简单的俄罗斯方块小游戏
前端·javascript·canvas·1024程序员节
howard200516 天前
2.1 HTML5 - Canvas标签
html5·canvas
普兰店拉马努金1 个月前
【Canvas与标牌】立入禁止标牌
canvas·警示·标识·立入禁止
Anlige1 个月前
Javascript:使用canvas画二维码矩阵
javascript·canvas·qrcode
牛老师讲GIS1 个月前
分享一个从图片中提取色卡的实现
canvas·提取颜色
云樱梦海1 个月前
OpenAI 推出 Canvas 工具,助力用户与 ChatGPT 协作写作和编程
人工智能·chatgpt·canvas