Figma 一直鼓励开发人员和设计师之间的协作。它依靠社区制作的插件的无限宝库。需要 3D 元素?有一个插件可以做到。需要抽象 SVG?也有一个插件可以做到。
也就是说,Figma 的设计部分一直相对静态——始终使用无法移动的矩形,通过预定义的用户交互相互连接。但如果我告诉你,你的设计可以突然栩栩如生——它们可以被动画化、交互化,甚至是有状态的?那么,概念与实现之间有什么区别呢?
Figma在 6 月份宣布将基于 JavaScript 的小部件引入其中。现在,设计师可以直接在 Figma 中浏览和实现逻辑驱动的组件!
向小部件 API问好!您想知道它是什么以及如何使用它?这正是我们将在本文中一起做的事情。
Figma 小部件开启了无限可能
想象一下,您与您的搭档通宵达旦地设计一个大型餐厅应用程序。你们都在同一个 Figma 画板上进行协作;你们都在共享完全相同的文档,并且更改会实时发生。
当然,您已经知道协作不仅仅涉及设计过程
- 项目管理,
- 举办投票以收集意见,
- 导入和可视化模拟数据,
- 甚至可能玩一个多人游戏来在长时间工作后放松身心。
我们只需要一个人来管理所有事情并向小组的其他成员发送链接。但是,这效率不高,不是吗?
好吧,这就是小部件发挥作用的地方。我们可以想象地做到所有这些——是的,所有这些——无需离开 Figma。
以下是在 Figma 中可能希望使用小部件的一些方法
列表还在继续。如您所见,您可以在文档中自由使用大量的小部件。实际上,您可以从“小部件”菜单(Shift
+I
)直接将小部件添加到您的画板中。
但我们并不是要学习如何使用小部件,因为这很简单。让我们做我们最擅长的事情:我们将创建我们自己的 Figma 小部件!这个小部件将受到Chris Coyier 的设计引用网站的启发。我们将获取 API,将其馈送到小部件,然后直接在 Figma 中显示随机的设计引用。

我们需要什么
我不喜欢做坏消息的传达者,但为了开发小部件,您必须使用 Windows 或 Mac。Linux 用户,对不起,但您运气不佳。(如果您想继续学习,您仍然可以使用虚拟机。)
我们将下载 Figma 桌面应用程序。最简单的入门方法是直接从应用程序生成小部件模板。

让我们通过打开小部件菜单(Shift
+I
)、切换到“开发”选项卡并创建一个新项目来创建一个新画板。

之后,Figma 将提示您命名新的小部件并确定它是否更适合设计画板还是 FigJam 画板。对于本文的目的,前者就足够了。

自定义功能不止于此;Figma 还将为您提供选择,可以选择从预制计数器小部件或启用 iFrame 的替代方案开始,后者还允许您访问 Canvas 和 Fetch API(以及所有其他浏览器 API)。我们将选择简单的“空”选项,但最终我们会对其进行修改以利用 Fetch API。
然后,系统将提示您将新的小部件项目保存到系统中的特殊目录中。完成后,启动您的终端并将其定向到该文件夹。暂时不要运行任何命令——我们稍后会这样做,并有意地获得错误,目的是了解有关小部件 API 的更多信息。
设计小部件

我们直接从Chris Coyier 的设计引用网站获取设计。因此,让我们访问该网站并通过启动 DevTools 进行深入研究。
我在这里使用的两个关键快捷键是Ctrl
+Shift
+C
(或Cmd
+Shift
+C
)切换“拾取元素”工具,以及Shift
+Click
将颜色格式更改为 HEX 代码。我们这样做是为了了解 Chris 网站中使用的颜色、字体、字体粗细和字体大小。所有这些信息对于在 Figma 中构建一个非常相似的部件至关重要,这将是我们的下一步!您可以获取设计好的组件并在您自己的画布中使用它。
我不会在这里详细介绍,因为本文的主题是通过编写代码构建小部件。但我必须强调,妥善维护小部件的样式非常重要……CSS-Tricks 已经有很多面向设计的 Figma 教程;将它们添加到您的阅读列表中,您不会后悔的。
创建小部件的布局
设计完成后,是时候拿出我们的编程手指并开始构建我们小部件的齿轮了。
Figma 如何将其设计构建块转换为类似 React 的组件非常有趣。例如,具有自动布局功能的框架元素在代码中表示为<AutoLayout />
组件。除此之外,我们还将使用另外两个组件:<Text />
和<SVG />。
看看我的 Figma 画板……我正是要求您关注对象树。这是我们需要能够将我们的小部件设计转换为 JSX 代码的关键。

如您所见,我们的设计引用小部件需要导入三个组件。考虑到完整 API仅包含八个基于图层的节点,这是一个相当数量的组件。但您很快就会看到,这些模块足以制作各种布局。
// code.tsx
const { widget } = figma;
const { AutoLayout, Text, SVG } = widget;
有了这些,我们就可以继续像在 React 中一样构建我们小部件的骨架了
function QuotesWidget() {
const quote = `...`;
const author = `...`;
return (
<AutoLayout>
<SVG />
<AutoLayout>
<Text>{quote}</Text>
<Text>— {author}</Text>
</AutoLayout>
<SVG />
</AutoLayout>
);
}
widget.register(QuotesWidget);
这段代码至少可以说非常混乱。现在,我们无法区分设计图层。值得庆幸的是,我们可以通过使用name
属性轻松解决此问题。
<AutoLayout name={"Quote"}>
<SVG name={"LeftQuotationMark"} />
<AutoLayout name={"QuoteContent"}>
<Text name={"QuoteText"}>{quote}</Text>
<Text name={"QuoteAuthor"}>— {author}</Text>
</AutoLayout>
<SVG name={"RightQuotationMark"} />
</AutoLayout>;
当然,我们仍然看不到我们的引号 SVG,所以让我们着手解决这个问题。<SVG/>
组件接受一个src
属性,该属性获取 SVG 元素的源代码。对此没有太多可说的,所以让我们保持简单并直接跳回代码
const leftQuotationSvgSrc = `<svg width="117" height="103" viewBox="0 0 117 103" fill="none" xmlns="<http://www.w3.org/2000/svg>">
// shortened for brevity
</svg>`;
const rightQuotationSvgSrc = `<svg width="118" height="103" viewBox="0 0 118 103" fill="none" xmlns="<http://www.w3.org/2000/svg>">
// shortened for brevity
</svg>`;
function QuotesWidget() {
return (
<SVG name={"LeftQuotationMark"} src={leftQuotationSvgSrc} />
<SVG name={"RightQuotationMark"} src={rightQuotationSvgSrc} />
);
}
我认为我们都可以同意现在一切都清楚多了!当我们命名事物时,它们的用途突然变得对我们代码的读者更加清晰。
实时预览我们的部件
Figma 在构建小部件时提供了极佳的开发体验,包括(但不限于)热重载。使用此功能,我们能够实时编写代码并预览对我们小部件的更改。
通过打开小部件菜单(Shift
+I
)、切换到开发选项卡并将新的小部件单击或拖动到画板上来开始。找不到你的小部件?别担心,只需单击三点菜单并导入小部件的manifest.json
文件即可。是的,这足以让它重新出现!

等等,您是否在屏幕底部收到错误消息?

如果是这样,让我们调查一下。单击“打开控制台”并阅读其内容。如果“打开控制台”按钮消失了,则还有另一种方法可以打开调试控制台。单击 Figma 徽标,跳转到“小部件”类别并显示开发菜单。

这个错误可能是因为我们还没有将 TypeScript 编译成 JavaScript。我们可以在命令行中运行 npm install
和 npm run watch
来完成编译。(或者使用 yarn
和 yarn watch
)。这次没有错误了!
你可能会遇到的另一个问题是,每次代码更改时,小部件都无法重新渲染。我们可以使用以下上下文菜单命令轻松强制更新小部件: 小部件 → 重新渲染小部件。

样式设置小部件
目前,我们小部件的 外观 与最终目标还相差甚远。

那么,我们如何从代码中设置 Figma 组件的样式呢?也许像我们在 React 项目中那样使用 CSS?不。对于 Figma 小部件, 所有 样式都是通过一组 完善的 props 来设置的。幸运的是,这些项目的名称几乎与 Figma 中的对应项 完全相同。

我们将从配置两个 <AutoLayout />
组件开始。如上图所示,prop 名称对它们的用途进行了很好的描述。这使得我们可以直接跳转到代码并开始进行一些更改。我不会再次显示整个代码,所以请依靠组件名称来指导您代码片段所属的位置。
<AutoLayout
name={"Quote"}
direction={"horizontal"}
verticalAlignItems={"start"}
horizontalAlignItems={"center"}
spacing={54}
padding={{
horizontal: 61,
vertical: 47,
}}
>
<AutoLayout
name={"QuoteContent"}
direction={"vertical"}
verticalAlignItems={"end"}
horizontalAlignItems={"start"}
spacing={10}
padding={{
horizontal: 0,
vertical: 0,
}}
></AutoLayout>
</AutoLayout>;
我们取得了很大的进展!让我们保存并返回 Figma 以查看我们的 widget 现在是什么样子。还记得 Figma 在每次有新更改时都会自动重新加载小部件吗?

但它还没有完全完成。我们还必须为根组件添加背景颜色。
<AutoLayout name={"Quote"} fill={"#ffffff"}>
再次查看您的 Figma 画板,注意更改如何几乎立即反映回小部件中。

让我们继续本指南,并设置 <Text>
组件的样式。

在查看了 小部件 API 文档 后,可以再次清楚地看到属性名称几乎与其在 Figma 应用中的对应项相同,如上图所示。我们还将使用上一节中检查 Chris 网站时获取的值。

<Text name={'QuoteText'}
fontFamily={'Lora'}
fontSize={36}
width={700}
fill={'#545454'}
fontWeight={'normal'}
>{quote}</Text>
<Text name={'QuoteAuthor'}
fontFamily={'Raleway'}
fontSize={26}
width={700}
fill={'#16B6DF'}
fontWeight={'bold'}
textCase={'upper'}
>— {author}</Text>
向小部件添加状态
我们的小部件目前显示相同的引用,但我们希望从整个引用池中随机提取。我们必须向小部件添加 状态 ,所有 React 开发人员都知道这是一个变量,其变化会触发组件的重新渲染。
在 Figma 中,状态是使用 useSyncedState
钩子创建的;它几乎等同于 React 的 useState
,但它要求程序员指定一个 唯一 的键。此要求源于这样一个事实,即 Figma 必须在 所有 可能正在查看同一设计画板的客户端之间同步我们小部件的状态,但这些客户端使用不同的计算机。
const { useSyncedState } = widget;
function QuotesWidget() {
const [quote, setQuote] = useSyncedState("quote-text", "");
const [author, setAuthor] = useSyncedState("quote-author", "");
}
目前,我们只需要进行这些更改。在下一节中,我们将了解如何从 Internet 获取数据。剧透警告:这并不像看起来那么简单。
从网络获取数据
回想一下 Figma 是否给了我们选择以启用 iFrame 的小部件开始。虽然我们没有选择该选项,但我们仍然必须实现其中的一些功能。让我解释一下为什么我们不能在小部件代码中简单地调用 fetch()
。
当您使用小部件时,您是在自己的计算机上运行由其他人编写的 JavaScript 代码。虽然所有小部件都经过 Figma 员工的彻底审查,但它仍然是一个巨大的安全漏洞,因为我们都知道即使是一行 JavaScript 也可以造成多大的 损害。
因此,Figma 不能简单地 eval()
由匿名程序员编写的任何小部件代码。长话短说,团队决定最佳解决方案是在一个严密保护的沙盒环境中运行第三方代码。正如您可能猜到的那样,浏览器 API 在这种环境中是不可用的。
但不要担心,Figma 对此第二个问题的解决方案是 <iframe>
。我们在文件中编写的任何 HTML 代码(最好称为 ui.html
)都将可以访问所有浏览器 API。您可能想知道我们如何从小部件中触发此代码,但我们稍后会研究这个问题。现在,让我们回到代码中。
// manifest.json
{
"ui": "ui.html"
}
<!-- ui.html -->
<script>
window.onmessage = async (event) => {
if (event.data.pluginMessage.type === 'networkRequest') {
// TODO: fetch data from the server
window.parent.postMessage({
pluginMessage: {
// TODO: return fetched data
}
}, '*')
}
}
</script>
这是小部件到 iframe
通信的通用模板。让我们用它从服务器获取数据。
<!-- ui.html -->
<script>
window.onmessage = async (event) => {
if (event.data.pluginMessage.type === 'networkRequest') {
// Get random number from 0 to 100
const randomPage = Math.round(Math.random() * 100)
// Get a random quote from the Design Quotes API
const res = await fetch(`https://quotesondesign.com/wp-json/wp/v2/posts/?orderby=rand&per_page=1&page=${randomPage}&_fields=title,yoast_head_json`)
const data = await res.json()
// Extract author name and quote content from response
const authorName = data[0].title.rendered
const quoteContent = data[0].yoast_head_json.og_description
window.parent.postMessage({
pluginMessage: {
authorName,
quoteContent
}
}, '*')
}
}
</script>
为了保持简单和重点,我们省略了错误处理。让我们回到小部件代码中,看看我们如何访问在 <iframe>
中定义的函数。
function fetchData() {
return new Promise<void>(resolve => {
figma.showUI(__html__, {visible: false})
figma.ui.postMessage({type: 'networkRequest'})
figma.ui.onmessage = async ({authorName, quoteContent}) => {
setAuthor(authorName)
setQuote(quoteContent)
resolve()
}
})
}
如您所见,我们首先告诉 Figma 公开对我们隐藏的 <iframe>
的访问权限,并触发一个名为 "networkRequest"
的事件。我们通过检查 event.data.pluginMessage.type === 'networkRequest',
在 ui.html
文件中处理此事件,然后将数据发布回小部件。
但目前还没有发生任何事情……我们还没有调用 fetchData()
函数。如果我们直接在组件函数中调用它,控制台会显示以下错误。
Cannot use showUI during widget rendering.
Figma 告诉我们不要直接在函数体中调用 showUI
……那么我们应该把它放在哪里呢?答案是一个新钩子和一个新函数: useEffect
和 waitForTask
。如果您是 React 开发人员,您可能已经熟悉 useEffect
,但我们在这里将使用它来在小部件组件挂载时从服务器获取数据。
const { useEffect, waitForTask } = widget;
function QuotesWidget() {
useEffect(() => {
waitForTask(fetchData());
});
}
但这会导致另一个“错误”,其中我们的 widget 会永远不断地使用新的引用进行重新渲染。发生这种情况是因为 useEffect
根据定义,每当 widget 的状态发生变化时,或者当我们调用 fetchData
时,都会再次触发。虽然 有一种技术 可以只在 React 中调用一次 useEffect
,但它不适用于 Figma 的实现。来自 Figma 文档
由于 小部件的运行方式,
useEffect
应该能够处理多次使用相同状态被调用。
幸运的是,我们可以利用一个简单的解决方法,只在组件第一次挂载时调用一次 useEffect
,方法是检查状态的值是否仍然为空。
function QuotesWidget() {
useEffect(() => {
if (!author.length & !quote.length) {
waitForTask(fetchData());
}
});
}
您可能会遇到一个可怕的“内存访问越界”错误。在 插件和 widget 开发中,这种情况非常常见。只需重新启动 Figma,它就不会再出现了。
您可能已经注意到,有时引用文本包含奇怪的字符。

这些是 Unicode 字符 ,我们必须在代码中正确格式化它们。
<!-- ui.html -->
<script>
window.onmessage = async (event) => {
// ...
const quoteContent = decodeEntities(data[0].yoast_head_json.og_description);
};
// <https://stackoverflow.com/a/9609450>
var decodeEntities = (function () {
// this prevents any overhead from creating the object each time
var element = document.createElement("div");
function decodeHTMLEntities(str) {
if (str && typeof str === "string") {
// strip script/html tags
str = str.replace(/<script[^>]*>([\\\\S\\\\s]*?)<\\\\/script>/gim, "");
str = str.replace(/<\\\\/?\\\\w(?:[^"'>]|"[^"]*"|'[^']*')*>/gim, "");
element.innerHTML = str;
str = element.textContent;
element.textContent = "";
}
return str;
}
return decodeHTMLEntities;
})();
</script>
就这样,每次将我们的 widget 添加到设计画板时,它都会获取一个全新的设计引用。
向小部件添加属性菜单
虽然我们的 widget 在实例化时会获取新的引用,但如果我们能够再次执行此过程而无需删除它,则会更实用。本节内容较短,因为解决方案非常出色。借助 属性菜单 ,我们可以通过一次调用 usePropertyMenu
钩子来为我们的 widget 添加交互性。

const { usePropertyMenu } = widget;
function QuotesWidget() {
usePropertyMenu(
[
{
itemType: "action",
propertyName: "generate",
tooltip: "Generate",
icon: `<svg width="22" height="15" viewBox="0 0 22 15" fill="none" xmlns="<http://www.w3.org/2000/svg>">
<!-- Shortened for brevity -->
</svg>`,
},
],
() => fetchData()
);
}
使用一个简单的钩子,我们就可以创建一个按钮,当 widget 被选中时,该按钮会出现在 widget 附近。这是我们为了完成这个项目而需要添加的最后一部分。
将我们的 widget 发布到公共
如果没有人 使用 它,那么构建 widget 就没有多大意义。虽然 Figma 允许组织选择为内部使用启动 私有 widget ,但将这些小程序发布到全世界更为常见。
Figma 拥有一个严格的小部件审查流程,可能需要 5 到 10 个工作日。虽然我们一起构建的设计引用 widget 已存在于 widget 库中 ,但我仍将演示它是如何到达那里的。 请勿尝试重新发布此 widget,因为这只会导致其被删除。但是,如果您对其进行了一些重大更改,请继续与社区分享您自己的 widget!
首先,点击 widget 菜单(Shift
+I
)并切换到 开发 选项卡以查看我们的 widget。点击三个点菜单,然后按 发布。

Figma 会提示您输入有关 widget 的一些详细信息,例如标题、描述和一些标签。我们还需要一个 128×128 的图标图像和一个 1920×960 的横幅图像。

导入所有这些资源后,我们仍然需要 widget 的屏幕截图。关闭发布模态(不用担心,您不会丢失数据),然后右键单击 widget 以显示一个有趣的上下文菜单。找到 复制/粘贴为类别并选择 复制为 PNG。
完成此操作后,让我们回到发布模态并粘贴 widget 的屏幕截图。

向下滚动,最后发布您的模态。庆祝吧!🎉

Figma 会在几天内与您联系,告知您的模态窗口审查状态。如果被拒绝,您将获得再次修改并重新提交的机会。
结论
我们刚刚从头构建了一个 Figma 小组件!这里没有涵盖许多内容,例如点击事件、输入表单和更多内容。您可以在这个 GitHub 仓库中深入了解该小组件的完整源代码。
对于那些渴望将 Figma 技能提升到更高水平的人,我建议探索小组件社区,并以您感兴趣的内容作为灵感。继续构建更多小组件,继续磨练您的 React 技能,在您意识到之前,您将教我如何做这一切。
更多资源
在制作这个小组件时,我不得不参考大量的文档。我想我会分享我发现最有帮助的内容。
Linux 用户可以使用一个非官方的基于 Electron 的 Figma Linux 桌面应用程序。唯一的缺点是,我无法使其热重载工作。