当前位置: 首页 > 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…...

面板安全能力持续增强,新增日志审计功能,1Panel开源面板v1.3.0发布

2023年6月12日&#xff0c;现代化、开源的Linux服务器运维管理面板1Panel正式发布v1.3.0版本。 在这一版本中&#xff0c;1Panel进一步增强了安全方面的能力&#xff0c;包括新增SSH配置管理、域名绑定和IP授权支持&#xff0c;以及启用网站防盗链功能。此外&#xff0c;该版本…...

k8s学习-CKS考试必过宝典

目录 CKS考纲集群安装&#xff1a;10%集群强化&#xff1a;15%系统强化&#xff1a;15%微服务漏洞最小化&#xff1a;20%供应链安全&#xff1a;20%监控、日志记录和运行时安全&#xff1a;20% 报名模拟考试考试注意事项考前考中考后 参考 CKS考纲 集群安装&#xff1a;10% 使…...

jmeter如何将上一个请求的结果作为下一个请求的参数

目录 1、简介 2、用途 3、下载、简单应用 4、如何将上一个请求的结果作为下一个请求的参数 1、简介 在JMeter中&#xff0c;可以通过使用变量来将上一个请求的结果作为下一个请求的参数传递。 ApacheJMeter是Apache组织开发的基于Java的压力测试工具。用于对软件做压力测…...

JAVA如何学习爬虫呢?

学习Java爬虫需要掌握以下几个方面&#xff1a; Java基础知识&#xff1a;包括Java语法、面向对象编程、集合框架等。 网络编程&#xff1a;了解HTTP协议、Socket编程等。 HTML、CSS、JavaScript基础&#xff1a;了解网页的基本结构和样式&#xff0c;以及JavaScript的基本语…...

距离保护原理

距离保护是反映故障点至保护安装处的距离&#xff0c;并根据距离的远近确定动作时间的一种保护。故障点距保护安装处越近&#xff0c;保护的动作时间就越短&#xff0c;反之就越长&#xff0c;从而保证动作的选择性。测量故障点至保护安装处的距离&#xff0c;实际上就是用阻抗…...

从微观世界的RST包文视角助力企业网络应用故障排查和优化

1. 前言 随着互联网的普及和发展&#xff0c;各行业的业务和应用越来越依赖于网络。然而&#xff0c;网络环境的不稳定性和复杂性使得出现各种异常现象的概率变得更高了。这些异常现象会导致业务无法正常运行&#xff0c;给用户带来困扰&#xff0c;甚至影响企业的形象和利益。…...

企业微信开发,简单测试。

企业微信开发&#xff0c;参考文档&#xff1a; https://github.com/wechat-group/WxJava/wiki...

element日期选择设置默认时间el-date-picker

<el-date-pickerv-model"rangeDate"style"width:350px"type"daterange"value-format"yyyy-MM-dd"change"dataChange"start-placeholder"开始日期"end-placeholder"结束日期"></el-date-picker…...

AB32VG:SDK_AB53XX_V061(3)IO口复用功能的补充资料

文章目录 1.IO口功能复用表格2.功能映射寄存器 FUNCTION03.功能映射寄存器 FUNCTION14.功能映射寄存器 FUNCTION2 AB5301A的官方数据手册很不完善&#xff0c;没有开放出来。我通过阅读源码补充了一些关于IO口功能复用寄存器的资料。 官方寄存器文档&#xff1a;《 AB32VG1_Re…...

UnityVR--组件10--UGUI简单介绍

目录 前言 UI基础组件 1. Canvas 2. EventSystem 3. Image 4. Text/TextMeshPro/InputField 5. Button控件 其他 前言 UGUI是Unity推出的新的UI系统&#xff0c;它与Unity引擎结合得更紧密&#xff0c;并拥有强大的屏幕自适应和更简单的深度处理机制&#xff0c;更容易使用和…...

网站建设执招标评分表/如何做网页

最近学习Runtime&#xff0c;顺便总结一下在Objective-C中KVO使用到的Runtime机制。 系统的KVO使用 故事还得从OC的KVO说起&#xff0c;一般的我们使用KVO类似的如下所示&#xff0c;创建一个对象&#xff0c;然后调用addObserver方法进行某个属性的监听&#xff0c;有意思的是…...

html做的旅游网站/百度最新秒收录方法2023

在macos里ssh的秘钥放在~/.ssh文件夹中: id_rsa.pub就是秘钥所在的文件,这个文件是通过这一串命令生成的: ssh-keygen -t -C "你的GitHub邮箱" 这一串执行后会有三个提示,连环回车就行了都不用填,如果不是第一次生成会提示是否覆盖,覆盖就完事儿了. 生成后将文件中…...

网站色差表/软文代写公司

晚上帮同事解决一个merge问题的时候&#xff0c;发现了我去年在上一家公司时候写的一篇非常不错的关于git的文章&#xff0c;分享出来&#xff0c;有助于更加高效地使用git。 &#xff08;1&#xff09;配置lg2 git默认的日志查看命令是“git log”&#xff0c;界面显示如下&am…...

海南专业做网站的公司/域名注册需要多久

献给所有得到过所爱却又失去所爱的人&#xff0c;给从未得到所爱的人&#xff0c;给因为仍旧爱着&#xff0c;于是选择自欺欺人的人。这&#xff0c;是一面镜子&#xff0c;勇敢面对&#xff0c;勇敢放弃&#xff0c;勇敢重新开始。 当她不爱你的时候&#xff0c;无论过去她是…...

企业网站的模块功能/青岛网站建设

打印出一个等腰三角形。 思路很容易&#xff1a;双重for循环处理。 难点在于如何控制等腰&#xff0c;让图形像个金字塔&#xff0c;可以想象一个矩形挖成等腰三角形 package com.math.forth;/**** 打印出一个等腰三角形。 思路很容易&#xff1a;双重for循环处理。 * 难点在于…...

广告制作公司开票范围/百度seo排名优化如何

安装说明 下载Pixymon和pixy对应markone的固件&#xff0c;在这里。 固件必须是firmware_IRLOCKpixy_1.0.1.hex irlock markone 然后固件里 irlock pixy 给Pixy刷固件的方法 打开PixyMon&#xff0c;将Pixy连接到电脑&#xff0c;然后长按Pixy上的按钮&#xff0c;进入烧…...