当前位置: 首页 > news >正文

图形编辑器开发:最基础但却复杂的选择工具

大家好,我是前端西瓜哥。

对于一个图形设计软件,它最基础的工具是什么?选择工具

但这个选择工具,却是相当的复杂。这次我来和各位,细说细说选择工具的一些弯弯道道。

我正在开发的图形设计工具的:

https://github.com/F-star/suika

线上体验:

https://blog.fstars.wang/app/suika/

单选

最基本的,要做到单个图形的选中。

光标停留在图形上方,按下鼠标左键,这个图形就被选中了。这就是一个简单的选中了单个图形的场景。

注意必须是 mousedown,不是 click。后面会说为什么。

在代码层,我们会使用 “图形拾取” 算法确定光标落在哪个图形的点击区域上,注意考虑隐藏、锁定、组的情况。

如果你对图形拾取的细节感兴趣,可以看我的这篇文章:

《如何在 Canvas 上实现图形拾取?》

隐藏和锁定的图形会被忽略,如果点的是组下的一个元素,要将整个组的所有元素都选中。

清空 被选中图形集合(暂且叫做 selectSet),然后把这个图形添加进去。

selectSet.clear()
selectSet.add(targetEl)

选中集合保存的是被选中的图形,可以保存 id,也可以是图形对象。

在渲染层,会对被选中的图形进行轮廓高亮,让用户有感知。

此外还会有一个 矩形选中框,上面还会有控制点,让用户可以缩放和旋转图形。

选中框是图形的包围盒,通常是 带旋转的 OBB 包围盒

如果点击到空白区域,要将 selectSet 清空。

在这里插入图片描述

多选

有时候我们希望选中出多个图形。

通常的做法是,按住 Shift 键,然后点击一个图形。

同时也要 支持取消选中:原来被选中的一个图形,我按住 Shift 再

代码的核心逻辑是:

如果这个图形不在 selectSet 中,将其加入;如果这个图形在 selectSet,将其移除

if (event.shiftKey) {if (selectSet.has(targetEl)) {selectSet.delete(targetEl)} else {selectSet.add(targetEl)}
}

多个图形被选中了,除了给它们高亮轮廓线,我们还需要用一个更大的矩形选中框包裹所有被选中图形

一个小点:如果是取消选中的逻辑,需要鼠标释放后才更新 selectSet。因为要防止和后面会说的按住 Shift 水平垂直拖拽冲突。

在这里插入图片描述

框选

框选,提供了 一次性选中大量特定区域内图形 的能力。

在空白区域按下鼠标拖拽,然后释放,可以构造出一个矩形,这个矩形我们称为 “选区”。

在这里插入图片描述

选区矩形会和图形进行碰撞检测判断,决定将哪些图形是被框选中的。

碰撞检测有三种方案:

  1. 选区矩形和选中图形的包围盒属于 包含(contain)关系
  2. 选区矩形和选中图形的包围盒属于 相交(intersect)关系
  3. 不使用包围盒,精准判断是否有真正的 像素上的相交

个人比较推荐相交的判断方案,figma 也选择了该方案。

如果你对碰撞检测的细节感兴趣,可以看我之前写的文章:

《图形编辑器——矩形选区是如何实现选中多个图形的?》

《几何算法:矩形碰撞和包含检测算法》

框选可以和多选结合。即你可以按住 Shift 键,然后去框选。

它的效果是和按住 Shift 一个个去选中图形的效果是一样的。

核心代码实现:

if (!event.shiftKey) {selectSet.clear();
}for (const el of elementsInScence) {// 判断是否碰撞,这个方法if (isRectIntersect(selectionBox, el)) {// 普通框选if (!event.shiftKey) {selectSet.add(el);}// 连续和框选的组合else {if (selectSet.has(el)) {selectSet.delete(el);} else {selectSet.add(el);}}}
}

移动

选择工具,主要是用来选择,选中后一个很普遍的操作是:移动选中元素

所以这也是它有时候也被叫做 移动工具 的原因。

移动的交互过程:

  1. 光标停留在已经被选中的图形上,按下鼠标不放;
  2. 然后拖拽鼠标,被选中图形跟随光标移动;
  3. 释放鼠标,表示移动到目标位置,移动结束。

在这里插入图片描述

代码核心实现:

  1. 移动前此时记录图形的位置,和起始位置;
  2. 拖拽时计算相对位移,更新图形的位置;
  3. 释放时重置状态,以及记录到历史记录中。
// 图形移动前位置
let elStartCoords = [];
// 鼠标按下事件的光标位置,计算偏移量时作为基准
let startCoord = { x: undefined, y: undefined };const onStart = (e) => {// 记录初始坐标elStartCoords = elements.map((el) => ({ x: el.x, y: el.y }));startCoord.x = e.clientX;startCoord.y = e.clientY;
};const onDrag = (e) => {// 计算偏移量,更新坐标const dx = e.clientX - startCoord.x;const dy = e.clientY - startCoord.y;elements.forEach((el, i) => {el.x = elStartCoords[i].x + dx;el.y = elStartCoords[i].y + dy;});
};const onEnd = () => {// 重置状态elStartCoords = [];startCoord = { x: undefined, y: undefined };
};

按住 Shift 键的垂直水平移动

假设我们做好了几个对齐的图形,当我们移动其中一个图形的时候,希望能够保持原来的对齐。

这时候,限制移动为水平或垂直方向就很有用。

通常通过在拖拽时按住 Shift 来开启这个能力。

在这里插入图片描述

要点:

  1. 拖拽的中途从没按住 Shift 到按住,要立即响应,代码实现上要补一个键盘事件监听,而不是靠鼠标移动事件,因为你不移动鼠标,被选中元素就不会更新。
  2. 比较 dx 和 dy 的大小。dx 大,水平移动;dy 大,垂直移动。这样图形就能尽量靠近十字线(水平线+垂直线)

对齐到像素网格

对齐到网格,开启后,让图形在移动的时候,让图片尽量贴到网格线上。

在这里插入图片描述

做法是将一个或多个图形的包围盒(AABB)的左上角坐标,进行取余,得到一个落在网格线上的位置,用这位置去更新选中图形。

扩展能力:控制点

选中图形,是为了对它们进行操作。

这些 操作的实现,要通过控制点来落地

常见的有:

  • 缩放控制点,在图形选中框的 4 个角上;

  • 旋转控制点,拖拽它设置图形的旋转,旋转控制点;

  • 给图形设置渐变填充色,需要指定两种颜色的颜色和位置,需要的 渐变色控制点

下面是 figma 的缩放和旋转演示,我开发的编辑器还没实现完整。

在这里插入图片描述

此外,不同图形绘制工具可能会有它们独有的操作方式,这些都需要你根据图形的特性去设计。

看看 Figma 对不同图形的特殊控制点逻辑。

在这里插入图片描述

所以选择工具模块在设计上,要提供 注册各种类型图形控制点逻辑 的能力。

在 “图形拾取” 时,要把控制点也考虑进来,光标是否点在控制点上。

如果点在控制点上,拖拽逻辑就要走控制点的逻辑,不再走选择工具的基础逻辑。

其他

还有一些可考虑实现的增强能力:

  • 双击,进入编辑模式,进行一些更复杂的操作,比如可以变成贝塞尔曲线操作任意点。
  • 移动时,用线条显示和其他图形的点(比如中点、选中框角落的 4 个点)的距离,并在很接近时吸附过去。

结尾

总结一下,选择工具,是一款图形设计软件最基础的功能。

它的作用是选中的图形,对它们进行操作,目的是 更新指定图形属性

最基础的操作是移动,接着是通过控制点实现的增强操作。

控制点操作的两个基本能力是旋转和缩放。然后我们会根据不同类型的图形,去实现不同的控制点逻辑。

说是工具的一种,但它其实的定位更多是底层的基础建设。

我是前端西瓜哥,欢迎关注我,学习更多图形开发知识。

相关文章:

图形编辑器开发:最基础但却复杂的选择工具

大家好,我是前端西瓜哥。 对于一个图形设计软件,它最基础的工具是什么?选择工具。 但这个选择工具,却是相当的复杂。这次我来和各位,细说细说选择工具的一些弯弯道道。 我正在开发的图形设计工具的: http…...

apk签名-signapk.jar

如果做平台app开发,需要签platform签名,除了通过adroid.bp或者android.mk的方式使用AOSP整个大工程中签名外,还可以直接通过signapk.jar的方式进行签名,效率更高更快捷简便。 首先我们来回顾下AOSP平台签名的办法。 Android.mk 使…...

【100个高大尚求职简历】简历模板+修改教程+行业分类简历模板 (涵盖各种行业) (简历模板+编辑指导+修改教程)

文章目录 1 简历预览2 简历下载 很多人说自己明明投了很多公司的简历,但是都没有得到面试邀请的机会。自己工作履历挺好的,但是为什么投自己感兴趣公司的简历,都没有面试邀请的机会。反而是那些自己没有投递的公司,经常给自己打电…...

Nginx平滑升级版本或添加模块

文章目录 一、Nginx 平滑升级二、升级失败 回滚操作三、遇到问题 一、Nginx 平滑升级 一般有两种情况下需要升级 nginx,一种是确实要升级 nginx 的版本,另一种是要为 nginx 添加新的模块。 Nginx平滑升级其原理简单概括: (1&am…...

高阶复杂网络重建:从时间序列中重建高阶网络

论文链接:https://www.nature.com/articles/s41467-022-30706-9 一、为什么要研究高阶网络? 复杂网络跟我们生活息息相关,例如社交网络的信息传播,疾病的感染扩散和基因调控网络的相互作用等。越来越多的研究突破了传统网络中两…...

Day05 03-MySQL主从-主主原理与搭建详解

文章目录 第十六章 MySQL的系统架构(主从架构)16.1 MySQL集群架构的介绍16.1.1 主从架构介绍16.1.2 主从复制的原理 16.2 MySQL主从复制的实现16.2.1 环境说明16.2.2 主库配置16.2.3 从库配置16.2.4 主从复制测试 16.3 MySQL主主复制的实现16.3.1 主主复…...

STL之vector

目录 vector模拟实现一. vector的基本框架二. 常用方法及实现1.初始化和清理a. 默认构造函数b. 析构函数 2. 迭代器a. beginb. end 3.数据访问a. sizeb. capacityc. operator[]d. frontc. back 4.增删查改操作a. reserveb. resizec. insertd. push_backe. erasef. pop_back 5.构…...

2020年CSP-J认证 CCF非专业级别软件能力认证第一轮真题-单项选择题解析

2020 CCF认证第一轮(CSP-J)真题 一、单项选择题 (共15题,每2分,共30分;每题有且有一个正确选项) 1、在内存储器中每个存储单元都被赋予一个唯一的序号,称为 A、下标 B、序号 C、地址 D、编号 答案:C…...

vscode Delete `␍⏎·····`

在公司拉取代码报错 Delete ␍⏎,首先问题的关键是换行导致,相信你看别的博客也知道为什么了,但是我使用别的博客的解决办法,没搞定,无论是配置 auto 还是命令行执行,都不行 下面介绍我的解决办法 我使用…...

读书笔记-《ON JAVA 中文版》-摘要16[第十六章 代码校验]

文章目录 第十六章 代码校验1. 测试1.1 单元测试1.2 JUnit1.3 测试覆盖率的幻觉 2. 前置条件2.1 断言(Assertions)2.2 Java 断言语法2.3 Guava 断言2.4 使用断言进行契约式设计2.4.1 检查指令2.4.2 前置条件2.4.3 后置条件2.4.4 不变性2.4.5 放松 DbC 检…...

SQL Server:打造高效数据管理系统的利器

使用SQL Server进行数据管理 简介 SQL Server是由Microsoft开发的一款关系型数据库管理系统,它可以用于存储和管理大量结构化数据。本篇博客将介绍如何使用SQL Server进行数据管理。 数据库连接 在开始使用SQL Server之前,需要先建立与数据库的连接。…...

代码随想录二刷day20 | 二叉树之 654.最大二叉树 617.合并二叉树 700.二叉搜索树中的搜索 98.验证二叉搜索树

day20 654.最大二叉树617.合并二叉树700.二叉搜索树中的搜索98.验证二叉搜索树 654.最大二叉树 题目链接 解题思路: 本题属于构造二叉树,需要使用前序遍历,因为先构造中间节点,然后递归构造左子树和右子树。 确定递归函数的参数…...

python基础知识(十三):numpy库的基本用法

目录 1. numpy的介绍2. numpy库产生矩阵2.1 numpy将列表转换成矩阵2.2 numpy创建矩阵 3. numpy的基础运算4. numpy的基础运算25. 索引 1. numpy的介绍 numpy库是numpy是python中基于数组对象的科学计算库。 2. numpy库产生矩阵 2.1 numpy将列表转换成矩阵 import numpy as …...

【SA8295P 源码分析】16 - TouchScreen Panel (TP)线程函数 tp_recv_thread() 源码分析

【【SA8295P 源码分析】16 - TouchScreen Panel (TP)线程函数 tp_recv_thread 源码分析 一、TP 线程函数:tp_recv_thread()二、处理&上报 坐标数据 cypress_read_touch_data()系列文章汇总见:《【SA8295P 源码分析】00 - 系列文章链接汇总》 本文链接:《【SA8295P 源码…...

Python3数据分析与挖掘建模(13)复合分析-因子关分析与小结

1.因子分析 1.1 探索性因子分析 探索性因子分析(Exploratory Factor Analysis,EFA)是一种统计方法,用于分析观测变量之间的潜在结构和关联性。它旨在确定多个观测变量是否可以归结为较少数量的潜在因子,从而帮助简化…...

【stable diffusion】图片批量自动打标签、标签批量修改(BLIP、wd14)用于训练SD或者LORA模型

参考: B站教学视频【:AI绘画】新手向!Lora训练!训练集准备、tag心得、批量编辑、正则化准备】官方教程:https://github.com/darkstorm2150/sd-scripts/blob/main/docs/train_README-en.md#automatic-captioning 一、…...

TCP可靠数据传输

TCP的可靠数据传输 1.TCP保证可靠数据传输的方法 TCP主要提供了检验和、序号/确认号、超时重传、最大报文段长度、流量控制等方法实现了可靠数据传输。 检验和 通过检验和的方式,接收端可以检测出来数据是否有差错和异常,假如有差错就会直接丢失该TC…...

Python 私有变量和私有方法介绍

Python 私有变量和私有方法介绍 关于 Python 私有变量和私有方法,通常情况下,开发者可以在方法或属性名称前加上单下划线(_),以表示该方法或属性仅供内部使用,但这只是一种约定,并没有强制执行禁…...

Kotlin Lambda表达式和匿名函数的组合简直太强了

Kotlin Lambda表达式和匿名函数的组合简直太强了 简介 首先,在 Kotlin 中,函数是“第一公民”(First Class Citizen)。因此,它们可以被分配为变量的值,作为其他函数的参数传递或者函数的返回值。同样&…...

uniapp 小程序 获取手机号---通过前段获取

<template><!-- 获取手机号&#xff0c;登录内容 --><view><!-- 首先需要先登录获取code码&#xff0c;然后才可以获取用户唯一标识openid以及会话密钥及用于解密获取手机的加密信息 --><view click"login">登录</view><view…...

在软件开发中正确使用MySQL日期时间类型的深度解析

在日常软件开发场景中&#xff0c;时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志&#xff0c;到供应链系统的物流节点时间戳&#xff0c;时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库&#xff0c;其日期时间类型的…...

Neo4j 集群管理:原理、技术与最佳实践深度解析

Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...

uniapp微信小程序视频实时流+pc端预览方案

方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度​WebSocket图片帧​定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐​RTMP推流​TRTC/即构SDK推流❌ 付费方案 &#xff08;部分有免费额度&#x…...

微信小程序云开发平台MySQL的连接方式

注&#xff1a;微信小程序云开发平台指的是腾讯云开发 先给结论&#xff1a;微信小程序云开发平台的MySQL&#xff0c;无法通过获取数据库连接信息的方式进行连接&#xff0c;连接只能通过云开发的SDK连接&#xff0c;具体要参考官方文档&#xff1a; 为什么&#xff1f; 因为…...

高考志愿填报管理系统---开发介绍

高考志愿填报管理系统是一款专为教育机构、学校和教师设计的学生信息管理和志愿填报辅助平台。系统基于Django框架开发&#xff0c;采用现代化的Web技术&#xff0c;为教育工作者提供高效、安全、便捷的学生管理解决方案。 ## &#x1f4cb; 系统概述 ### &#x1f3af; 系统定…...

[USACO23FEB] Bakery S

题目描述 Bessie 开了一家面包店! 在她的面包店里&#xff0c;Bessie 有一个烤箱&#xff0c;可以在 t C t_C tC​ 的时间内生产一块饼干或在 t M t_M tM​ 单位时间内生产一块松糕。 ( 1 ≤ t C , t M ≤ 10 9 ) (1 \le t_C,t_M \le 10^9) (1≤tC​,tM​≤109)。由于空间…...

python读取SQLite表个并生成pdf文件

代码用于创建含50列的SQLite数据库并插入500行随机浮点数据&#xff0c;随后读取数据&#xff0c;通过ReportLab生成横向PDF表格&#xff0c;包含格式化&#xff08;两位小数&#xff09;及表头、网格线等美观样式。 # 导入所需库 import sqlite3 # 用于操作…...

OPENCV图形计算面积、弧长API讲解(1)

一.OPENCV图形面积、弧长计算的API介绍 之前我们已经把图形轮廓的检测、画框等功能讲解了一遍。那今天我们主要结合轮廓检测的API去计算图形的面积&#xff0c;这些面积可以是矩形、圆形等等。图形面积计算和弧长计算常用于车辆识别、桥梁识别等重要功能&#xff0c;常用的API…...

Angular中Webpack与ngx-build-plus 浅学

Webpack 在 Angular 中的概念 Webpack 是一个模块打包工具&#xff0c;用于将多个模块和资源打包成一个或多个文件。在 Angular 项目中&#xff0c;Webpack 负责将 TypeScript、HTML、CSS 等文件打包成浏览器可以理解的 JavaScript 文件。Angular CLI 默认使用 Webpack 进行项目…...

Docker 镜像上传到 AWS ECR:从构建到推送的全流程

一、在 EC2 实例中安装 Docker&#xff08;适用于 Amazon Linux 2&#xff09; 步骤 1&#xff1a;连接到 EC2 实例 ssh -i your-key.pem ec2-useryour-ec2-public-ip步骤 2&#xff1a;安装 Docker sudo yum update -y sudo amazon-linux-extras enable docker sudo yum in…...