目的:介绍 Figma 插件;figma 插件开发从 0 到 1; 分享自己开发的想法
Figma 初印象
figma 是一个 基于浏览器 的协作式 UI 设计工具
figma 插件初印象
加强功能:让选中元素一起旋转的同时,让每个元素自己单独旋转 ...
组合功能:让某批同类型元素先改变颜色,然后建立层次结构...
导出功能:将一些想要的元素内容设置为一些数据结构导出供其他程序使用...
导入功能:与导出功能相反...
替换重复工作
{
"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": [
0,
9,
0
],
"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": [
0,
9,
1,
0
],
"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": [
0,
9,
1,
1
],
"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": [
0,
9,
1,
2
],
"url": "https://sf3-ttcdn-tos.pstatp.com/img/edux-data/16270092820316c732f80fe~0x0.png"
}
],
"index": [
0,
9,
1
]
}
],
"index": [
0,
9
]
}
],
"scene": "ER_L1"
}
如上图所示,整个插件分为两个部分,左边成为沙箱线程,之后就叫做主线程,右边为 iframe
线程,之后就叫 UI 线程
整个插件的入口是主线程,主线程采用一种沙盒结构,其中存在类似 node
环境,可以运行 js 代码,访问 ES6
的 API,可以去操控 figma 里面的内容。
由主线程创建创建 iframe
,并将我们写的 html
插入,由主线程控制整个 iframe
的大小,在 html
中可以使用浏览器的 API,比如说向后端发起请求,可以和用户进行交流。
UI 线程与主线程通过 postMessage 相互通信,所以这里可以有个流程,主线程先运行,主线程设置接收UI线程信息的方法,创建 UI,UI 设置接收信息方法。注意:两者通信内容有限制,除了常见的JSON数据类型外,还有 blob,arraybuffer,像在 figma 中的一些对象都不能直接传输
这两个部分分别就是我们需要提供的两份文件
插件能暴露文件的内容,这个内容指的是我们在 figma 插件中看到的,比如某个元素的尺寸,位置,层次结构,颜色,文本内容等,我们不仅能获取到,还能更改。
iframe
)indexedDB
)有网络请求;打开文件;使用 canvas
,webgl(pixi)
;使用 WebAssembly
,使用音频 API;
打开创建插件的窗口
创建 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
有一些操作必须得用到这个 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 中重启插件即可应用新的插件内容。
2 . ### 创建 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 嵌入
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>
</div>
<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>
</div>
);
这是使用 react
生成一个 div 标签,作为 ui 中的子节点,与平常开发网页类似,最后将有关内容全都集合在一个 ui.html
中
导入 manifest.json 过程之前已经介绍,导入后执行过程如下
点击执行-->执行主线程文件-->创建 iframe
-->插入 UI 内容
找到插件管理
发布插件
主线程文件名为 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();
}
};
}
2 . 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);
}
};
3 . 获取选中元素,转化为结构化数据通过 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,
y: 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 时,由于要记录层级关系,因此要转化为相对于父节点的坐标,而非全局坐标
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
});
}
});
2 . UI 向 code 发送消息
const sendMessageToCore = (message: any, type: UIToCodeType) => {
// pluginMessage, * 是不可变的
parent.postMessage({ pluginMessage: { message, type } }, '*');
};
3 . 业务场景区别
三个场景 x 轴正方向都是 向右
通用:COMMON_DEV,锚点在 (0.5,0.5),y 轴正方向 向下
ER L1:ER_L1,锚点在(0,0),y 轴正方向 向下
ER 课后练习:ER_GAME,锚点在 (0.5,0.5),y 轴正方向 向上
不会做
[1]figma 插件案例: https://github.com/figma/plugin-samples/tree/master/webpack
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/1CgE12zDAPuEhyZA4AwMeA
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。