图形编辑器开发:自定义光标 鼠标光标编辑器
connygpt 2024-10-05 14:50 4 浏览
大家好,我是前端西瓜哥。
今天来讲讲如何在图形编辑器中使用自定义光标,并对光标其进行管理。
编辑器 github 地址:
https://github.com/F-star/suika
线上体验:
https://blog.fstars.wang/app/suika/
自定义光标的意义是什么?
光标(游标)在图形界面交互中是非常基础的一环。
它是一个指针,悬浮在屏幕的最上层。除了可以标记出指针的当前位置,同时也会通过它独特的样式,提示用户此时可以执行怎么的操作。
比如抓手(grab)光标,是一个展开的手掌,表示可以对目标进行拖拽操作。
缩放(xx-resize)光标,是一个有方向的单(双)箭头,表示可以往特定方向移动以改变目标大小。
长得像英文字母 I 的文字(text)光标,则提示可以进行文字的操作,细瘦的垂直线是为了更好地点中字符之间的空白区域。
点击(pointer)光标,一根手指(食指,不是中指)伸出来是要干嘛,是为了试探,看到按钮就尝试点一下,表示某个区域是可点击的。
操作系统有丰富的光标样式可以选择,在 Web 网页中可以通过 cursor 样式属性进行设置。
对于一般应用来说,通常是够用的。但对于一个成熟的图形编辑器来说,这还远远不够。
我们还需要一些 更具体的光标样式来向用户传递信息,比如:
- 旋转光标:表示图形可旋转。cursor 属性中没有旋转光标,勉强可用抓手工具做个平替;
- 支持任意度数的缩放光标。cursor 属性的缩放光标只有 45 度的正数倍数光标,这精度远远不够。
- 钢笔工具相关光标:钢笔光标、锚点光标、新增/删除点光标;
等等。
此外,自定义光标还有一个很重要的作用,就是 实现不同平台的视觉一致性。
不同操作系统的 UI 风格是不同的,它们的光标是相当不一致的,会给用户带来不同的体验。
(我希望在 Windows 系统看到 MacOS 的光标)
如何支持自定义光标
没有光标,我们自己造。
好在 cursor 是支持自定义光标的。
具体用法如下。
.suika-cursor-default {
cursor: url(./cursor-icons/suika-cursor-default.png) 5 5, pointer;
}
值依次为:
- url(<url>):自定义光标的图片资源 url,因为不大且不希望额外作为单独资源加载,通常会选择转换为 base64 格式内嵌;
- x y:使用相对图片左上角的像素位置作为光标位置;
- <keyword>:如果没有指定自定义光标图片,或者加载光标资源失败,就会使用浏览器支持的光标值,比如 pointer。
我们需要绘制好光标图片,然后导出为 png(背景为透明度),然后定义好 x 和 y,再通过 css 类包裹一下,然后根据需要在 Canvas 上设置对应的 css 样式即可。
多种旋转角度的旋转和缩放光标
有两种光标比较特殊,它们有特殊的旋转角度的参数。
它们就是旋转和缩放光标。
因为 cursor 这个 css 属性并不支持设置旋转角度,所以我们只能绘制 0 到 359 之间度数共 360 个不同的旋转光标图片。
缩放光标因为其样式中心对称的原因,倒是不需要这么多,只要绘制 0 到 179 共 180 个图片。
然后是 精细度的问题。
你这里可以整一些猫腻,比如偷懒,抽走一些度数,只给偶数的度数,比如 2、4,奇数的度数都丢掉,没有 1、3 这些度数。设置光标的时候舍入一下,找最接近的度数。
或者你精益求精,你说间隔 1 度未免太大,我们要更精确一点,我们不仅支持整数,我们还要支持 1.5、6.5 这种中间值,我们要用 720 个图片。
没问题,都可以上。
批量生成方案
但是呢,我们发现,这些光标其实都来自一个源图片,只是旋转了不同的角度,我们手工一个个操作未免太低效了。
这时候我们就可以自己写或找一些工具,批量对一张源图形生成旋转多种角度后的图片,然后再写个脚本去自动生成 css 代码,把这些图片引入进去。
这是一个方案,figma 是这么做的。
感觉还是有亿点麻烦。没事,我们有另一个方案。
上面做的是打包前生成大量图片,那我们可不可以在运行时动态生成光标呢?
可以的。图片有位图的,也有矢量的啊,我们可以用一种叫做 SVG 的特殊图片格式,它的内容是文本,一种的 xml 文本。
我们可以将光标 UI 导出为 SVG,然后在最顶层的元素加上 transform 的旋转变换。
可以写一个方法,传入角度和位置信息,动态生成对应的 SVG 字符串,然后转成 DataURL 给 cursor 应用上。
大概像这样:
const getRotationIconDataUrl = (degree, x = 0, y = 0) => {
return `url("data:image/svg+xml,
...
<g fill='none' transform='rotate(${degree} ${x} ${y})'
...
") ${x} ${y}, pointer`
}
canvas.style.cursor = getRotationIconDataUrl(114.544);
开源白板工具 tldraw 选择了这个方案。
你可以给一个精度很高的旋转度数。
模块设计
代码设计上,我们会设计一个 CursorManager 类进行光标的管理。
这个类最重要的作用就是设置光标值。
setCursor 方法接收一个光标值,除了支持传统的字符串,也支持 { type: 'rotation'; degree: 45 } 这种形式。
它主要做了如下操作:
- 标准化光标值,比如把度数取余到 0~360 内;
- 比较新旧光标值,相同就跳过;
- 清空原来设置的光标样式;
- 根据光标不同,执行各自的逻辑。
下面是核心代码实现。
// 支持的光标类型
export type ICursor =
| 'default'
| { type: 'resize'; degree: number }
| { type: 'rotation'; degree: number }
| 'grab'
| ...
}
class CursorManager {
private cursor: ICursor;
setCursor(cursor: ICursor) {
// 1. 标准化光标值,比如把度数取余到 0~360 内
cursor = this.normalizeCursor(cursor);
// 2. 比较新旧光标值,相同就跳过
if (isEqual(cursor, this.cursor)) {
return;
}
this.cursor = cursor;
const clsPrefix = 'suika-cursor-';
// 3. 清空原来设置的光标样式
canvasElement.classList.forEach((className) => {
if (className.startsWith(clsPrefix) {
canvasElement.classList.remove(className);
}
});
this.editor.canvasElement.style.cursor = '';
// 4. 根据光标不同,执行各自的逻辑
if (this.customClassCursor.has(cursor)) {
// 这个是注册了 class 的光标
const className = `${clsPrefix}${cursor}`;
this.editor.canvasElement.classList.add(className);
} else if (typeof cursor == 'string') {
// 用浏览器自带的
this.editor.canvasElement.style.cursor = cursor;
} else if (cursor.type === 'resize') {
// 后面都是使用动态 svg 字符串
this.setResizeCursorInCanvas(cursor.degree);
} else if (cursor.type === 'rotation') {
this.setRotationCursorInCanvas(cursor.degree);
}
}
}
绘制在画布上的光标
光标还有一种比较少用的方案,也说说吧。
就是有些光标是绘制在画布上的。
一个经典的例子就是 AutoCAD 的十字光标,这个十字的长度是可以设置的,可以相当长。
如果你修改操作系统的光标,那这个十字便会突破天际地显示到非绘制区域上。
此外,AutoCAD 的光标并不忠实跟随操作系统光标,比如有时候会吸附于某点不动,并基于它的位置显示下拉菜单,此时可以用真正的光标去点选。
考虑到性能,建议把光标放到另一个 canvas 上,和图形放一个 canvas 会让画布中没做任何操作的图形频繁重绘。
结尾
总结一下。
关于图形编辑器的光标,我们有以下方案:
- 使用浏览器本身就提供的一些光标值。优点是成本低,缺点是样式有限,且不同操作系统风格差异大;
- cursor 支持自定义光标,所以我们可以自己设置自己的一套光标去应用。但其中有一些比较特殊的有各种旋转方向的光标,需要做特别的处理。一种是用工具批量生产光标图片,一种是利用 svg 在运行时动态生成;
- 最后是在画布上渲染光标的方案,适合一些有特殊需求的图形编辑器。这类图形编辑器的光标往往可以自定义,且可以非常大,或是它们在某些场景下会脱离鼠标的控制,喜欢特立独行,比如突然吸附到某个吸附点上。缺点是实现比较复杂,你可能需要像管理图形一样去管理它。
我是前端西瓜哥,欢迎关注我,学习更多图形编辑器知识。
相关推荐
- 3分钟让你的项目支持AI问答模块,完全开源!
-
hello,大家好,我是徐小夕。之前和大家分享了很多可视化,零代码和前端工程化的最佳实践,今天继续分享一下最近开源的Next-Admin的最新更新。最近对这个项目做了一些优化,并集成了大家比较关注...
- 干货|程序员的副业挂,12个平台分享
-
1、D2adminD2Admin是一个完全开源免费的企业中后台产品前端集成方案,使用最新的前端技术栈,小于60kb的本地首屏js加载,已经做好大部分项目前期准备工作,并且带有大量示例代码,助...
- Github标星超200K,这10个可视化面板你知道几个
-
在Github上有很多开源免费的后台控制面板可以选择,但是哪些才是最好、最受欢迎的可视化控制面板呢?今天就和大家推荐Github上10个好看又流行的可视化面板:1.AdminLTEAdminLTE是...
- 开箱即用的炫酷中后台前端开源框架第二篇
-
#头条创作挑战赛#1、SoybeanAdmin(1)介绍:SoybeanAdmin是一个基于Vue3、Vite3、TypeScript、NaiveUI、Pinia和UnoCSS的清新优...
- 搭建React+AntDeign的开发环境和框架
-
搭建React+AntDeign的开发环境和框架随着前端技术的不断发展,React和AntDesign已经成为越来越多Web应用程序的首选开发框架。React是一个用于构建用户界面的JavaScrip...
- 基于.NET 5实现的开源通用权限管理平台
-
??大家好,我是为广大程序员兄弟操碎了心的小编,每天推荐一个小工具/源码,装满你的收藏夹,每天分享一个小技巧,让你轻松节省开发效率,实现不加班不熬夜不掉头发,是我的目标!??今天小编推荐一款基于.NE...
- StreamPark - 大数据流计算引擎
-
使用Docker完成StreamPark的部署??1.基于h2和docker-compose进行StreamPark部署wgethttps://raw.githubusercontent.com/a...
- 教你使用UmiJS框架开发React
-
1、什么是Umi.js?umi,中文可发音为乌米,是一个可插拔的企业级react应用框架。你可以将它简单地理解为一个专注性能的类next.js前端框架,并通过约定、自动生成和解析代码等方式来辅助...
- 简单在线流程图工具在用例设计中的运用
-
敏捷模式下,测试团队的用例逐渐简化以适应快速的发版节奏,大家很早就开始运用思维导图工具比如xmind来编写测试方法、测试点。如今不少已经不少利用开源的思维导图组件(如百度脑图...)来构建测试测试...
- 【开源分享】神奇的大数据实时平台框架,让Flink&Spark开发更简单
-
这是一个神奇的框架,让Flink|Spark开发更简单,一站式大数据实时平台!他就是StreamX!什么是StreamX大数据技术如今发展的如火如荼,已经呈现百花齐放欣欣向荣的景象,实时处理流域...
- 聊聊规则引擎的调研及实现全过程
-
摘要本期主要以规则引擎业务实现为例,陈述在陌生业务前如何进行业务深入、调研、技术选型、设计及实现全过程分析,如果你对规则引擎不感冒、也可以从中了解一些抽象实现过程。诉求从硬件采集到的数据提供的形式多种...
- 【开源推荐】Diboot 2.0.5 发布,自动化开发助理
-
一、前言Diboot2.0.5版本已于近日发布,在此次发布中,我们新增了file-starter组件,完善了iam-starter组件,对core核心进行了相关优化,让devtools也支持对IAM...
- 微软推出Copilot Actions,使用人工智能自动执行重复性任务
-
IT之家11月19日消息,微软在今天举办的Ignite大会上宣布了一系列新功能,旨在进一步提升Microsoft365Copilot的智能化水平。其中最引人注目的是Copilot...
- Electron 使用Selenium和WebDriver
-
本节我们来学习如何在Electron下使用Selenium和WebDriver。SeleniumSelenium是ThoughtWorks提供的一个强大的基于浏览器的开源自动化测试工具...
- Quick 'n Easy Web Builder 11.1.0设计和构建功能齐全的网页的工具
-
一个实用而有效的应用程序,能够让您轻松构建、创建和设计个人的HTML网站。Quick'nEasyWebBuilder是一款全面且轻巧的软件,为用户提供了一种简单的方式来创建、编辑...
- 一周热门
- 最近发表
- 标签列表
-
- kubectlsetimage (56)
- mysqlinsertoverwrite (53)
- addcolumn (54)
- helmpackage (54)
- varchar最长多少 (61)
- 类型断言 (53)
- protoc安装 (56)
- jdk20安装教程 (60)
- rpm2cpio (52)
- 控制台打印 (63)
- 401unauthorized (51)
- vuexstore (68)
- druiddatasource (60)
- 企业微信开发文档 (51)
- rendertexture (51)
- speedphp (52)
- gitcommit-am (68)
- bashecho (64)
- str_to_date函数 (58)
- yum下载包及依赖到本地 (72)
- jstree中文api文档 (59)
- mvnw文件 (58)
- rancher安装 (63)
- nginx开机自启 (53)
- .netcore教程 (53)