相关文章推荐
唠叨的硬盘  ·  fp = ...·  2 月前    · 
火爆的沙滩裤  ·  iview modal 弹框 ...·  1 年前    · 

加强功能:让选中元素一起旋转的同时,让每个元素自己单独旋转 ...

组合功能:让某批同类型元素先改变颜色,然后建立层次结构...

导出功能:将一些想要的元素内容设置为一些数据结构导出供其他程序使用...

导入功能:与导出功能相反...

替换重复工作

二、展示 lego-quiz-figma 初步功能

  1. 插件将选中元素保存为图片

  2. 根据不同场景,转化元素的位置

  3. 图片上传到后端

  4. 调整元素顺序

  5. 将当前场景保存,之后直接使用

  6. 将当前位置复制到剪切板

  7. 刷新当前选中元素,重新执行 1-5

"data": [       "type": "container",       "name": "container_01",       "width": 222.78260803222656,       "height": 172.21739196777344,       "x": 16,       "y": 204,       "children": [           "type": "image",           "name": "玩具车3",           "width": 75.78260803222656,           "height": 50.21739196777344,           "x": 0,           "y": 0,           "index": [           "url": "https://sf6-ttcdn-tos.pstatp.com/img/edux-data/1627009282549ca8bcf0b17~0x0.png"           "type": "container",           "name": "container_02",           "width": 170.78260803222656,           "height": 124.21739196777344,           "x": 52,           "y": 48,           "children": [               "type": "image",               "name": "Frame_662",               "width": 151.78260803222656,               "height": 124.21739196777344,               "x": 11,               "y": 0,               "index": [               "url": "https://sf3-ttcdn-tos.pstatp.com/img/edux-data/1627009282384e5da6a2689~0x0.png"               "type": "image",               "name": "玩具车2",               "width": 75.78260803222656,               "height": 50.21739196777344,               "x": 95,               "y": 9,               "index": [               "url": "https://sf6-ttcdn-tos.pstatp.com/img/edux-data/1627009282222dfb3a4ffa4~0x0.png"               "type": "image",               "name": "玩具车1",               "width": 75.78260803222656,               "height": 50.21739196777344,               "x": 0,               "y": 45,               "index": [               "url": "https://sf3-ttcdn-tos.pstatp.com/img/edux-data/16270092820316c732f80fe~0x0.png"           "index": [       "index": [   "scene": "ER_L1"

三、Figma 插件深入体会

如上图所示,整个插件分为两个部分,左边成为沙箱线程,之后就叫做主线程,右边为 iframe 线程,之后就叫 UI 线程

整个插件的入口是主线程,主线程采用一种沙盒结构,其中存在类似 node 环境,可以运行 js 代码,访问 ES6 的 API,可以去 操控 figma 里面的内容

由主线程创建创建 iframe ,并将我们写的 html 插入,由主线程控制整个 iframe 的大小,在 html 中可以使用浏览器的 API,比如说向后端发起请求,可以 和用户进行交流

UI 线程与主线程通过 postMessage 相互通信,所以这里可以有个流程,主线程先运行,主线程设置接收UI线程信息的方法,创建 UI,UI 设置接收信息方法。注意:两者通信内容有限制,除了常见的JSON数据类型外,还有 blob,arraybuffer,像在 figma 中的一些对象都不能直接传输

这两个部分分别就是我们需要提供的两份文件

Figma 插件能做什么

  1. 读取本地文件中的图层和图层属性

插件能暴露文件的内容,这个内容指的是我们在 figma 插件中看到的,比如某个元素的尺寸,位置,层次结构,颜色,文本内容等,我们不仅能获取到,还能更改。

  1. 设计插件的 UI( iframe

  2. 可以访问一些浏览器 API (有些 API 例外,例如 indexedDB

有网络请求;打开文件;使用 canvas webgl(pixi) ;使用 WebAssembly ,使用音频 API;

四、从 0 到发布,走一遍流程

  1. 创建新插件

打开创建插件的窗口

创建 manifest.json 文件

manifest.json 内容

"name": "lego-quiz-figma",   "id": "996264569045667578",   "api": "1.0.0",   "main": "dist/code.js",   "ui": "dist/ui.html"

注意:id,main,ui

有一些操作必须得用到这个 id,否则操作会被拒绝,目前我在让 figma 存储一些内容时,必须得在这里声明 id,否则报错。这个 id 生成的方式是,上图中选 "生成新的 manifest.json" ,在生成的 json 文件中就带有属于当前插件的 id

Main 和 ui

回顾 figma 插件两大组成,main 对应主线程内容,ui 对应 ui 线程内容。这里存放的只是路径,在开发模式下,意味着懒加载,即启动时才去相应路径取文件,所以后面我们开发时可以使用 webpack watch 特性,每次更新程序都会把更新程序打包到 dist 文件下,在之后打开插件取的就是更新后的插件,在发布模式下,文件会上传至公司内部,因此地址会发生变化,需要手动上传更新

Figma 插件开发时,只需要向 figma 软件提供一个 manifest.json 文件即可,但是在 manifest.json 文件中必须带有 ui 线程和主线程需要的两份文件地址,当然相应文件得存在。在完成一些特别的操作,需要提供 id,id 声明在 manifest.json 文件中。在开发模式下,dist 文件夹中的内容可以实时更新,在 figma 中重启插件即可应用新的插件内容。

  1. 创建 Main 和 ui 对应的文件

Figma 获取到 manifest.json 文件后,会先执行 main 中的内容

插件需要两个文件,主线程用于和 figma 进行交互,ui 线程用于和用户进行交互。我们开发肯定不是直接在这两个文件中写内容,而是将写的内容打包到这两个文件中。

webpack 配置在项目中,建议后续开发在我这个配置上更改,因为里面有些内容是 figma 官方提供,官方提供插件案例代码地址:figma 插件案例 [1]

打包写入时有个细节,使用 HtmlWebpackInlineSourcePlugin 将代码嵌入到 ui.html 中,这里不能使用 link 或是 script 的 src 标签,因为 figma 只要 manifest.json 中声明的文件。

使用 ts 时安装 npm install --save-dev @figma/plugin-typings 获取 figma 中各种元素类型

编写主线程代码

/// <reference path="../../node_modules/@figma/plugin-typings/index.d.ts" />
import { receiveUIMessage, sendCurrentMode } from './ui-relation';
figma.showUI(__html__, { visible: true, width: 300, height: 180 });
function start() {
  // 1. 设置接受 ui 方法,第一步
  receiveUIMessage();
  // 2. 获取当前模式, 并发送 UI
  sendCurrentMode();
start();
  • Reference 用作 figma 元素的类型提示

  • 第五行是主线程创建 ui 线程并赋予宽高

具体内容后面再看,主线程代码完成,并且创建了 ui ,接下来就是把我们写的 ui 嵌入

编写 UI 代码

return (
    <div className="upload-image">
      <div className="button-group">
        <Button
          loading={loading(imageInfo)}
          icon={<CopyOutlined />}
          id="copy-btn"
          data-clipboard-text={addImageInfo(imageInfo)}
        </Button>
        <Button onClick={updateEvent} icon={<RetweetOutlined />}>
        </Button>
      <p className="label-model">业务场景</p>
      <Radio.Group onChange={onChange} value={model}>
        {/* 中点y轴向下 */}
        <Radio value={Models.COMMON_DEV}>通用</Radio>
        {/* 0,0 y轴向下 */}
        <Radio value={Models.ER_L1}>ER L1</Radio>
        {/* 中点y轴向上 */}
        <Radio value={Models.ER_GAME}>ER 课后练习</Radio>
      </Radio.Group>
 

这是使用 react 生成一个 div 标签,作为 ui 中的子节点,与平常开发网页类似,最后将有关内容全都集合在一个 ui.html 中

  1. 导入配置并执行

导入 manifest.json 过程之前已经介绍,导入后执行过程如下

点击执行-->执行主线程文件-->创建 iframe-->插入 UI 内容

找到插件管理

五、介绍 lego-quiz-figma 细节

主线程文件名为 code

Code 相关细节

  1. code 通过 postmessage 接收和传输消息

// 发送消息到 UI
export function transferUIData(transferData: CodeToUIData) {
  figma.ui.postMessage(transferData);
// 接受来自 UI 的消息
export function receiveUIMessage() {
  figma.ui.onmessage = ({ message, type }) => {
    if (type === UIToCodeType.UPDATE) {
      figmaStorage.setData('model', message);
      startDataTransform();
 
  1. Figma 存储和获取信息,用于存储当前选中的场景,下次打开时使用,这里的 api 就需要使用到 id,没有提供会报错(不能使用 LocalStorage 和 indexedDB

export const figmaStorage = {
  cache: {},
  async getData(key: string) {
    if (this.cache[key]) {
      return this.cache[key];
    // 主要 api
    const value = await figma.clientStorage.getAsync(key);
    this.cache[key] = value;
    return value;
  // value 可以是任意类型数据
  setData(key: string, value: any) {
    if (this.cache[key] && this.cache[key] === value) {
      return;
    this.cache[key] = value;
    // 主要 api
    return figma.clientStorage.setAsync(key, value);
 
  1. 获取选中元素,转化为结构化数据通过 postmessage 传输到 ui 线程,

获取选中元素的顺序是有问题的:

选中此页面上的节点。每个页面分别存储自己的选择。选择中的节点顺序是未指定的,您不应该依赖它

我的解决方案,记录下每个元素的层级信息 [0,8,4],[1,6,5],[0,8,5],[1,7],[2] 根据每位数值判定谁是上级谁是下级(先后顺序判断 )

获取选中节点 api: figma.currentPage.selection,得到选中元素的数组

遍历每一个元素,将每个元素转化成预期数据结构并存入一个数组中

不同场景下,每个元素的坐标略有差异,每个元素经过转化后的类型

// 1. 创建对象
  const nodeData = {
    type: exportNodeType,
    name: normalName,
    width: width,
    height: height,
    x: x + pos.x,
    y: y + pos.y,
    bytes,
    children: null
 

type:分为四种类型,zone:热区,locateDot:锚点,container:容器,image:图片

热区:表示一块区域,这块区域用于一些判断操作,最后预览时不会显示,比如某些点击事件只能在这里面进行操作,比如某些元素只能在区域内部移动等

锚点:给某些元素提供一个参考原点,便于计算

容器:专门用作多级目录使用,存储自己以及孩子的信息,container 自己没有需要显示的内容

图片:基本上能看到的内容都属于图片类型

转化过程需要注意:在 figma 中,frame 和 group 坐标计算方式是不同的

建立一个原点,让选中的所有元素的 x 和 y 都是基于这个原点,剔除 frame 对其孩子的影响

const pos = {
    x: 0,
  let temp = findFrameNode(node.parent);
  let ratio = 1;
  while (temp) {
    ratio = temp.width / CONVENTION_SIZE.width;
    if (frameMayBeContainer(temp, ratio)) {
      break;
    if (temp.parent.type !== 'PAGE') {
      // 当前 frame 在 page 中的地方
      pos.x += temp.x;
      pos.y += temp.y;
    temp = findFrameNode(temp.parent);
 

如果元素是图片,将图片导出成 Uint8Array 格式存入 bytes 属性中

// 遍历 node
  if (exportNodeType === 'image') {
    nodeData.bytes = await node.exportAsync({
      format: 'PNG',
      constraint: {
        type: 'SCALE',
        value: 1.5
    delete nodeData.children;
 

如果元素是 container 并且有孩子,那么递归调用自己

if ('children' in node) {
      for (const child of node.children) {
        nodeData.children.push(await getImageInfo(child, originNode));
 

前面工作完成后,就可以开始转换每个元素的坐标,影响坐标的元素有两大点,第一大点是当前场景,第二大点是选中节点是 container 类型

转化方法如下

// nodeInfo 为节点转化后的信息,position 相对原点
// originInfo 是离节点最近的参考点,主要用于 container,如果没有 container,最近参考点就是原点
function getModelPosition(nodeInfo: ExportNodeInfo, originInfo) {
  return {
    [Models.COMMON_DEV]: () => ({
      x: normalNum(
        nodeInfo.x - originInfo.originX - originInfo.w + nodeInfo.width / 2
      y: normalNum(
        nodeInfo.y - originInfo.originY - originInfo.h + nodeInfo.height / 2
    [Models.ER_L1]: () => ({
      x: normalNum(nodeInfo.x - originInfo.originX),
      y: normalNum(nodeInfo.y - originInfo.originY)
    [Models.ER_GAME]: () => ({
      x: normalNum(
        nodeInfo.x - originInfo.originX - originInfo.w + nodeInfo.width / 2
      y: normalNum(
        -nodeInfo.y + originInfo.originY + originInfo.h - nodeInfo.height / 2
 

这里还要考虑在 edit 中,三个场景的区别,方便后续将数据传入并解析

首先分析上述代码,以 ER_L1 为例,同时这也是最容易转换的情况

ER_L1 坐标计算方式与 figma 相同,因此不需要进行坐标变换,减去最近参考点坐标的原因如下

有同学肯定会问,之前把参考点都置为原点,现在又把参考点置为最近参考点,有必要吗?

有,置为原点原因在于:可能直接复制 frame 下的元素,此时该元素的 X 和 Y 的参考点就应该是原点,而不是 父节点frame。还原的原因是复制 container 时,由于要记录层级关系,因此要转化为相对于父节点的坐标,而非全局坐标

UI 相关细节

  1. UI 接收 code 消息

window.addEventListener('message', (event) => {
      const { type, data } = event.data.pluginMessage;
      switch (type) {
        case CodeToUIType.UploadImage:
          uploadHandler(data);
          break;
        // 初始化 模式
        case CodeToUIType.ModelData:
          setModel(data);
          break;
        case CodeToUIType.COMMON_MESSAGE:
          message[data[0]]({
            content: data[1],
            className: 'message-style',
            duration: 2
 
  1. UI 向 code 发送消息

const sendMessageToCore = (message: any, type: UIToCodeType) => {
// pluginMessage, * 是不可变的
    parent.postMessage({ pluginMessage: { message, type } }, '*');
 
  1. 业务场景区别

三个场景 x 轴正方向都是 向右👉

通用:COMMON_DEV,锚点在 (0.5,0.5),y 轴正方向 向下👇

ER L1:ER_L1,锚点在(0,0),y 轴正方向 向下👇

ER 课后练习:ER_GAME,锚点在 (0.5,0.5),y 轴正方向 向上👆

六、体会与交流

  • Figma 插件能做一些我们手动做的事,因此有些规律性工作可以使用 figma 插件

  • 我们不能总是向 UI 设计师索取内容,假如看到一些好的元素,好好利用 figma 插件导入一些资源,给 UI 一些惊喜也是好的

  • 我做的功能是导出自定义格式数据,可扩展性不高,后续可以调研如果导出一种通用的格式

  • Figma 有些比较有趣的内容,比如可以直接导出 Uint8Array 格式的图片,可以细腻控制每一个元素

  • 可以多多交流 制作插件的体会

Figma 对插件的展望

  1. 更多的访问文件、用户、团队信息、评论

  2. 完全访问团队库

  3. 在事件上触发插件代码,挑战在于:性能下降,内部稳定性,外部稳定性

  4. 长时间允许插件

  5. 属性面板/工具栏中设置插件

  6. 插件的键盘快捷键

  7. 文件浏览器中的插件

  8. 访问版本历史记录

  9. 辅助函数 API

  10. 插件的 Figma UI 组件,能够倒入 Figma 中的元素

  11. 插件分析错误报告

  1. 桌面特定的API

  2. 加载外部字体

figma 插件案例: https://github.com/figma/plugin-samples/tree/master/webpack

关于奇舞团

奇舞团是 360 集团最大的大前端团队,代表集团参与 W3C 和 ECMA 会员(TC39)工作。奇舞团非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。

一个前端新手,最近在写Figma插件插件主要是写给自己用。由于功能不多,之前组件和样式是自己手写的css,调来调去使用体验也不好,Figma说目前还没有官方的ui组件库,大家可以试试 figma-plugin-ds (后面简称ds),组件样式和svg图标跟figma原生的一样,虽然有些figma上很细节的功能还做不到,比如在输入框的边缘移动能调整数值,但做一个简单的面板还可以。 要快速上手,可以直接在html里引用css和js的在线地址。但由于文件在github上,启动插件时ui样式要等加载完才显示正常 command + shift + p立即重新运行插件 安装和贡献 克隆存储库: git clone https://github.com/madebyankur/figma-proportional-scale.git 转到目录: cd figma-proportional-scale 安装figplug: npm i -g fig 在 Figma,我们最近解决了迄今为止最大的工程挑战之一:支持插件。我们的插件 API 使第三方开发人员可以直接在基于浏览器的设计工具中运行代码,因此团队可以使 Figma 适应自己的工作流程。他们可以用可访问性检查器测量对比度,用翻译应用程序转换语言,进口商可以用内容填充设计,以及其他需求。我们必须仔细设计该插件的功能。在整个软件历史中,有很多第三方扩展对平台产生负面影响的例子。在某些情况下,他们拖慢了工具的运行速度,在其他情况下,每当平台有新版本发布时,插件就会中断。... 原文:https://www.figma.com/blog/how-we-built-the-figma-plugin-system/翻译:https://juejin.cn/post/6844903956263501838作者:夏华在 Figma,我们最近解决了迄今为止最大的工程挑战之一:支持插件。我们的插件 API 使第三方开发人员可以直接在基于浏览器的设计工具中运行代码,因此团队可以使 F... Figma自2012年创办于美国,2015年逐渐被国内设计行业知晓,虽然不如Adobe产品那样历史悠久,但其后来居上,国外和国内部分先进的设计团队,都已逐步将Figma作为主力设计工具。 许多年前,人们对UI设计工具的认识还停留在Ps(Adobe Photoshop)的时代。不可否认,Ps确实是目前最牛逼的设计软件,但“牛逼”不代表“适合”。Ps更倾向于处理位图,而它的同胞兄弟Ai(Adobe Illustrator)虽可做矢量图形,但也仅适合做平面设计。 对于UI设计来说,视觉层仅仅是表现形式,核心部分. 更方便地看到和分享图片。我们都知道电脑屏幕的布局是以宽屏的形式存在的,这意味着,FigmaEX利用横向总宽,在右侧固定一个区域,以显示基本信息,从而快速固定你想要的东西figma插件,而且这个插件是中国人生产的,更让人骄傲。其实在日常使用中,我们要填充的无非是普通的“姓名、地址、电话号码、邮箱”等等,在常用功能中,我们最重要的是要想快速上手,在我打开的时候,Contento可直接使用。有时,如果你想快速连接原始形状之间的交互,你可以使用它,可以连接不同的页面,也可以定制流程线的颜色、厚度、弧形尺寸等。... figma 安装插件 Inside the plugin, you can do whatever is available to the developer on the web.There are practically no restrictions on the possibilities, except for those related to the system.Fortuna... 拼多多:拼多多 开放平台 京东:http://prod-oms-app-cprt.jdwl.com/OpenCloudPrint/setup.zip 抖店:https://logistics.douyinec.com/davinci/index 快手:轻雀文档 唯品会:唯品会企业云盘-文档共享 密码 BP98xn 得物:得物开放平台为统一各供应商云打印配置,V6提供以下六个字段处理: DOCUMENTID、SIGNATURE、TEMPLATEUR 原文:https://www.figma.com/blog/how-we-built-the-figma-plugin-system/翻译:https://juejin.cn/post/6844903956263501838作者:夏华在 Figma,我们最近解决了迄今为止最大的工程挑战之一:支持插件。我们的插件 API 使第三方开发人员可以直接在基于浏览器的设计工具中运... figma里并没有图像对象,图像其实是各种形状和文字上的填充,默认情况下导入一张图片,会自动创建一个矩形形状,并填充图像以及缩放到对应尺寸。实际使用时,会需要类似将多张图片缩放至2倍大小的需求,而其中部分图片在这之前已经被缩放过了,此时就必须知道原图的大小了。 在figma中可以找到原图的链接,用浏览器访问该链接,链接的标题(浏览器tab页的标题)会有尺寸信息,亦或是你想把这张图下载重新处理,这种方法也是有效的。 最近在研究figma插件开发,由于不懂前端技术,学习起来挺吃力的,但好歹是做出来了。 figma 安装插件Since Figma released the Figma Community (Beta), I’ve been working on Figma plugins in my free time while I study the code. With the help of an engineer friend of mine, I’ve developed four s... 预备知识Figma 插件开发本质上是 web 开发,你需要了解基础的 web 开发知识,比如 HTML、CSS、JavaScript。当然,如果要开发一些功能复杂的插件,也许你需要用到一些更高级的工具,比如 TypeScript、Webpack、React 等,但这些对于入门来说不是必要的。不过,要完成本教程,你需要掌握 TypeScript……的安装。如果还没有对最基本的 HCJ(HTML,CS... 测试可视化My professional duties include building data visualizations that a) contain useful information and b) are intuitive to use and interpret. Therefore, it bugs me when I encounter visualizations in ...