图形编辑器:修改图形 x、y、width、height、rotation

news2025/1/13 9:45:01

大家好,我是前端西瓜哥。图形编辑器的一个需求,就是可以通过属性面板的输入框设置选中元素的属性值。

项目地址,欢迎 star:

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

线上体验:

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

最终效果如下:
在这里插入图片描述

元素对象的结构:

interface IGraph {
  x: number;
  y: number;
  width: number;
  height: number;
  rotation: number; // 旋转角度,单位为弧度
}

设置 x / y

UI 界面显示上说的 x / y,指的是旋转后的 x(即 rotatedX / rotatedY)。

为什么不是对应真正的 x 和 y 呢?因为需要对应用户的视角。

开发者理解底层,理解一个图形是先有基本的物理信息(x、y、width、height),然后再做变换(旋转、缩放等)后得到新的坐标再进行绘制。

而用户看到的则是直观的绘制出来的图形,并希望图形的左上角坐标能够对上他设置的坐标。旋转前的 x 和 y 是无法直观体现在画布上的,用户也不会在意。

OK,先看看怎么修改 rotatedX。图形对象上没有 rotatedX 属性,本质还是要修改 x 值。

先看看 rotatedX 和 rotatedY 是怎么计算出来的,其实就是计算 x 和 y 基于图形的中点旋转后的结果:

// 对坐标做旋转
function transformRotate(x, y, radian, cx ,cy) {
  if (!radian) {
    return [x, y];
  }
  const cos = Math.cos(radian);
  const sin = Math.sin(radian);
  return [
    (x - cx) * cos - (y - cy) * sin + cx,
    (x - cx) * sin + (y - cy) * cos + cy,
  ];
}

// 计算旋转后的 x 和 y
const [rotatedX, rotatedY] = transformRotate(x, y, rotation, cx, cy);

计算一个元素 rotatedX / rotatedY 的方法实现:

// 计算中点
function getRectCenterPoint({x, y, width, height}) {
  return [x + width / 2, y + height / 2];
}

// 计算 rotatedX / rotatedY
export function getElementRotatedXY(element) {
  const [cx, cy] = getRectCenterPoint(element);
  return transformRotate(element.x, element.y, element.rotation || 0, cx, cy);
}

所以,设置新的 rotatedX,其实就是加上一个移动前后 rotatedX 的偏移值,将其加到 x 上就行了。

class Graph {
	// ...
  setRotatedX(rotatedX) {
    const [prevRotatedX] = getElementRotatedXY(this);
    const dx = rotatedX - prevRotatedX;
    this.x += dx;
  }
}

rotatedY 同理:

class Graph {
	// ...
  setRotatedY(rotatedY: number) {
    const [, prevRotatedY] = getElementRotatedXY(this);
    const dy = rotatedY - prevRotatedY;
    this.y += dy;
  }
}

设置 width / height

首先修改width 和 height。

但是这样会导致 rotatedX 和 rotatedY 发生偏移,我们需要修正一下。

修正方式有两种思路:

思路 1:计算修改 width 前后的 rotatedX / rotatedY 之间的差值,给元素进行修正。

const [preRotatedX, preRotatedY] = getElementRotatedXY(el); // 修改 width 前的
el.width = width;
const [rotatedX, rotatedY] = getElementRotatedXY(el); // 修改 width 后的
const dx = rotatedX - preRotatedX;
const dy = rotatedY - preRotatedY;
el.x -= dx; // "-" 是因为要复原状态
el.y -= dy;

思路 2:确定后最终的 rotatedX / rotatedY,然后对之前的 transformRotate 方法中的等式,进行逆推导,通过 rotatedX、rotatedY、radian、width、height 计算出对应的 x 和 y。这个思路比上一个思路有点复杂。

const [rotatedX, rotatedY] = getElementRotatedXY(el);
el.width = width;
const [x, y] = getOriginXY(
  rotatedX,
  rotatedY,
  el.rotation || 0,
  width,
  el.height
);
el.x = x;
el.y = y;

/**
 * 计算旋转前的 x、y
 * transformRotate 的反推
 */
function getOriginXY(rotatedX, rotatedY, radian, width, height) {
  if (!radian) {
    return [rotatedX, rotatedY];
  }
  const cos = Math.cos(radian);
  const sin = Math.sin(radian);
  const halfWidth = width / 2;
  const halfHeight = height / 2;
  return [
    rotatedX - halfWidth - halfHeight * sin + halfWidth * cos,
    rotatedY - halfHeight + halfHeight * cos + halfWidth * sin,
  ];
}

我一开始用的思路 2 实现的,后面写这篇文章梳理时,相处了思路 1 的解法,因为更简单更好理解,就换成思路 1 的实现了。

修改 rotation

修改 rotation 就很简单了,直接改就好了。

但需要注意将度数转成弧度,以及通过取余来限定弧度范围。

// 角度转弧度
function degree2Radian(degree: number) {
  return (degree * Math.PI) / 180;
}

/**
 * 标准化角度
 */
const PI_DOUBLE = 2 * Math.PI;
export const normalizeAngle = (angle) => {
  return angle % PI_DOUBLE;
};


element.rotation = normalizeAngle(degree2Radian(rotation));

结尾

算法实现上并不复杂。

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

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

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

相关文章

Express框架连接MongoDB数据库操作

在上一篇中已经在Node.js中引入使用了mongoose进行MongoDB数据库的基本操作,在本篇当中在Express框架中来连接数据库以及操作数据库; Express 项目环境 这里是通过全局安装Express框架生产的项目环境,也可以通过局部安装的方式,安…

【MySQL】第十一部分 SELECT的执行过程

【MySQL】第十一部分 SELECT的执行过程 文章目录【MySQL】第十一部分 SELECT的执行过程11. SELECT的执行过程11.1 SQL92语法的结构11.2 SQL92语法的结构11.3 语句执行顺序11.4 解释之前遗留问题总结11. SELECT的执行过程 11.1 SQL92语法的结构 SELECT ... FROM TABLE WHERE 多…

STM32G431—ADC+E2PROM读写实验

目录 1.ADC介绍 配置cubemx 代码 2.IIC 通信 原理图 AT24C02 代码 i2C.c 主函数 1.ADC介绍 ADC 有多达 18 个转换通道,其中通道 0~通道 15 是外部通道,使用的 GPIO 引脚如表 所示。 ADC引脚GPIO引脚GPIO配置ADC引脚GPIO引脚GPIO配置IN0PA0模拟输…

Aspose.PDF for .NET提取矢量图像的支持

Aspose.PDF for .NET提取矢量图像的支持 增加了对提取矢量图像的支持。 改进了PDF到Microsoft Excel的转换。 添加了对创建“PrinterMark”批注的支持。 增加了检测PDF文件是否包含矢量图形的功能。 Aspose.PDF for.NET是一个高级PDF处理和解析API,用于在跨平台应用…

Linux常用命令——setsid命令

在线Linux命令查询工具(http://www.lzltool.com/LinuxCommand) setsid 在新的会话中运行程序。 补充说明 setsid命令子进程从父进程继承了:SessionID、进程组ID和打开的终端。子进程如果要脱离这些,代码中可通过调用setsid来实现。,而命令…

C#中的基本概念(类_对象_属性_方法)

C#中的基本概念(类_对象_属性_方法) 对象的概念: 任何存在的实体都是对象,对象是真实存在的, 它包括“静态特征”和“动态特征” 对象包含2个方面: 静态特征:表示对象的属性,用来存储对象本身的数据 动态特征(行为):表示对象的方法,用来说明对象能做什么 例如: 一…

云服务器部署Node.js前后端分离项目

云服务器部署Node.js 前后端分离项目 准备工作 1.拥有一台服务器 自行选择阿里云,腾讯云… 2.重装系统 以腾讯云为例,安装centos 7.6系统 重置一下密码 3.使用远程管理工具 以宝塔工具为例,添加连接 4.安装宝塔面板 宝塔面板 - 简单好用的Linux…

基于ImageAI的图像识别

博主简介 博主是一名大二学生,主攻人工智能研究。感谢让我们在CSDN相遇,博主致力于在这里分享关于人工智能,c,Python,爬虫等方面知识的分享。 如果有需要的小伙伴可以关注博主,博主会继续更新的&#xff0c…

每日学术速递1.25

CV - 计算机视觉 今天带来的是北航IRIP实验室被国际计算机视觉与模式识别会议CVPR 2022接收的6篇论文。 CVPR是由IEEE主办的计算机视觉、模式识别及人工智能等领域最具影响力和最重要的国际顶级会议。CVPR官网显示,此次会议有超过8161篇的大会论文投稿,…

测试篇(三):测试用例的万能公式、对水杯和登录页面设计测试用例、测试用例的设计方法

目录一、测试用例的万能公式二、对登录页面设计测试用例三、测试用例的设计方法3.1 基于需求的设计方法3.2 等价类3.3 边界值3.4 判定表3.5 正交排列3.6 场景设计法3.7 错误猜测法四、面试真题一、测试用例的万能公式 首先,为"水杯"设计一个测试用例&…

计算机网络基础(二)

文章目录1. 应用层1.1 定制应用层协议1.2 网络版计算器实现1.3 应用层细分1.4 HTTP协议1.4.1 认识网址(URL)1.4.2 HTTP协议简易了解1.4.3 简易版HTTP服务器1.4.4 HTTP请求报文详解1.4.5 响应报文1.4.6 HTTP的一些安全问题1.5 HTTPS协议1.5.1 HTTPS和SSL/TLS1.5.2 短链接和长链接…

【Java|golang】1802. 有界数组中指定下标处的最大值---双指针

给你三个正整数 n、index 和 maxSum 。你需要构造一个同时满足下述所有条件的数组 nums&#xff08;下标 从 0 开始 计数&#xff09;&#xff1a; nums.length n nums[i] 是 正整数 &#xff0c;其中 0 < i < n abs(nums[i] - nums[i1]) < 1 &#xff0c;其中 0 &l…

2023年最新Python常见编程面试题(1)精选30个题目附答案

2023年最新Python面试题&#xff08;1&#xff09;之基础篇精选1.统计字符串中字符出现的次数2.获取字典key值键中小写字母对应键值对3.将字典中key变小写4.更改字典value值5.将cookies变成字典形式6.有如下字符串username把它转换成为UserName7.分别求出奇数和偶数把奇数偶数存…

NLP | 打造一个‘OpenAI智能’机器人,只需要五分钟

借用openai api,做一个聊天机器人&#xff0c;别去openai主页啦~Step 1.登录OpenAI账号网站&#xff1a;Overview - OpenAI APIStep 2.生成API key点击右上角的个人个人账户-> 点击 View API keys点击生成一个秘钥&#xff08;Create new secret key&#xff09;,并且复制St…

GameFrameWork框架(Unity3D)使用笔记(九) AssetBundle和游戏打包

目录 前言&#xff1a; 整个流程&#xff1a; 一、配置路径 二、打包 三、初始化资源 四、测试打包 前言&#xff1a; 如果使用了GameFrameWork框架的话&#xff0c;你会发现你点击Build And Run按钮打包运行大概是运行不起来的。本篇就讲了怎么打包游戏运行。 我觉得我对…

什么是YOLOR?

简述YOLOR 是一种用于对象检测的最先进的机器学习算法&#xff0c;与 YOLOv1-YOLOv5 不同&#xff0c;原因在于作者身份、架构和模型基础设施的差异。YOLOR 代表“你只学习一种表示”&#xff0c;不要与 YOLO 版本 1 到 4 混淆&#xff0c;其中 YOLO 代表“你只看一次”。 YOLO…

Docker的常用命令

文章目录 目录 文章目录 前言 一、帮助命令 二、镜像命令 1.查看镜像 2.搜索镜像 3.下载镜像 4.删除镜像 三、容器命令 1.启动容器 2.查看容器 3.退出容器 4.删除容器 5.启动和停止容器 四、常用的其它命令 后台运行 查看日志 查看容器中进程的信息 查看镜像的元数据…

lego-loam学习笔记(一)

前言&#xff1a; 主要记录配置编译lego-loam源码时遇到的问题和解决的方法。 系统&#xff1a;ubuntu18.04 一、安装gtsam 因为系统是18.04所以不需要作很大的更改&#xff0c;按照官网的doc一步一步的来就行了。 根据官网&#xff1a; GitHub - RobustFieldAutonomyLab…

DlhSoft Gantt Chart Light Library自定义的网格列

DlhSoft Gantt Chart Light Library自定义的网格列 Improved the loading of Microsoft Project XML files and the exporting of images from ScheduleChartDataGrid.Added new TotalResourceEffort and TotalResourceCompletedEffort properties to compute the real effort …

MySQL数据库数据动态监控(canal+Kafka)

参考资料: 参考文章https://gper.club/articles/7e7e7f7ff3g59gc6g6d canal官网 https://github.com/alibaba/canal