Figma一直鼓励开发者和设计师之间的协作,并凭借其丰富的社区插件库而蓬勃发展。需要3D元素?有插件!需要抽象的SVG?也有插件!
然而,Figma的设计部分一直相对静态——始终使用不可移动的矩形,通过预定义的用户交互连接在一起。但如果我说你的设计可以突然栩栩如生——可以动画化、交互式,甚至是有状态的,你会怎么想?那么,概念与实现之间还有什么区别呢?
Figma在6月份宣布将推出基于JavaScript的小部件。现在,设计师可以直接在Figma中浏览和实现逻辑驱动的组件!
让我们一起了解一下Widgets API!想知道它是什么以及如何使用它吗?这正是我们将在本文中一起探讨的内容。
想象一下,你和你的合作伙伴日夜合作设计一个大型餐厅应用程序。你们都在同一个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。
然后,系统会提示你将新的Widget项目保存到系统中的特殊目录。完成后,启动你的终端并将其定向到该文件夹。现在还不要运行任何命令——我们稍后会这样做,并故意出现错误,目的是学习更多关于Widgets API的知识。
我们直接从Chris Coyier的设计报价网站获取设计。所以,让我们去那里,通过启动DevTools来深入研究。
我在这里使用的两个关键快捷键是Ctrl Shift C(或Cmd Shift C)来切换“拾取元素”工具,以及Shift 单击来将颜色格式更改为HEX代码。我们这样做是为了了解Chris网站中使用的颜色、字体、字体粗细和字体大小。所有这些信息对于在Figma中构建一个非常相似的Widget至关重要,这将是我们的下一步!你可以抓取设计好的组件并将其用于你自己的画布。
我在这里不会详细介绍,因为本文的主题是通过编写代码来构建小部件。但我必须强调,精心设计小部件的样式非常重要……CSS-Tricks已经有大量面向设计的Figma教程;你不会后悔将它们添加到你的阅读列表中。
设计完成后,是时候拿出我们的编程手指,开始构建我们小部件的齿轮了。
Figma如何将其设计构建块转换为类似React的组件非常有趣。例如,具有自动布局功能的框架元素在代码中表示为<autolayout></autolayout>
组件。除此之外,我们还将使用另外两个组件:<text></text>
和<svg></svg>
。
看看我的Figma画板……我正是要求你关注对象树。这是我们需要能够将我们的Widget设计转换为JSX代码的关键。
如你所见,我们的设计报价小部件需要导入三个组件。考虑到完整的API只包含八个基于图层的节点,这是一个相当数量的组件。但正如你很快就会看到的,这些模块足以制作各种布局。
<code>// code.tsx const { widget } = figma; const { AutoLayout, Text, SVG } = widget;</code>
有了这个,我们就可以像在React中一样构建我们小部件的骨架了:
<code>function QuotesWidget() { const quote = `...`; const author = `...`; return ( <autolayout><svg></svg><autolayout><text>{quote}</text><text>— {author}</text></autolayout><svg></svg></autolayout> ); } widget.register(QuotesWidget);</code>
这段代码至少可以说是非常混乱的。现在,我们无法区分设计图层。幸运的是,我们可以通过使用name
属性轻松解决这个问题。
<code><autolayout name="{" quote><svg name="{" leftquotationmark></svg><autolayout name="{" quotecontent><text name="{" quotetext>{quote}</text><text name="{" quoteauthor>— {author}</text></autolayout><svg name="{" rightquotationmark></svg></autolayout>;</code>
当然,我们仍然看不到我们的引号SVG,所以让我们着手解决这个问题。<svg></svg>
组件接受一个src
属性,该属性采用SVG元素的源代码。对此没有太多可说的,所以让我们保持简单,直接跳回代码:
<code>const leftQuotationSvgSrc = `<svg fill="none" height="103" viewbox="0 0 117 103" width="117" xmlns="<http://www.w3.org/2000/svg>"> // 为简洁起见,已缩短 </svg>`; const rightQuotationSvgSrc = `<svg fill="none" height="103" viewbox="0 0 118 103" width="118" xmlns="<http://www.w3.org/2000/svg>"> // 为简洁起见,已缩短 </svg>`; function QuotesWidget() { return ( <svg name="{" leftquotationmark src="%7BleftQuotationSvgSrc%7D"></svg><svg name="{" rightquotationmark src="%7BrightQuotationSvgSrc%7D"></svg> ); }</code>
我认为我们都可以同意现在一切都清楚多了!当我们命名事物时,它们的目的突然变得对我们代码的读者更加明显。
Figma在构建小部件时提供了良好的开发体验,包括(但不限于)热重载。使用此功能,我们可以实时编码和预览对小部件的更改。
首先打开小部件菜单(Shift I),切换到开发选项卡,然后单击或拖动你的新小部件到画板。找不到你的小部件?别担心,只需单击三点菜单并导入你的小部件的manifest.json
文件即可。是的,这就是让它恢复存在的全部步骤!
等等,你的屏幕底部是否出现错误消息?
如果是这样,让我们调查一下。单击“打开控制台”并阅读它的内容。如果“打开控制台”按钮消失了,则有一种替代方法可以打开调试控制台。单击Figma徽标,跳转到“小部件”类别并显示开发菜单。
该错误可能是因为我们尚未将TypeScript编译为JavaScript。我们可以通过运行npm install
和npm run watch
(或yarn
和yarn watch
)在命令行中执行此操作。这次没有错误!
你可能会遇到的另一个障碍是,每次代码更改时,小部件都无法重新渲染。我们可以使用以下上下文菜单命令轻松强制小部件更新:小部件→重新渲染小部件。
就目前而言,我们小部件的外观仍然与我们的最终目标相差甚远。
那么我们如何从代码中设置Figma组件的样式呢?也许像在React项目中那样使用CSS?错误。对于Figma小部件,所有样式都是通过一套完善的属性来实现的。幸运的是,这些项目的名称与Figma中的对应项目几乎完全相同。
我们将首先配置我们的两个<autolayout></autolayout>
组件。如上图所示,属性名称对它们的目的进行了非常清晰的描述。这使我们能够直接跳转到代码并开始进行一些更改。我不会再次显示整个代码,所以请依靠组件名称来指导你代码片段的位置。
<code><autolayout direction="{" horizontal horizontal:="" horizontalalignitems="{" center name="{" quote padding="{{" spacing="{54}" vertical:="" verticalalignitems="{" start><autolayout direction="{" vertical horizontal:="" horizontalalignitems="{" start name="{" quotecontent padding="{{" spacing="{10}" vertical:="" verticalalignitems="{" end></autolayout></autolayout>;</code>
我们取得了很大的进展!让我们保存并跳回到Figma以查看我们的Widget的外观。还记得新更改后Figma如何自动重新加载Widget吗?
但它还不够。我们还必须向根组件添加背景颜色:
<code><autolayout fill="{" name="{" quote></autolayout></code>
同样,查看你的Figma画板,注意更改如何几乎立即反映到Widget中。
让我们继续本指南并设置<text></text>
组件的样式。
查看Widgets API文档后,再次清楚地看到属性名称与其在Figma应用程序中的对应项几乎相同,如上图所示。我们还将使用上一节中检查Chris网站的值。
<code><text fill="{'#545454'}" fontfamily="{'Lora'}" fontsize="{36}" fontweight="{'normal'}" name="{'QuoteText'}" width="{700}">{quote}</text><text fill="{'#16B6DF'}" fontfamily="{'Raleway'}" fontsize="{26}" fontweight="{'bold'}" name="{'QuoteAuthor'}" textcase="{'upper'}" width="{700}">— {author}</text></code>
我们的小部件目前显示相同的报价,但我们希望随机从整个报价池中提取。我们必须向我们的Widget添加状态,所有React开发人员都知道这是一个变量,它的更改会触发我们组件的重新渲染。
在Figma中,状态是使用useSyncedState
钩子创建的;它几乎就是React的useState
,但它要求程序员指定一个唯一的key
。此要求源于这样一个事实,即Figma必须同步我们Widget的状态,这些状态跨越可能正在查看同一设计画板的所有客户端,但通过不同的计算机。
<code>const { useSyncedState } = widget; function QuotesWidget() { const [quote, setQuote] = useSyncedState("quote-text", ""); const [author, setAuthor] = useSyncedState("quote-author", ""); }</code>
目前,这就是我们需要做的全部更改。在下一节中,我们将弄清楚如何从互联网获取数据。剧透警告:这并不像看起来那么简单。
回想一下Figma让我们选择从启用iFrame的小部件开始。虽然我们没有选择该选项,但我们仍然必须实现其一些功能。让我解释一下为什么我们不能简单地在小部件代码中调用fetch()
。
当你使用小部件时,你正在你的计算机上运行由其他人编写的JavaScript代码。虽然所有小部件都经过Figma员工的彻底审查,但这仍然是一个巨大的安全漏洞,因为我们都知道即使是一行JavaScript也会造成多大的损害。
因此,Figma不能简单地eval()
匿名程序员编写的任何小部件代码。长话短说,团队决定最佳解决方案是在严密保护的沙箱环境中运行第三方代码。正如你可能猜到的那样,浏览器API在这种环境中是不可用的。
但不要担心,Figma对此第二个问题的解决方案是<iframe></iframe>
。我们在文件中编写的任何HTML代码(最好称为ui.html
)都可以访问所有浏览器API。你可能想知道我们如何从Widget触发此代码,但我们稍后会研究这个问题。现在,让我们回到代码中:
<code>// manifest.json { "ui": "ui.html" }</code>
<code> window.onmessage = async (event) => { if (event.data.pluginMessage.type === 'networkRequest') { // TODO: 从服务器获取数据 window.parent.postMessage({ pluginMessage: { // TODO: 返回获取的数据 } }, '*') } } </code>
这是Widget到iFrame通信的通用模板。让我们用它从服务器获取数据:
<code> window.onmessage = async (event) => { if (event.data.pluginMessage.type === 'networkRequest') { // 获取从0到100的随机数 const randomPage = Math.round(Math.random() * 100) // 从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() // 从响应中提取作者姓名和报价内容 const authorName = data[0].title.rendered const quoteContent = data[0].yoast_head_json.og_description window.parent.postMessage({ pluginMessage: { authorName, quoteContent } }, '*') } } </code>
为了保持简单明了,我们省略了错误处理。让我们回到小部件代码中,看看我们如何访问<iframe></iframe>
中定义的函数:
<code>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() } }) }</void></code>
如你所见,我们首先告诉Figma公开访问我们隐藏的<iframe></iframe>
并触发名为“networkRequest”的事件。我们通过检查event.data.pluginMessage.type === 'networkRequest'
来处理ui.html
文件中的此事件,然后将数据发布回小部件。
但是现在还没有发生任何事情……我们仍然没有调用fetchData()
函数。如果我们直接在组件函数中调用它,则控制台中会出现以下错误:
<code>在小部件渲染期间无法使用showUI。</code>
Figma告诉我们不要直接在函数体中调用showUI
……那么我们应该把它放在哪里呢?答案是一个新的钩子和一个新的函数:useEffect
和waitForTask
。如果你是一位React开发人员,你可能已经熟悉useEffect
了,但我们将在这里使用它来在小部件组件挂载时从服务器获取数据。
<code>const { useEffect, waitForTask } = widget; function QuotesWidget() { useEffect(() => { waitForTask(fetchData()); }); }</code>
但这会导致另一个“错误”,我们的Widget将永远使用新的报价不断重新渲染。发生这种情况是因为useEffect
根据定义,每当Widget的状态发生更改时,或者当我们调用fetchData
时,它都会再次触发。虽然有一种技术可以在React中只调用一次useEffect
,但它不适用于Figma的实现。来自Figma文档:
由于小部件的运行方式,useEffect应该处理使用相同状态多次被调用。
幸运的是,我们可以利用一个简单的解决方法,只在组件第一次挂载时调用一次useEffect
,方法是检查状态的值是否仍然为空:
<code>function QuotesWidget() { useEffect(() => { if (!author.length & !quote.length) { waitForTask(fetchData()); } }); }</code>
你可能会遇到一个可怕的“内存访问越界”错误。这在插件和小部件开发中非常常见。只需重新启动Figma,它就不会再出现了。
你可能已经注意到,有时报价文本包含奇怪的字符。
这些是Unicode字符,我们必须在代码中正确格式化它们:
<code> window.onmessage = async (event) => { // ... const quoteContent = decodeEntities(data[0].yoast_head_json.og_description); }; // <https:> 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(/]*>([\\\\S\\\\s]*?)<\\\\/script>/gim, ""); str = str.replace(/<\\\\/?\\\\w(?:[^"'>]|"[^"]*"|'[^']*')*>/gim, ""); element.innerHTML = str; str = element.textContent; element.textContent = ""; } return str; } return decodeHTMLEntities; })(); </https:></code>
瞧,我们的Widget每次添加到设计画板时都会获取一个全新的设计报价。
虽然我们的Widget在实例化时会获取新的报价,但如果我们能够再次执行此过程而无需删除它,则会更实用。本节将简短介绍,因为解决方案非常出色。使用属性菜单,我们可以通过一次调用usePropertyMenu
钩子来向我们的Widget添加交互性。
<code>const { usePropertyMenu } = widget; function QuotesWidget() { usePropertyMenu( [ { itemType: "action", propertyName: "generate", tooltip: "Generate", icon: `<svg fill="none" height="15" viewbox="0 0 22 15" width="22" xmlns="<http://www.w3.org/2000/svg>"></svg>`, }, ], () => fetchData() ); }</code>
使用一个简单的钩子,我们就可以创建一个按钮,当选中我们的Widget时,该按钮会出现在我们的Widget附近。这是我们需要添加的最后一个部分才能完成这个项目。
如果没有人使用它,那么构建Widget就没有多大用处。虽然Figma允许组织启动用于内部使用的私有Widget,但将这些小程序发布到世界各地更为常见。
Figma有一个细致的小部件审查流程,可能需要5到10个工作日。虽然我们一起构建的设计报价小部件已经位于小部件库中,但我仍然会演示它是如何到达那里的。请不要尝试再次发布此小部件,因为这只会导致删除。但是,如果你对其进行了一些重大更改,请继续与社区分享你自己的小部件!
首先单击小部件菜单(Shift I),然后切换到开发选项卡以查看我们的Widget。单击三点菜单并按发布。
Figma会提示你输入有关你的Widget的一些详细信息,例如标题、描述和一些标签。我们还需要一个128×128的图标图像和一个1920×960的横幅图像。
导入所有这些资源后,我们仍然需要Widget的屏幕截图。关闭发布模式(别担心,你不会丢失数据),然后右键单击Widget以显示一个有趣的上下文菜单。找到复制/粘贴为类别并选择复制为PNG。
完成此操作后,让我们回到发布模式并将Widget的屏幕截图粘贴进去:
向下滚动并最终发布你的模式。庆祝!?
Figma会在几天后与你联系,告知你的模式审查状态。如果被拒绝,你将有机会进行更改并再次提交。
我们刚刚从头开始构建了一个Figma小部件!这里有很多内容没有涵盖,例如单击事件、输入表单等等。你可以在这个GitHub存储库中深入了解Widget的完整源代码。
对于那些渴望将他们的Figma技能提升到更高水平的人,我建议探索Widgets社区,并使用你感兴趣的内容作为灵感。继续构建更多的小部件,继续磨练你的React技能,在你意识到之前,你将教我如何做到这一切。
在制作这个Widget时,我不得不参考大量的文档。我认为我会分享我发现最有帮助的内容。
以上是构建互动无花果小部件的详细内容。更多信息请关注PHP中文网其他相关文章!