图形编辑器:标尺功能的实现

news2024/12/24 20:49:20

大家好,我是前端西瓜哥。今天我们来实现图形编辑器的标尺功能。

项目地址:

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

线上体验:

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

标尺指的是画布上边和左边的两个有刻度的尺子,作用让用户知道他正在编辑的视口所在位置范围。

我们的需求是:间隔特定的长度,绘制一个刻度,并显示这个刻度在 X 轴或 Y 轴上的位置。

先看最终实现效果:

在这里插入图片描述

可以看到,视口移动后,标尺上的刻度能正确地改变。此外缩放画布,标尺的步长会发生改变,保持一个比较适合的密度。

实现思路

总体实现思路:

  1. 确定刻度尺的步长(step)。步长是和画布缩放比(zoom)相关的,zoom 越大,step 就越小;
  2. 计算出需要绘制的所有刻度。分别为从视口从左侧到右侧,从上边到下边的范围;
  3. 绘制。绘制上也是有考量的,先绘制背景,然后绘制刻度,最后绘制分界线。

步长选择

步长会根据 zoom 进行设置,目的是让视口中的标尺能绘制适宜密度的刻度。

假设我们的步长固定为 50,不跟随 zoom 改变,在 100% 看起来效果不错:

但当你缩小时,会变成下面这样:

密度过大,导致数字重叠。同样,放大时则过于稀疏,刻度很难才见到一个,没能发挥标尺的效用。

步长怎么计算呢?

理论上步长可以是 50,那么 51 好像也行,3 也行。但更建议使用 5 的倍数、2 的倍数、25 的倍数这些作为步长。

因为没有什么理论参考,所以我还是选择参考市面上的设计工具的步长变化设计。

比如 figma,zoom 落在 [100%, 200%) 的步长为 50,[200%, 500%) 则是 10 等等。

我的实现为:

const getStepByZoom = (zoom: number) => {
  // 可用的步长列表
  const steps = [1, 2, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000];
  // 看着 figma 的 step 变化想出的一个奇怪的规律
  // 然后找出可选步长列表最近的并大于它的 step 作为最终步长
  const step = 50 / zoom;
  for (let i = 0, len = steps.length; i < len; i++) {
    if (steps[i] >= step) return steps[i];
  }
  return steps[0];
};

const step = getStepByZoom(zoom);

计算范围

这里我讲解水平(x 轴)方向的情况。垂直方向同理,就不赘叙了。

首先计算出视口最左侧和最右侧的 x 坐标值。

需要视口坐标转场景坐标的知识,如果你不懂,看我这篇文章:

《图形编辑器:场景坐标、视口坐标以及它们之间的转换》

let startXInScene = viewport.x + startXInViewport / zoom; // 视口坐标转场景
let endXInScene = viewport.width + startYInViewport / zoom; // 视口坐标转场景

然后找离它们最近的落在刻度上的值。

对此,我实现了一个 getClosestVal 方法。

/**
 * 找出离 value 最近的 segment 的倍数值
 */
const getClosestVal = (value: number, segment: number) => {
  const n = Math.floor(value / segment);
  const left = segment * n;
  const right = segment * (n + 1);
  return value - left <= right - value ? left : right;
};

startXInScene = getClosestVal(startXInScene, step);
endXInScene = getClosestVal(endXInScene, step);

得到起点和终点,我们可以开始循环了,从 startXInScene 开始,每次循环加一个 step,直至达到末尾为止。

ctx.textAlign = 'center'; // 文字水平居中对齐

while (startXInScene <= endXInScene) {
  ctx.strokeStyle = setting.rulerMarkStroke;
  ctx.fillStyle = setting.rulerMarkStroke;
  // 场景转回视口再绘制。刻度线不能直接在场景中绘制,因为缩放变换会导致线的粗细变化
  const x = (startXInScene - viewport.x) * zoom;
  // 绘制刻度
  ctx.beginPath();
  ctx.moveTo(x, y);
  ctx.lineTo(x, y + setting.rulerMarkSize);
  ctx.stroke();
  ctx.closePath();
  // 刻度值则用场景坐标的值
  ctx.fillText(String(startXInScene), x, y - 4);
  // +step,指针移动
  startXInScene += step;
}

垂直方向的标尺同理,只是稍微特殊的是刻度值文字需要多做一个 -90 度的旋转。

export const rotateInCanvas = (
  ctx: CanvasRenderingContext2D,
  angle: number,
  cx: number,
  cy: number
) => {
  ctx.translate(cx, cy);
  ctx.rotate(angle);
  ctx.translate(-cx, -cy);
};

rotateInCanvas(ctx, -HALF_PI, x, y);

绘制顺序

绘制顺序需要注意一下,先后顺序为:

  1. 绘制两个标尺的背景色;
  2. 绘制刻度值;
  3. 用一个和背景色同色的矩形盖掉左上角那个方形,那个地方不能有刻度值,不如两个标尺的刻度会重叠。你也可以在绘制刻度值时,用裁切(ctx.clip)不让绘制到那个方形区域上;
  4. 绘制两条分割线;

最后

标尺实现大致如此,并不复杂。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/164292.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

java 探花交友day2 项目简介,环境搭建 登录验证码

技术方案&#xff1a; 项目结构&#xff1a; 项目概述 通过接口文档&#xff08;API文档&#xff09;定义规范 开发工具安装与配置 Linux虚拟机 YAPI 账号 tanhuaitcast.cn 密码123456 安装个安卓模拟器&#xff0c;然后安装APK 开发环境说明 初始工程搭建 阿里云短…

Leetcode:235. 二叉搜索树的最近公共祖先(C++)

目录 问题描述&#xff1a; 实现代码与解析&#xff1a; 递归&#xff1a; 原理思路&#xff1a; 精简版&#xff1a; 迭代&#xff1a; 原理思路&#xff1a; 问题描述&#xff1a; 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先…

1589_AURIX_TC275_PMU_Flash的基本特性以及操作

全部学习汇总&#xff1a; GreyZhang/g_TC275: happy hacking for TC275! (github.com) 关于这部分&#xff0c;感觉能够看到的比较有实践指导价值的信息不多。这里关于是否支持cache的信息&#xff0c;之前在内核手册等地方其实也看过了。 DFlash不支持buffer命中的功能&#…

21.Isaac教程--GEMS 导航堆栈简介

Isaac GEMS 导航堆栈简介 ISAAC教程合集地址: https://blog.csdn.net/kunhe0512/category_12163211.html 导航堆栈必须执行以下高级功能&#xff1a; Mapping 映射用于自动创建操作环境的地图。 该地图既用于定位&#xff0c;又用于路径规划。 它可以由具有附加功能的人进行注…

deap遗传算法 tirads代码解读

deap遗传算法 tirads代码解读写在最前面Overview 程序概览参考deap框架介绍creator模块创建适应度类Types定义适应度策略创建个体类Toolbox类创建种群&#xff08;个体、策略以及粒子&#xff09;Initialization1. 创建 attr_int 运算符2. 创建 individual_guess() 运算3.创建新…

学会python后:收集每天热点内容信息,并发送到自己的邮箱

嗨害大家好鸭&#xff01;我是小熊猫~ 实现目的 本篇文章内容主要为如何用代码&#xff0c;把你想要的内容&#xff0c;以邮件的形式发送出去 内容可以自己完善&#xff0c;还可以设置一个定时发送&#xff0c;或者开机启动自动运行代码 代理注册与使用 注册账号并登录 生成ap…

使用TDengine时序数据库的介绍以及系统整合

目录 一、 如何使用 安装目录介绍 数据文件查看和备份 客户端连接 sql使用 二、 系统整合 Java连接配置 Demo示例 三、 对采集点、超级表、子表和设备等关系进行维护 一、 如何使用 安装目录介绍 目录/文件 说明 /usr/local/taos/bin TDengine 可执行文件目录…

css笔记2

目录 选择器进阶 1、复合选择器 1.1后代选择器&#xff1a;空格 1.2 子代选择器&#xff1a; > 2、并集选择器&#xff1a;&#xff0c; 3、交集选择器 4、hover伪类选择器 Emmet语法 背景相关属性 1.1背景颜色 2.1背景图片 3.1背景平铺 4.1背景位置 5.1背景相关属…

linux中断机制

目录 1.中断机制 1.1.中断流程图 1.2.代码结构图 2.中断代码 2.1.硬件中断 2.2.asm.s 2.3.trap.c 2.3.1.trap_init函数 2.3.2.die函数 2.4 .sys_call.s 2.4.1._system_call.s 3.总结 1.中断机制 何为中断&#xff0c;中断里面各种名词的区分&#xff0c;请看下面这几篇…

安装VSCode图文版(附安装所需插件)

安装VSCode安装地址下载安装安装成功安装所需插件安装go插件安装中文简体安装地址 VSCode 安装地址 https://code.visualstudio.com/ 下载 在下面两个地方都可以下载&#xff0c;左侧下载可以根据自己的需要进行版本或者系统的选择下载。 安装 同意协议 选择附加项 为什…

基于python知识图谱医疗领域问答系统实现(完整代码+数据可直接运行)

直接上结果展示: “让人类永远保持理智,确实是一种奢求” ,机器人莫斯,《流浪地球》 项目概况 本项目为一个使用深度学习方法解析问题,知识图谱存储、查询知识点,基于医疗垂直领域的对话系统的后台程序 运行效果:

【阶段四】Python深度学习04篇:深度学习项目实战:深度神经网络预测客户流失率(分类模型)

本篇的思维导图: 深度神经网络预测客户流失率(分类模型) 项目背景 应用Keras框架构建单隐层网络和深度神经网络进行金融客户流失率的预测,以及模型的优化。主要用来熟悉Keras全连接层网络的使用。 数据获取 本次建模数据来源于网络,数据项统计如下: 编号

uni-app Vue3实现一个酷炫的多功能音乐播放器支持微信小程序后台播放

前言 本文存在多张gif演示图&#xff0c;建议在wifi环境下阅读&#x1f4d6; 最近在做网易云音乐微信小程序开源项目的时候&#xff0c;关于播放器功能参考了一些成熟的微信小程序&#xff0c;如网易云音乐小程序和QQ音乐小程序&#xff0c;但是发现这些小程序端的播放器相对于…

【寒假每日一题】洛谷 P7471 [NOI Online 2021 入门组] 切蛋糕

题目链接&#xff1a;P7471 [NOI Online 2021 入门组] 切蛋糕 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题目描述 Alice、Bob 和 Cindy 三个好朋友得到了一个圆形蛋糕&#xff0c;他们打算分享这个蛋糕。 三个人的需求量分别为 a,b,c&#xff0c;现在请你帮他们切蛋糕…

Linux文件的默认权限、软硬链接和属性

✅作者简介&#xff1a;热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏&#xff1a;Java案例分…

Java-FileInputStream和FileOutputStream的使用,txt文件及图片文件的拷贝

Java-FileInputStream和FileOutputStream的使用什么是IO流&#xff1f;流是什么&#xff1f;IO流的类图流的分类字符与字节的区别FileInputStream的使用1.构造器2.常用方法3.使用FileInputStream来读取txt文件FileOutputStream的使用1.构造器2.常用方法3.使用FileOutputStream写…

(11)go-micro微服务雪花算法

文章目录一 雪花算法介绍二 雪花算法优缺点三 雪花算法实现四 最后一 雪花算法介绍 雪花算法是推特开源的分布式ID生成算法&#xff0c;用于在不同的机器上生成唯一的ID的算法。 该算法生成一个64bit的数字作为分布式ID&#xff0c;保证这个ID自增并且全局唯一。 1.第一位占用…

【嘉立创EDA】构建自己的元件库,绘制符号、封装的方法

器件问题 先选择需要的元器件&#xff0c;然后查看其数据手册&#xff0c;找到官方提供的元件封装进行绘制。 器件 选择一款卧贴式双排排针进行绘制。 器件模型 主要用到的就是 Recommended P.C.B Layout 前期资料准备完毕&#xff0c;下面开始绘制自己的元件库。 元件库制作…

微服务多模块feign更新数据问题

文章目录问题测试1.bill模块抛异常&#xff0c;data模块正常2.bill模块抛异常&#xff0c;data模块正常解决方案1.分布式事务2.复制data的dao mapper到bill中3.判断feign返回值&#xff0c;抛异常做回滚最近在做一个财务系统&#xff0c;用到了两个模块bill账单模块和data数据模…

C语言文件补充笔记1:EOF与feof

1 关于EOF 可以查看EOF的宏定义 函数fgetc如果读取失败就返回-1&#xff0c;对于文本文件而言&#xff0c;以为着读取结束&#xff0c;因此-1可以作为结束的标志。 #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> int main() {FILE* fp fopen("a.txt"…