当前位置:首页 > 科技  > 软件

图形编辑器开发:实现缩放图形

来源: 责编: 时间:2023-10-20 10:02:58 286观看
导读编辑器 github 地址:https://github.com/F-star/suika线上体验:https://blog.fstars.wang/app/suika/图形的属性图形有几个重要的基础属性,会经常被用到,我们在实现缩放图形前需要理清一下它们。x / ywidth / heightrotat

rBc28资讯网——每日最新资讯28at.com

编辑器 github 地址:rBc28资讯网——每日最新资讯28at.com

https://github.com/F-star/suikarBc28资讯网——每日最新资讯28at.com

线上体验:rBc28资讯网——每日最新资讯28at.com

https://blog.fstars.wang/app/suika/rBc28资讯网——每日最新资讯28at.com

图形的属性

图形有几个重要的基础属性,会经常被用到,我们在实现缩放图形前需要理清一下它们。rBc28资讯网——每日最新资讯28at.com

  • x / y
  • width / height
  • rotation

位置和大小

x 和 y 为图形的左上角位置,注意是旋转前的。rBc28资讯网——每日最新资讯28at.com

x、y 旋转后我们叫做 rotatedX、rotatedY,属性面板中会用到。rBc28资讯网——每日最新资讯28at.com

width 和 height 为图形的宽高,这个没什么好说的。rBc28资讯网——每日最新资讯28at.com

另外,有些图形有些特殊,它的 x、y、width、height 是要通过其他属性计算出来的,比如贝塞尔曲线。rBc28资讯网——每日最新资讯28at.com

旋转

rotation 为图形的旋转度数,通常使用 弧度单位。rBc28资讯网——每日最新资讯28at.com

因为弧度是数学计算中的常客,各种 API 都是要求提供弧度的,比如内置的 Math.sin() 方法。rBc28资讯网——每日最新资讯28at.com

你存角度自然也是可以,但不推荐,但计算时多了一层多余的单位转换,且丢失一些微小的精度。rBc28资讯网——每日最新资讯28at.com

当然 UI 层还是要展示角度,因为是面向用户的,对于数据和 UI 不统一的问题,在 UI 层做一个转换即可。rBc28资讯网——每日最新资讯28at.com

旋转度数通常要配合一个变换中心(origin),这个可以作为一个属性让用户设置。rBc28资讯网——每日最新资讯28at.com

但我更建议将 x、y、width、height 形成的 矩形的中点 作为旋转中心,这样更简单一些,减少用户的心智负担,也防止出现用户设置一些奇怪 origin 的场景。rBc28资讯网——每日最新资讯28at.com

下图中,红色矩形是蓝色矩阵顺时针旋转 45 度得到。rBc28资讯网——每日最新资讯28at.com

rBc28资讯网——每日最新资讯28at.com

旋转度数还要考虑 旋转方向、基准角度、取值范围 问题。rBc28资讯网——每日最新资讯28at.com

(因为弧度不直观,后面会用角度来描述,但数据层依旧还是用的弧度)rBc28资讯网——每日最新资讯28at.com

  • 旋转方向:设置旋转后,图形是会往顺时针方向还是逆时针方向旋转。
  • 基准角度:朝向哪里是 0 度。
  • 取值范围:通常为 [0, 360) 和 (-180, 180]。二者其实等价,只是显示有区别,后者其实只是前者减去 180 度。

通常这些编辑器自己决定就好。像我的项目,向上表示 0 度,顺时针方向为旋转方向,方向取值为 [0, 360)。rBc28资讯网——每日最新资讯28at.com

一些编辑器是支持用户自己设置的,比如 AutoCAD 可通过图形单位命令,设置旋转方向和基准角度。rBc28资讯网——每日最新资讯28at.com

图片rBc28资讯网——每日最新资讯28at.com

缩放实现思路

进入正题,对图形进行缩放。rBc28资讯网——每日最新资讯28at.com

接下来会以通过右下角(也叫东南 se 方向) 缩放控制点缩放为例进行讲解。rBc28资讯网——每日最新资讯28at.com

rBc28资讯网——每日最新资讯28at.com

交互逻辑:rBc28资讯网——每日最新资讯28at.com

选择工具下,当光标落在右下角的缩放控制点上时,光标会变成缩放样式(这个不是本文核心,不讲)。rBc28资讯网——每日最新资讯28at.com

此时按下鼠标,然后进行拖拽,即可对图形以左上角为缩放中心,进行缩放。rBc28资讯网——每日最新资讯28at.com

实现思路:更新 width 和 height,然后确定参照点,修正 x  和 y。rBc28资讯网——每日最新资讯28at.com

按下鼠标时,我们要把当前图形的 x、y、width、height、rotation 记录下来。之后的缩放是基于这个初始状态进行的。rBc28资讯网——每日最新资讯28at.com

const mousedown = (e) => {  // ...    // 缩放前图形的属性,之后我们会直接更新图形属性,导致原来的属性丢失,所以要记录下这个快照。  prevElement = {    x: item.x,    y: item.y,    width: item.width,    height: item.height,    rotation: item.rotation ?? 0,  }}

拖拽时,调用我们将要实现的 movePoint 方法,去更新这个图形。rBc28资讯网——每日最新资讯28at.com

const drag = (e) = {  // ...    selectElement.movePoint(    'se', // 缩放控制点类型:右下(或东南)    lastPoint, // 当前光标位置(基于场景坐标系)    prevElement, // 缩放前的属性快照  );}

下面就是核心方法 movePoint 的实现逻辑了。rBc28资讯网——每日最新资讯28at.com

更新 width 和 height

首先是更新矩形宽高。rBc28资讯网——每日最新资讯28at.com

因为有一个旋转,所以算法不会这么直观。rBc28资讯网——每日最新资讯28at.com

我们要意识到这里有一个变换。看到的图形,是做过变换(基于矩形中心旋转)之后的,但我们需要修改的 width、height、x、y 则是旋转前的。rBc28资讯网——每日最新资讯28at.com

所以我们需要把光标位置给旋转回来,然后再减去 x 和 y 去得到真正的 width 和 height。rBc28资讯网——每日最新资讯28at.com

rBc28资讯网——每日最新资讯28at.com

看看代码rBc28资讯网——每日最新资讯28at.com

class Graph {  // ...  // 根据缩放点更新图形  movePoint(type, newPos, oldBox) {    // 1. 计算 width 和 height    // 计算缩放中心(也就是矩形的中点)    const cx = oldBox.x + oldBox.width / 2;    const cy = oldBox.y + oldBox.height / 2;    // 计算反向旋转的光标位置    const { x: posX, y: poxY } = transformRotate(      newPos.x,      newPos.y,      -(oldBox.rotation || 0), // 注意这里是负数      cx,      cy    );        let width = 0;    let height = 0;    if (type === 'se') {      // 参照点为左上角(x 和 y)      // 新的宽高自然就是光标位置减去 x、y      width = posX - oldBox.x;      height = poxY - oldBox.y;    }    // 其他控制点的逻辑暂且省略...        // 2. 计算 x 和 y    // ...  }}

看看只更新宽高的效果。rBc28资讯网——每日最新资讯28at.com

rBc28资讯网——每日最新资讯28at.com

可以看到是有问题的,因为修改宽高后,矩形的中心点也发生了变化,导致缩放中心错误。所以我们要修正一下 x 和 y。rBc28资讯网——每日最新资讯28at.com

修正 x 和 y

接着我们就要修正 x 和 y 的值。rBc28资讯网——每日最新资讯28at.com

重点就一句话:缩放前的参考点和缩放后的参考点的位置要保持一致。这个参考点其实就是图形缩放过程中的缩放中心。rBc28资讯网——每日最新资讯28at.com

对于右下角缩放控制点,它的缩放中心就是左上角,即 x 和 y 经过旋转的位置。rBc28资讯网——每日最新资讯28at.com

class Graph {  // ...  movePoint(type, newPos, oldBox) {    // 1. 计算 width 和 height    // ...        // 2. 计算 x 和 y    // 设置参照点,不同缩放类型的参照点不同    let prevOriginX = 0;    let prevOriginY = 0;    let originX = 0;    let originY = 0;    if (type === "se") {      prevOriginX = oldBox.x;      prevOriginY = oldBox.y;      originX = oldBox.x;      originY = oldBox.y;    }    // 其他缩放类型暂且省略    // 缩放前的参考点位置    const { x: prevRotatedOriginX, y: prevRotatedOriginY } = transformRotate(      prevOriginX,      prevOriginY,      oldBox.rotation || 0,      cx,      cy    );    // 缩放后的参考点位置    const { x: rotatedOriginX, y: rotatedOriginY } = transformRotate(      originX,      originY,      oldBox.rotation || 0,      oldBox.x + width / 2, // 旋转中心是新的      oldBox.y + height / 2    );    // 计算新旧两个参考点的差值,对 x、y 进行补正    const dx = rotatedOriginX - prevRotatedOriginX;    const dy = rotatedOriginY - prevRotatedOriginY;    const x = oldBox.x - dx;    const y = oldBox.y - dy;  }}

width 和 height 可能为负数,这里要做一个标准化,然后赋值给图形属性即可。rBc28资讯网——每日最新资讯28at.com

this.setAttrs(  normalizeRect({    x,    y,    width,    height,  }),);

其他缩放控制点

对于其他类型缩放控制点,比如左上、右上、左下缩放控制点,它们的大框架是一样的,只是 width 和 height 计算方式不同,以及参考点不同。rBc28资讯网——每日最新资讯28at.com

不同类型下 width 和 height 的设置:rBc28资讯网——每日最新资讯28at.com

let width = 0;let height = 0;if (type === 'se') { // 右下  width = posX - oldBox.x;  height = poxY - oldBox.y;} else if (type === 'ne') { // 右上  width = posX - oldBox.x;  height = oldBox.y + oldBox.height - poxY;} else if (type === 'nw') {  width = oldBox.x + oldBox.width - posX;  height = oldBox.y + oldBox.height - poxY;} else if (type === 'sw') {  width = oldBox.x + oldBox.width - posX;  height = poxY - oldBox.y;}

新旧参考点设置:rBc28资讯网——每日最新资讯28at.com

let prevOriginX = 0;let prevOriginY = 0;let originX = 0;let originY = 0;if (type === 'se') {  prevOriginX = oldBox.x; // 右下缩放点,参考点为左上角  prevOriginY = oldBox.y;  originX = oldBox.x;  originY = oldBox.y;} else if (type === 'ne') { // 右上缩放点,参考点为左下角  prevOriginX = oldBox.x;  prevOriginY = oldBox.y + oldBox.height;  originX = oldBox.x;  originY = oldBox.y + height;} else if (type === 'nw') {  prevOriginX = oldBox.x + oldBox.width;  prevOriginY = oldBox.y + oldBox.height;  originX = oldBox.x + width;  originY = oldBox.y + height;} else if (type === 'sw') {  prevOriginX = oldBox.x + oldBox.width;  prevOriginY = oldBox.y;  originX = oldBox.x + width;  originY = oldBox.y;}

暂时没实现正北、正南、正西、正东的逻辑,逻辑大差不差。rBc28资讯网——每日最新资讯28at.com

锁定缩放比

按住 shift 可以锁定缩放比。rBc28资讯网——每日最新资讯28at.com

做法是对比新旧图形宽高比,将 width 和 height 其中一个进行修正即可。注意正负号。rBc28资讯网——每日最新资讯28at.com

方法需要多传一个 keepRatio 的参数:rBc28资讯网——每日最新资讯28at.com

class Graph {  // ...  movePoint(type, newPos, oldBox, keepRatio = false) {    // 1. 计算 width 和 height    // ...        if (keepRatio) {      const ratio = oldBox.width / oldBox.height;      const newRatio = Math.abs(width / height);      if (newRatio > ratio) {        height = (Math.sign(height) * Math.abs(width)) / ratio;      } else {        width = Math.sign(width) * Math.abs(height) * ratio;      }    }        // 2. 计算 x 和 y    // ...  }}

貌似没考虑除数 height 为 0 的情况..rBc28资讯网——每日最新资讯28at.com

优化点

本文的实现是考虑的是比较简单的缩放图形场景,一些更复杂的场景并未实现。rBc28资讯网——每日最新资讯28at.com

缩放还有另一种策略,就是会产生 反向颠倒 的缩放。要实现这个效果,需要引入缩放属性,复杂度会提升很多。rBc28资讯网——每日最新资讯28at.com

另外就是选中多个图形,然后缩放的场景我没实现。这种场景下,通常是要锁定宽高比的。rBc28资讯网——每日最新资讯28at.com

否则就会出现图形的斜切效果,这个如果要实现,我们还要引入斜切属性,复杂度再一次提升。rBc28资讯网——每日最新资讯28at.com

下面是 Figma 的效果,真是让人头扁。rBc28资讯网——每日最新资讯28at.com

rBc28资讯网——每日最新资讯28at.com

按住 Alt 实现图形中心缩放也没做,这个比较简单,有空再做。rBc28资讯网——每日最新资讯28at.com

读者如果看懂我这篇文章,心里应该有思路的:width、height 的计算要加入图形中点参数,参照点设置为图形中点。rBc28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-14348-0.html图形编辑器开发:实现缩放图形

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 深入理解 Netty FastThreadLocal

下一篇: 国际权威大奖GCAs揭晓 秦淮数据包揽数据中心类两项大奖

标签:
  • 热门焦点
  • MIX Fold3包装盒泄露 新机本月登场

    MIX Fold3包装盒泄露 新机本月登场

    小米的全新折叠屏旗舰MIX Fold3将于本月发布,近日该机的真机包装盒在网上泄露。从图上来看,新的MIX Fold3包装盒在外观设计方面延续了之前的方案,变化不大,这也是目前小米旗舰
  • 小米官宣:2023年上半年出货量中国第一!

    小米官宣:2023年上半年出货量中国第一!

    今日早间,小米电视官方微博带来消息,称2023年小米电视上半年出货量达到了中国第一,同时还表示小米电视的巨屏风暴即将开始。“公布一个好消息2023年#小米电视上半年出货量中国
  • 对标苹果的灵动岛 华为带来实况窗功能

    对标苹果的灵动岛 华为带来实况窗功能

    继苹果的灵动岛之后,华为也在今天正式推出了“实况窗”功能。据今天鸿蒙OS 4.0的现场演示显示,华为的实况窗可以更高效的展现出实时通知,比如锁屏上就能看到外卖、打车、银行
  • vivo TWS Air开箱体验:真轻 臻好听

    vivo TWS Air开箱体验:真轻 臻好听

    在vivo S15系列新机的发布会上,vivo的最新款真无线蓝牙耳机vivo TWS Air也一同发布,本次就这款耳机新品给大家带来一个简单的分享。外包装盒上,vivo TWS Air保持了vivo自家产
  • 雅柏威士忌多款单品价格大跌,泥煤顶流也不香了?

    雅柏威士忌多款单品价格大跌,泥煤顶流也不香了?

    来源 | 烈酒商业观察编 | 肖海林今年以来,威士忌市场开始出现了降温迹象,越来越多不断暴涨的网红威士忌也开始悄然回归市场理性。近日,LVMH集团旗下苏格兰威士忌品牌雅柏(Ardbeg
  • 品牌洞察丨服务本地,美团直播成效几何?

    品牌洞察丨服务本地,美团直播成效几何?

    来源:17PR7月11日,美团App首页推荐位出现“美团直播”的固定入口。在直播聚合页面,外卖“神枪手”直播间、美团旅行直播间、美团买菜直播间等均已上线,同时
  •  首发天玑9200+ iQOO Neo8系列发布首销售价2299元起

    首发天玑9200+ iQOO Neo8系列发布首销售价2299元起

    2023年5月23日晚,iQOO Neo8系列正式发布。其中,Neo系列首款Pro之作——iQOO Neo8 Pro强悍登场,限时售价3099元起;价位段最强性能手机iQOO Neo8同期上市
  • iQOO Neo8 Pro抢先上架:首发天玑9200+ 安卓性能之王

    iQOO Neo8 Pro抢先上架:首发天玑9200+ 安卓性能之王

    经过了一段时间的密集爆料,昨日iQOO官方如期对外宣布:将于5月23日推出全新的iQOO Neo8系列新品,官方称这是一款拥有旗舰级性能调校的作品。随着发布时
  • 联想YOGA 16s 2022笔记本将要推出,屏幕支持触控功能

    联想YOGA 16s 2022笔记本将要推出,屏幕支持触控功能

    联想此前宣布,将于11月2日19:30召开联想秋季轻薄新品发布会,推出联想 YOGA 16s 2022 笔记本等新品。官方称,YOGA 16s 2022 笔记本将搭载 16 英寸屏幕,并且是一
Top