首页 >web前端 >css教程 >构建互动无花果小部件

构建互动无花果小部件

William Shakespeare
William Shakespeare原创
2025-03-11 09:37:10507浏览

Building Interactive Figma Widgets

Figma一直鼓励开发者和设计师之间的协作,并凭借其丰富的社区插件库而蓬勃发展。需要3D元素?有插件!需要抽象的SVG?也有插件!

然而,Figma的设计部分一直相对静态——始终使用不可移动的矩形,通过预定义的用户交互连接在一起。但如果我说你的设计可以突然栩栩如生——可以动画化、交互式,甚至是有状态的,你会怎么想?那么,概念与实现之间还有什么区别呢?

Figma在6月份宣布将推出基于JavaScript的小部件。现在,设计师可以直接在Figma中浏览和实现逻辑驱动的组件!

让我们一起了解一下Widgets API!想知道它是什么以及如何使用它吗?这正是我们将在本文中一起探讨的内容。

Figma小部件开启无限可能

想象一下,你和你的合作伙伴日夜合作设计一个大型餐厅应用程序。你们都在同一个Figma画板上协作;你们共享完全相同的文档,更改实时发生。

当然,你已经知道协作不仅仅包括设计过程:

  • 项目管理,
  • 举办投票,
  • 导入和可视化模拟数据,
  • 也许甚至玩一个多人游戏来放松一下长时间的工作。

只需要一个人管理所有内容并向小组的其他成员发送链接。但是,这效率不高,对吧?

这就是小部件发挥作用的地方。我们可以想象做到所有这些——是的,所有这些——无需离开Figma。

以下是一些你可能希望在Figma中使用小部件的方式:

  • 为Jira和Asana创建任务
  • 在GitHub中创建问题
  • 显示动态数据
  • 录制语音备忘录
  • 创建任务列表
  • 浪费时间玩井字游戏
  • 追踪活动
  • 创建计时器

等等等等。正如你所看到的,已经有大量的小部件可以自由地用于你的文档中。事实上,你可以直接从“小部件”菜单(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教程;你不会后悔将它们添加到你的阅读列表中。

为我们的Widget创建布局

设计完成后,是时候拿出我们的编程手指,开始构建我们小部件的齿轮了。

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>

我认为我们都可以同意现在一切都清楚多了!当我们命名事物时,它们的目的突然变得对我们代码的读者更加明显。

实时预览我们的Widget

Figma在构建小部件时提供了良好的开发体验,包括(但不限于)热重载。使用此功能,我们可以实时编码和预览对小部件的更改。

首先打开小部件菜单(Shift I),切换到开发选项卡,然后单击或拖动你的新小部件到画板。找不到你的小部件?别担心,只需单击三点菜单并导入你的小部件的manifest.json文件即可。是的,这就是让它恢复存在的全部步骤!

等等,你的屏幕底部是否出现错误消息?

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

该错误可能是因为我们尚未将TypeScript编译为JavaScript。我们可以通过运行npm installnpm run watch(或yarnyarn 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添加状态

我们的小部件目前显示相同的报价,但我们希望随机从整个报价池中提取。我们必须向我们的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……那么我们应该把它放在哪里呢?答案是一个新的钩子和一个新的函数:useEffectwaitForTask。如果你是一位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添加属性菜单

虽然我们的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发布到公共

如果没有人使用它,那么构建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时,我不得不参考大量的文档。我认为我会分享我发现最有帮助的内容。

构建更多小部件:

  • 构建小部件的最佳实践
  • 官方Figma小部件示例,包含代码

更深入地学习小部件:

  • 所有小部件钩子
  • 所有小部件组件
  • 小部件在幕后如何运行

小部件与插件

  • 小部件与插件
  • Figma插件简介
  • 插件在幕后如何运行

以上是构建互动无花果小部件的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn