Loading [MathJax]/jax/input/TeX/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >学到了!Figma 原来是这样表示矩形的

学到了!Figma 原来是这样表示矩形的

作者头像
前端西瓜哥
发布于 2023-12-20 06:50:53
发布于 2023-12-20 06:50:53
26400
代码可运行
举报
运行总次数:0
代码可运行

大家好,我是前端西瓜哥。

今天我们来研究一下 Figma 是如何表示图形的,这里以矩形为切入点进行研究。

明白最简单的矩形的表示后,研究其他的图形就可以举一反三。

矩形的一般表达

如果让我设计一个矩形图形的物理属性,我会怎么设计?

我张口就来:x、y、width、height、rotation。

对一些简单的图形编辑操作,这些属性基本上是够用的,比如白板工具,如果你不考虑或者不希望图形可以翻转(flip) 的话。

Figma 需要考虑翻转的情况的,此外还有斜切的情况。

翻转的场景:

还有斜切的场景,在选中多个图形然后缩放时有发生。

这些表达光靠上面的几个属性是不够的,我们看看 Figma为了表达这些效果,是怎么去设计矩形的。

Figma 矩形物理属性

上篇文章我们用 Figma-To-JSON 成功解析了 fig 文件,借助这个工具,我们得到了矩形图形的属性。

与物理信息相关的属性如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{
  "size": {
    "x": 100,
    "y": 100
  },
  "transform": {
    "m00": 1,
    "m01": 3,
    "m02": 5,
    "m10": 2,
    "m11": 4,
    "m12": 6
  },
  // 省略其他无关属性
}

没有位置属性,这个属性默认是 (0, 0),实际它转移到 transform 的矩阵的位移子矩阵上了。

size 表示宽高,但属性名用的是 x(宽) 和 y(高),理论上 width 和 height 语义更好,这样应该是用了矢量类型。

size 表示宽高,理论上 width 和 height 语义更好,这样应该是用了平面矢量类型的结构体,所以是 x 和 y。

transform 表示一个 3x3 的变换矩阵。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
m00 | m01 | m02
m10 | m11 | m12
 0  |  0  |  1

上面的 transform 属性的值所对应的矩阵为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
1 | 3 | 5
2 | 4 | 6
0 | 0 | 1

属性面板

再看看这些属性对应的右侧属性面板。

x、y 分别是 5 和 6,它是 (0, 0) 进行 transform 后的结果,这个直接对应 transform.m02tansfrom.m12

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import { Matrix } from "pixi.js";

const matrix = new Matrix(1, 2, 3, 4, 5, 6);
const topLeft = matrix.apply({ x: 0, y: 0 }); // { x: 5, y: 6 }

// 或直接点
const topLeft = { x: 5, y: 6 }

这里引入了 pixi.js 的 matrix 类,该类使用列向量方式进行表达。 文末有 demo 源码以及线上 demo,可打开控制台查看结果验证正确性。

然后这里的 width 和 height,是 223.61 和 500, 怎么来的?

它们对应的是矩形的两条边变形后的长度,如下:

uiWidth 为 (0, 0)(width, 0) 进行矩阵变换后坐标点之间的距离。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const distance = (p1, p2) => {
  const a = p1.x - p2.x;
  const b = p1.y - p2.y;
  return Math.sqrt(a * a + b * b);
};

const matrix = new Matrix(1, 2, 3, 4, 5, 6);
const topLeft = { x: 5, y: 6 }

const topRight = matrix.apply({ x: 100, y: 0 });
distance(topRight, topLeft); // 223.60679774997897

最后计算出 223.60679774997897,四舍五入得到 223.61。

高度计算同理。

uiHeight 为 (0, 0)(0, height) 进行矩阵变换后坐标点之间的距离。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const matrix = new Matrix(1, 2, 3, 4, 5, 6);
const topLeft = { x: 5, y: 6 }

const bottomLeft = matrix.apply({ x: 0, y: 100 });
distance(bottomLeft, topLeft); // 500

旋转角度

最后是旋转角度,它是宽度对应的矩形边向量,逆时针旋转 90 度的向量所对应的角度。

先计算宽边向量,然后逆时针旋转 90 度得到旋转向量,最后计算旋转向量对应的角度。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const wSideVec = { x: topRight.x - topLeft.x, y: topRight.y - topLeft.y };
// 逆时针旋转 90 度,得到旋转向量
const rotationMatrix = new Matrix(0, -1, 1, 0, 0, 0);
const rotationVec = rotationMatrix.apply(wSideVec);
const rad = calcVectorRadian(rotationVec);
const deg = rad2Deg(rad); // 

这里用了几个工具函数。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 计算和 (0, -1) 的夹角
const calcVectorRadian = (vec) => {
  const a = [vec.x, vec.y];
  const b = [0, -1]; // 这个是基准角度

  // 使用点积公式计算夹脚
  const dotProduct = a[0] * b[0] + a[1] * b[1];
  const d =
    Math.sqrt(a[0] * a[0] + a[1] * a[1]) * Math.sqrt(b[0] * b[0] + b[1] * b[1]);
  let rad = Math.acos(dotProduct / d);

  if (vec.x > 0) {
    // 如果 x > 0, 则 rad 转为 (-PI, 0) 之间的值
    rad = -rad;
  }
  return rad;
}

// 弧度转角度
const rad2Deg = (rad) => (rad * 180) / Math.PI;

Figma 的角度表示比较别扭。

特征为:基准角度朝上,对应向量为 (0, -1),角度方向为逆时针,角度范围限定为 (-180, 180],计算向量角度时要注意这个特征进行调整。

完整代码实现

线上 demo:

https://codepen.io/F-star/pen/WNPVWwQ?editors=0012

代码实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import { Matrix } from "pixi.js";

// 计算和 (0, -1) 的夹角
const calcVectorRadian = (vec) => {
  const a = [vec.x, vec.y];
  const b = [0, -1];

  const dotProduct = a[0] * b[0] + a[1] * b[1];
  const d =
    Math.sqrt(a[0] * a[0] + a[1] * a[1]) * Math.sqrt(b[0] * b[0] + b[1] * b[1]);
  let rad = Math.acos(dotProduct / d);

  if (vec.x > 0) {
    // 如果 x > 0, 则 rad 为 (-PI, 0) 之间的值
    rad = -rad;
  }
  return rad;
}

// 弧度转角度
const rad2Deg = (rad) => (rad * 180) / Math.PI;

const distance = (p1, p2) => {
  const a = p1.x - p2.x;
  const b = p1.y - p2.y;
  return Math.sqrt(a * a + b * b);
};

const getAttrs = (size, transform) => {
  const width = size.x;
  const height = size.y;
  const matrix = new Matrix(
    transform.m00, // 1
    transform.m10, // 2
    transform.m01, // 3
    transform.m11, // 4
    transform.m02, // 5
    transform.m12 // 6
  );

  const topLeft = { x: transform.m02, y: transform.m12 };
  console.log("x:", topLeft.x)
  console.log("y:", topLeft.y)

  const topRight = matrix.apply({ x: width, y: 0 });
  console.log("width:", distance(topRight, topLeft)); // 223.60679774997897

  const bottomLeft = matrix.apply({ x: 0, y: height });
  console.log("height:", distance(bottomLeft, topLeft)); // 500

  const wSideVec = { x: topRight.x - topLeft.x, y: topRight.y - topLeft.y };
  // 逆时针旋转 90 度,得到旋转向量
  const rotationMatrix = new Matrix(0, -1, 1, 0, 0, 0);
  const rotationVec = rotationMatrix.apply(wSideVec);

  const rad = calcVectorRadian(rotationVec);
  const deg = rad2Deg(rad);
  console.log("rotation:", deg); // -63.43494882292201
};

getAttrs(
  // 宽高
  { x: 100, y: 100 },
  // 变换矩阵
  {
    m00: 1,
    m01: 3,
    m02: 5,
    m10: 2,
    m11: 4,
    m12: 6,
  }
);

运行一下,结果和属性面板一致。

结尾

Figma 只用宽高和变换矩阵来表达矩形,在数据层可以用精简的数据表达丰富的变形,此外在渲染的时候也能将矩阵运算交给 GPU 进行并行运算,是不错的做法。

我是前端西瓜哥,欢迎关注我,学习更多图形编辑器知识。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-12-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端西瓜哥 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
AR涂涂乐⭐八、(add)优化原代码“7”、正方形识别图改为矩形识别图、增加BGM和App 图标
问题1:观察前面代码可发现,屏幕的宽和高在start()时便被复制,不可改变,导致旋转屏幕高宽调转方向,代码中数值却不会改变,发生问题。
星河造梦坊官方
2024/08/14
1000
AR涂涂乐⭐七、(end)取消“识别成功”提示面片、加入太阳系及其交互功能、退出按钮设置
星河造梦坊官方
2024/08/14
860
AR涂涂乐⭐五、关于“4”的部分解决方案
本章总结: 1、此处给地球赋值了,但地球仪支架处于透明材质的material设置中,不会显示,待增加。 2、增添:声明EarthFrame——将面片四点坐标赋值给EarthFrame shader——将投影矩阵Matrix4x4赋值给EarthFrame shader——最后截图赋值给地球仪主主纹理。实际上是先写的shader渲染方法,最后将截到的图片以此渲染方法赋值给模型。 3、因拍摄角度问题,实际渲染出来的模型底座不容易着色,待判断造成原因。
星河造梦坊官方
2024/08/14
910
AR涂涂乐⭐三、 C#实现识别图进入扫描框显示绿色,未进入为红色功能
测试过程发现几个待改进地点: 1、未考虑手机屏幕可旋转问题,导致旋转后手机UV的二维向量就互换了,即X、y变成了y、x(或其他方式),此时不该除以x(宽) 2、若识别图转动了一定角度(90°、180°、270°),即识别图不和屏幕同一方向了(面片和识别图同方向,面片四个点的 数值根据识别图位置确定,我们获取的是面片数值,转换后与屏幕数值相比较,很明显转动角度后比较的数值是错的),会出现不正确显示问题
星河造梦坊官方
2024/08/14
1320
Android多媒体之GLES2战记第四集--移形换影
这也就是m12,m13,m14,m15为什么特别,m0,m1,m2,m3为什么和x息息相关
张风捷特烈
2019/01/28
5990
Android多媒体之GLES2战记第四集--移形换影
碰撞检测的向量实现
注:1、本文只讨论2d图形碰撞检测。2、本文讨论圆形与圆形,矩形与矩形、圆形与矩形碰撞检测的向量实现
WecTeam
2019/12/16
1.6K0
碰撞检测的向量实现
Figma 的编组功能,比你想象的要复杂得多
最近做个人的开源编辑器项目,实现了和 Figma 一样的编组功能,期间踩了不少坑,和大家分享一下。
前端西瓜哥
2024/06/17
3640
Figma 的编组功能,比你想象的要复杂得多
Unity Shader 屏幕后效果——全局雾
Unity内置的雾效需要在每个shader中分别编写,造成了极大的不便。这里利用屏幕后处理产生可单独控制且自由度更高的雾效。
汐夜koshio
2020/03/19
1.7K0
我做了一个在线白板!!!
相信各位写文章的朋友平时肯定都有画图的需求,笔者平时用的是一个在线的手绘风格白板--excalidraw,使用体验上没的说,但是有一个问题,不能云端保存,不过好消息它是开源的,所以笔者就在想要不要基于它做一个支持云端保存的,于是三下两除二写了几个接口就完成了--小白板,虽然功能完成了,但是坏消息是excalidraw是基于React的,而且代码量很庞大,对于笔者这种常年写Vue的人来说不是很友好,另外也无法在Vue项目上使用,于是闲着也是闲着,笔者就花了差不多一个月的业余时间来做了一个草率版的,框架无关,先来一睹为快:
街角小林
2022/05/11
3.7K1
我做了一个在线白板!!!
unity3d:Matrix4x4矩阵位移,缩放,旋转
https://www.cnblogs.com/fangsmile/p/8622421.html
立羽
2023/08/24
8040
unity3d:Matrix4x4矩阵位移,缩放,旋转
剖析 Figma 数据结构:不同图形的特有属性
两个 angle 的弧度值范围为 [0, PI*2),方向为顺时针,基准方向为正右(对应向量为 (1, 0))。
前端西瓜哥
2024/01/22
5340
剖析 Figma 数据结构:不同图形的特有属性
Metal入门教程(六)边界检测
Metal入门教程(一)图片绘制 Metal入门教程(二)三维变换 Metal入门教程(三)摄像头采集渲染 Metal入门教程(四)灰度计算 Metal入门教程(五)视频渲染
落影
2018/07/29
1.6K0
微信小程序单指拖拽和双指缩放旋转
startTouches.length !== 2这个判断的原因是防止图片跳动,因为如果你两个手指触摸,然后离开一个手指,我是禁止拖拽的,只有双指都离开后再次触摸才能单指拖拽
超级小可爱
2023/03/07
2.9K3
微信小程序单指拖拽和双指缩放旋转
根据SVG Arc求出其开始角、摆动角和椭圆圆心
这时候我们通过矩阵运算得到了矩阵x1y1和矩阵cxcy,然后还有以下公式求开始角和摆动角:
ryzenWzd
2022/05/07
5970
根据SVG Arc求出其开始角、摆动角和椭圆圆心
HTML5+CSS3高级动画的应用实践
这个动画实现所用到的3D盒子模型是现在3D模型中最常用的一个 —— 不过我们先拿其中两个面分析:
winty
2020/09/22
1.3K0
HTML5+CSS3高级动画的应用实践
Flutter基础widgets教程-Transform篇
1 Transform 在绘制子widget之前应用转换的widget 2 构造函数 Transform({ Key key, @required this.transform, this.origin, this.alignment, this.transformHitTests = true, Widget child, }) 3 常用属性 3.1 origin:指定子组件做平移、旋转、缩放时的中心点 origin: Offset(50, 50), 3.2
青年码农
2020/10/22
1.2K0
Flutter基础widgets教程-Transform篇
CSS3 transform 和 canvas 背后不为人知的秘密
身为一个合格切图仔,CSS3 的 transfrom 那是熟的不能再熟,什么平移、缩放、旋转更是信手捏来,完全没有难度。不过碰到 transform: matrix() 这个样式,立刻脑袋一片空白,这个 matrix() CSS 函数接收高达 6 个参数!完全不知道它是用来干啥的,去看官方文档也完全没说,一条下面这个简单的样式。
羽月
2022/10/08
1.1K0
CSS3 transform 和 canvas 背后不为人知的秘密
CSS3 动画—transform
在 CSS3 中,跟动画相关的属性有:变形 transform、过渡 transition、动画 animation。先放一个 Lea Verou 大神的链接 animatable。
李振
2021/11/26
6710
地理位置计算之geohash算法
最近在做共享单车单车的项目,用户打开APP后,如果根据当前的经纬度坐标获取附近的车辆呢?
北溟有鱼QAQ
2020/01/15
4.2K0
漫天花雨HTML特效+3D相册
HTML特效是指在网页中使用各种技术和代码来实现动态效果的一种方式。这些效果可以是动画、过渡、交互和其他视觉效果。HTML特效可以在不影响网页性能的同时增强用户体验。
淼学派对
2023/10/14
5750
漫天花雨HTML特效+3D相册
推荐阅读
相关推荐
AR涂涂乐⭐八、(add)优化原代码“7”、正方形识别图改为矩形识别图、增加BGM和App 图标
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验