14. 第二个 Figma 插件 - 小专栏
搭建项目脚手架安装 Node.js 和 npm运行项目目录结构构造界面Vue 的基本概念模板语法事件处理组件式开发开始写我们的插件界面SwitcherButton
在上一节我们一起编写了第一个 Figma 主题切换插件,但是它是通过变体属性来切换的,我们需要给每个组件做一亮一暗两个变体,这太麻烦了。今天,我们来优化一下这个插件,通过样式来切换主题,这样我们只需要一亮一暗两套样式就可以了。创建好一亮一暗两套样式后,我们让插件根据样式名自动替换样式,比如将 Light/Text 换成 Dark/Text。
顺便一提,今天我们将使用一个更加现代化的前端框架 Vue 来编写这个插件,因为它是组件式的,后期维护起来会更加容易。
搭建项目脚手架
Vue 并不能直接被浏览器识别运行,所以我们需要一些额外的东西把它转换为浏览器可以识别运行的 HTML、JS 和 CSS,这个东西就是现代化的前端脚手架。如今我们写前端项目早已经不是新建几个 HTML、JS 或 CSS 文件就完事了,而是需要一个完整的脚手架来编译运行代码。
💡 以下操作皆是针对 macOS 的,Windows 用户需要自行查阅相关资料。
下面我将简单介绍一下如何搭建这个项目的脚手架,如果你不明白也没关系,先跟着做就可以了。一般来说前端脚手架会包含下面几个东西:
编译器,负责将代码转换为浏览器可以识别的 HTML、JS 和 CSS,我们使用 Webpack;UI 框架,用于组件化搭建界面,比较流行的就是 React 和 Vue,这里我们选 Vue;package.json 文件,用于描述项目的基本信息和依赖项(包);Node.js 环境,它是可以在操作系统中运行的 JS,它是我们项目运行的基础;包管理器 npm,它上面有很多别人写的包,比如图标、UI 组件,我们可以直接引用而不必自己写。
接下来我们一起搭建这个脚手架。
安装 Node.js 和 npm
进入 Node.js 网站,直接点击下载稳定版,然后跟着步骤安装即可。可以看到,npm 是包含在内的,所以不需要额外安装。
安装完成后,在你的启动台找到终端,并打开它。
在里面输入 node -v 回车,再输入 npm -v 回车,如果看到像我这样输出了版本号,则代表安装成功,可以使用它们了。
其实,Node.js 和 npm 也是软件,只不过它们是没有界面的软件,需要我们在终端中通过命令来使用它们。
运行项目
现在我们可以开始运行插件项目了,我已经为你写好了基础的项目代码,下载这个压缩包并解压即可使用。
theme-swapper.zip
用 VSCode 打开它,项目文件夹,然后打开 VSCode 自带的终端(Terminal)。你可以通过菜单栏中的菜单打开,也可以直接拖拽底部的分界线打开。
在终端中运行 npm install,它的意思是按照 package.json 中的清单安装第三方包。运行完成后,你的项目中会多一个 node_modules 文件夹,这里面就是我们安装的各种第三方包。这里的文件非常多,都是通过 npm 来安装或者删除的,一般情况下不要去管它们。
安装了所需要的依赖,现在在终端输入 npm run dev,回车,就可以成功运行项目啦。此时文件夹下会多一个 dist 目录,这就是编译成的最终插件代码(manifest.json 中也指定了)。
此时,我们可以去 Figma 中通过 manifest.json 添加这个插件,按下 Shift I 之后切换到 Plugins 这一栏,点击右边的加号,点击 Import plugin from manifest 并选中文件夹中的 manifest.json 文件即可。
然后运行插件,效果如下。我们的界面中只有一个简陋的按钮,点击它,Figma 底部会显示一个提示。你可以尝试将 src/ui/App.vue 中的“打招呼”修改为其他词,然后回到 Figma 按下 Cmd Option P,插件重新运行,可以看到按钮中的文字也变了。
也就是说,npm run dev 运行起来后会自动监听文件的变化,并将其实时编译为 dist 文件夹中的插件代码。如果你要退出开发模式,按下快捷键 Control C 即可。
目录结构
现在,让我们来看看整个项目的目录结构。需要我们关心的是 src/ui 和 src/figma 这两个目录下的文件,前者是我们构建插件界面的代码,后者是运行后台任务的代码。其他每个文件的作用我都写在下面啦,现在可以先不关心它们。
theme-swapper├── README.md # 项目说明├── manifest.json # Figma 插件清单├── package.json # 项目清单├── postcss.config.js # 样式框架配置├── src # 项目源代码,我们写的代码都在这里面│ ├── figma # 后台任务代码│ │ ├── code.js # 入口文件,负责接收界面发来的消息并分配任务│ │ └── main.js # 具体的任务执行代码│ └── ui # 前台界面代码│ ├── App.vue # 主界面入口│ ├── helper.js # 辅助函数│ ├── index.html│ ├── style.css│ └── ui.js├── tsconfig.json # ts 项目配置文件└── webpack.config.js # Webpack 配置文件,告诉 webpack 如何编译打包构造界面
接下来我们就可以构造界面啦,我们的界面也很简单,用太阳和月亮两个图标表示亮色和暗色模式,中间有一个箭头可以快速互换。
Vue 的基本概念
在写界面之前,先简单介绍一下 Vue 的基本概念,如果你对 Vue 很熟悉了可以直接看下一小节。一个 Vue 文件分为三部分:负责界面结构的 HTML,负责行为的 JS,和负责样式的 CSS,它们分别由 、 和 标签包围,和传统的前端写法类似。
但是 Vue 有自己的模板语法,以及响应式更新 DOM (文档对象模型,可以简单理解为 HTML 结构,虽然不太准确)结构的基本理念。下面我们通过一个简单的示例讲解一下。
模板语法
不同于传统的前端概念,Vue 是由数据驱动界面的,也就是说你有什么样的数据就有什么样的界面。如果需要更改界面元素状态,不需要直接操作 HTML 元素,而是更新数据。
比如下面的代码,在 中我们默认导出(export default)了一个对象,这个对象中有一个叫做 data 的方法,在这个方法里面又返回了一个对象,它只有一个属性 message,它的值是 Hello。
这个 message 其实就是 Vue 中数据的一个响应式数据,我们可以在 HTML 结构中显示它,只需要用一对大括号就可以了。
你可以打开这个演练场,尝试修改 message 的值,就会发现右边显示的文字会跟着改变,这就是我们所说的数据驱动界面变化。
事件处理
我们也可以给元素添加事件,比如当用户点击按钮时,改变显示的消息。如下所示的代码,我们新增了一个按钮,并且在 中增加了一个 methods 的属性,在 methods 内我们写了一个叫做 sayHi 的方法,它把 message 的值变为 Hi。最后,我们给按钮添加点击事件 @click=”sayHi”,HTML 元素上以 @ 开头的属性都表示事件。
需要注意的是,在 sayHi 方法内我们要通过 this.message 才能访问到 message 属性,不像在 HTML 中可以直接写 data 中的属性名(message)或 methods 中的方法名(sayHi)。
现在,你可以再去这个演练场中尝试一下,点击按钮消息会变成 Hi。
组件式开发
Vue 是组件式的,每个 vue 文件都可以看做一个组件,我们可以互相引用并组合它们。在演练场中,我们新建一个文件 Say.vue 并将刚才写的代码复制进去,在 App.vue 中写下如下代码,它的运行结果如右边所示。
我来解释一下,比如我们在 App.vue 中我们先通过 import Say from “./Say” 先将这个组件引入;然后在 中注册这个组件,也就是增加一个 components 属性,它是一个对象,可以在里面罗列出你要在这里使用的组件,在我们的例子里就是 Say;最后,我们就可以在 HTML 结构中使用它了。在使用时组件时直接用尖括号把它括起来,就像一个普通的 HTML 标签,不过一般自定义组件以大写字母开头,以便和常规的 HTML 标签作为区分。
开始写我们的插件界面
使用 Vue 的组件式开发,我们可以把一个界面分解成很多小块,然后像拼积木一样组合它们。对于我们的插件来说,它的界面很简单,我们可以把上面的亮暗色模式切换部分写成一个组件,把下面的按钮写成一个组件。
Switcher
在 src/ui/components 文件夹下新建一个 Switcher.vue 文件,它的 HTML 结构如下。
现在,我们需要在里面加三个图标,分别是太阳、月亮和箭头。在 npmjs.com 上面有很多图标库可供我们使用,在这里我决定使用 vue-feather-icons。要使用第三方库需要先安装它,在 VSCode 中打开终端执行 npm install vue-feather-icons 就可以安装了。
💡 需要注意的是,执行该命令一定要确定处于这个项目的根目录下。如果不确定,可以输入 ls 回车,看看终端输出的文件目录是不是这些项目文件。
如果一切正常,可以看到 package.json 中会多一行 vue-feather-icons,表明它已经被安装到 node_modules 文件夹中了。
现在,我们就可以在文件中引用图标了。就像前面介绍的一样,先使用 import 引用图标,再注册图标,最后在 HTML 结构中使用图标。
import { MoonIcon, ArrowRightIcon, SunIcon } from ‘vue-feather-icons’export default { components: { MoonIcon, ArrowRightIcon, SunIcon }};
现在打开 src/ui/App.vue,在里面引用一下 Switcher 组件。
import Switcher from “./components/Switcher”;import ‘./style.css’export default { components: { Switcher }} .main { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; gap: 12px; }
现在你的文件和代码应该像下面这样:
按下 Cmd Option P 运行插件,可以看到它显示了三个图标,说明我们的图标引用成功。
好了,现在该给 Switcher 组件添加样式了。我们通过 class 给元素添加样式,CSS 可以按照 HTML 一致的结构嵌套书写。
import { MoonIcon, ArrowRightIcon, SunIcon } from ‘vue-feather-icons’export default { components: { MoonIcon, ArrowRightIcon, SunIcon }}; .switcher { display: flex; align-items: center; gap: 8px; .mode { width: 40px; height: 40px; padding: 8px; border-radius: 20px; color: #333; background-color: #EEE; &.dark { color: #EEE; background-color: #333; } } .arrow { padding: 4px; border-radius: 12px; color: #999; cursor: pointer; transition: background-color 0.24s; &:hover { background-color: #EEE; } } }
在 Figma 中运行插件,它和我们的设计稿一样了。
现在我们来添加一些 JS 代码,实现点击中间的箭头可以互换左右两侧图标,就像在购买机票的 App 中快速互换出发地和目的地一样。因为这三个图标的布局方式是 flex,而 flex 布局有一个 [flex-direction](https://css-tricks.com/snippets/css/a-guide-to-flexbox/#aa-flex-direction) 属性可以让里面的元素反向排列,所以我们可以加一个 flex-direction: row-reverse 来让实现快速顺序互换。
那我们的 JS 逻辑就是:点击箭头,给 switcher 容器添加或移除 reversed 类。在 Vue 中,可以动态绑定元素的 class。我们要做的就是在这个组件的 data 中增加一个响应式数据 isReversed,通过它来控制这个容器元素(带有 switcher 类的 div)是否带有 reversed 类。
isReversed 是一个布尔值,点击中间的箭头时它会由 true 变为 false,由 false 变为 true,这样就可以控制容器是否带有 reversed 这个类。
事件处理这个就不在多说了,我们看一下 :class=”{ reverded: isReversed }” 这一行代码。以冒号开头的属性表示数据绑定,它后面的值是动态计算的。比如 class=”switcher” 表示这个容器带有 switcher 这个类,siwtcher 就是一个静态的字符串,而 :class=”{ reverded: isReversed }” 冒号中是一个对象,它表示当 isReversed 为 true 时,这个元素就会带有 reversed 这个类,否则就不带有。更多关于动态绑定元素 class 的内容可以看 Vue 的官方文档。
Button
按钮组件的结构比较简单,我们着重介绍一下插槽(Slots)和事件外传。我们先写出组件的结构和样式,代码如下:
切换 .button { position: relative; width: 100px; height: 32px; padding: 8px 16px; border-radius: 8px; text-align: center; line-height: 16px; font-size: 12px; border: 1px solid #0035FF; color: white; background: #0035FF; text-decoration: none; &:disabled { opacity: 0.32; cursor: not-allowed; } }
然后在 App.vue 中引入它,并且我们给它加上一个点击事件。
切换主题 import Button from “./components/Button”;import Switcher from “./components/Switcher”;import ‘./style.css’export default { components: { Button, Switcher }, methods: { swap () { console.log(‘切换’) } }}
运行插件,点击按钮,你会发现在控制台中没有发生任何事情,说明点击事件没有生效。
给自定义组件直接添加事件是不行的,我们需要在组件内部把需要外传的事件传出去才行。我们可以使用 this.$emit 将事件暴露出去。
“`html
搭建项目脚手架安装 Node.js 和 npm运行项目目录结构构造界面Vue 的基本概念模板语法事件处理组件式开发开始写我们的插件界面SwitcherButton
在上一节我们一起编写了第一个 Figma 主题切换插件,但是它是通过变体属性来切换的,我们需要给每个组件做一亮一暗两个变体,这太麻烦了。今天,我们来优化一下这个插件,通过样式来切换主题,这样我们只需要一亮一暗两套样式就可以了。创建好一亮一暗两套样式后,我们让插件根据样式名自动替换样式,比如将 Light/Text 换成 Dark/Text。
顺便一提,今天我们将使用一个更加现代化的前端框架 Vue 来编写这个插件,因为它是组件式的,后期维护起来会更加容易。
搭建项目脚手架
Vue 并不能直接被浏览器识别运行,所以我们需要一些额外的东西把它转换为浏览器可以识别运行的 HTML、JS 和 CSS,这个东西就是现代化的前端脚手架。如今我们写前端项目早已经不是新建几个 HTML、JS 或 CSS 文件就完事了,而是需要一个完整的脚手架来编译运行代码。
💡 以下操作皆是针对 macOS 的,Windows 用户需要自行查阅相关资料。
下面我将简单介绍一下如何搭建这个项目的脚手架,如果你不明白也没关系,先跟着做就可以了。一般来说前端脚手架会包含下面几个东西:
编译器,负责将代码转换为浏览器可以识别的 HTML、JS 和 CSS,我们使用 Webpack;UI 框架,用于组件化搭建界面,比较流行的就是 React 和 Vue,这里我们选 Vue;package.json 文件,用于描述项目的基本信息和依赖项(包);Node.js 环境,它是可以在操作系统中运行的 JS,它是我们项目运行的基础;包管理器 npm,它上面有很多别人写的包,比如图标、UI 组件,我们可以直接引用而不必自己写。
接下来我们一起搭建这个脚手架。
安装 Node.js 和 npm
进入 Node.js 网站,直接点击下载稳定版,然后跟着步骤安装即可。可以看到,npm 是包含在内的,所以不需要额外安装。
安装完成后,在你的启动台找到终端,并打开它。
在里面输入 node -v 回车,再输入 npm -v 回车,如果看到像我这样输出了版本号,则代表安装成功,可以使用它们了。
其实,Node.js 和 npm 也是软件,只不过它们是没有界面的软件,需要我们在终端中通过命令来使用它们。
运行项目
现在我们可以开始运行插件项目了,我已经为你写好了基础的项目代码,下载这个压缩包并解压即可使用。
theme-swapper.zip
用 VSCode 打开它,项目文件夹,然后打开 VSCode 自带的终端(Terminal)。你可以通过菜单栏中的菜单打开,也可以直接拖拽底部的分界线打开。
在终端中运行 npm install,它的意思是按照 package.json 中的清单安装第三方包。运行完成后,你的项目中会多一个 node_modules 文件夹,这里面就是我们安装的各种第三方包。这里的文件非常多,都是通过 npm 来安装或者删除的,一般情况下不要去管它们。
安装了所需要的依赖,现在在终端输入 npm run dev,回车,就可以成功运行项目啦。此时文件夹下会多一个 dist 目录,这就是编译成的最终插件代码(manifest.json 中也指定了)。
此时,我们可以去 Figma 中通过 manifest.json 添加这个插件,按下 Shift I 之后切换到 Plugins 这一栏,点击右边的加号,点击 Import plugin from manifest 并选中文件夹中的 manifest.json 文件即可。
然后运行插件,效果如下。我们的界面中只有一个简陋的按钮,点击它,Figma 底部会显示一个提示。你可以尝试将 src/ui/App.vue 中的“打招呼”修改为其他词,然后回到 Figma 按下 Cmd Option P,插件重新运行,可以看到按钮中的文字也变了。
也就是说,npm run dev 运行起来后会自动监听文件的变化,并将其实时编译为 dist 文件夹中的插件代码。如果你要退出开发模式,按下快捷键 Control C 即可。
目录结构
现在,让我们来看看整个项目的目录结构。需要我们关心的是 src/ui 和 src/figma 这两个目录下的文件,前者是我们构建插件界面的代码,后者是运行后台任务的代码。其他每个文件的作用我都写在下面啦,现在可以先不关心它们。
theme-swapper├── README.md # 项目说明├── manifest.json # Figma 插件清单├── package.json # 项目清单├── postcss.config.js # 样式框架配置├── src # 项目源代码,我们写的代码都在这里面│ ├── figma # 后台任务代码│ │ ├── code.js # 入口文件,负责接收界面发来的消息并分配任务│ │ └── main.js # 具体的任务执行代码│ └── ui # 前台界面代码│ ├── App.vue # 主界面入口│ ├── helper.js # 辅助函数│ ├── index.html│ ├── style.css│ └── ui.js├── tsconfig.json # ts 项目配置文件└── webpack.config.js # Webpack 配置文件,告诉 webpack 如何编译打包构造界面
接下来我们就可以构造界面啦,我们的界面也很简单,用太阳和月亮两个图标表示亮色和暗色模式,中间有一个箭头可以快速互换。
Vue 的基本概念
在写界面之前,先简单介绍一下 Vue 的基本概念,如果你对 Vue 很熟悉了可以直接看下一小节。一个 Vue 文件分为三部分:负责界面结构的 HTML,负责行为的 JS,和负责样式的 CSS,它们分别由 、 和 标签包围,和传统的前端写法类似。
但是 Vue 有自己的模板语法,以及响应式更新 DOM (文档对象模型,可以简单理解为 HTML 结构,虽然不太准确)结构的基本理念。下面我们通过一个简单的示例讲解一下。
模板语法
不同于传统的前端概念,Vue 是由数据驱动界面的,也就是说你有什么样的数据就有什么样的界面。如果需要更改界面元素状态,不需要直接操作 HTML 元素,而是更新数据。
比如下面的代码,在 中我们默认导出(export default)了一个对象,这个对象中有一个叫做 data 的方法,在这个方法里面又返回了一个对象,它只有一个属性 message,它的值是 Hello。
这个 message 其实就是 Vue 中数据的一个响应式数据,我们可以在 HTML 结构中显示它,只需要用一对大括号就可以了。
你可以打开这个演练场,尝试修改 message 的值,就会发现右边显示的文字会跟着改变,这就是我们所说的数据驱动界面变化。
事件处理
我们也可以给元素添加事件,比如当用户点击按钮时,改变显示的消息。如下所示的代码,我们新增了一个按钮,并且在 中增加了一个 methods 的属性,在 methods 内我们写了一个叫做 sayHi 的方法,它把 message 的值变为 Hi。最后,我们给按钮添加点击事件 @click=”sayHi”,HTML 元素上以 @ 开头的属性都表示事件。
需要注意的是,在 sayHi 方法内我们要通过 this.message 才能访问到 message 属性,不像在 HTML 中可以直接写 data 中的属性名(message)或 methods 中的方法名(sayHi)。
现在,你可以再去这个演练场中尝试一下,点击按钮消息会变成 Hi。
组件式开发
Vue 是组件式的,每个 vue 文件都可以看做一个组件,我们可以互相引用并组合它们。在演练场中,我们新建一个文件 Say.vue 并将刚才写的代码复制进去,在 App.vue 中写下如下代码,它的运行结果如右边所示。
我来解释一下,比如我们在 App.vue 中我们先通过 import Say from “./Say” 先将这个组件引入;然后在 中注册这个组件,也就是增加一个 components 属性,它是一个对象,可以在里面罗列出你要在这里使用的组件,在我们的例子里就是 Say;最后,我们就可以在 HTML 结构中使用它了。在使用时组件时直接用尖括号把它括起来,就像一个普通的 HTML 标签,不过一般自定义组件以大写字母开头,以便和常规的 HTML 标签作为区分。
开始写我们的插件界面
使用 Vue 的组件式开发,我们可以把一个界面分解成很多小块,然后像拼积木一样组合它们。对于我们的插件来说,它的界面很简单,我们可以把上面的亮暗色模式切换部分写成一个组件,把下面的按钮写成一个组件。
Switcher
在 src/ui/components 文件夹下新建一个 Switcher.vue 文件,它的 HTML 结构如下。
现在,我们需要在里面加三个图标,分别是太阳、月亮和箭头。在 npmjs.com 上面有很多图标库可供我们使用,在这里我决定使用 vue-feather-icons。要使用第三方库需要先安装它,在 VSCode 中打开终端执行 npm install vue-feather-icons 就可以安装了。
💡 需要注意的是,执行该命令一定要确定处于这个项目的根目录下。如果不确定,可以输入 ls 回车,看看终端输出的文件目录是不是这些项目文件。
如果一切正常,可以看到 package.json 中会多一行 vue-feather-icons,表明它已经被安装到 node_modules 文件夹中了。
现在,我们就可以在文件中引用图标了。就像前面介绍的一样,先使用 import 引用图标,再注册图标,最后在 HTML 结构中使用图标。
import { MoonIcon, ArrowRightIcon, SunIcon } from ‘vue-feather-icons’export default { components: { MoonIcon, ArrowRightIcon, SunIcon }};
现在打开 src/ui/App.vue,在里面引用一下 Switcher 组件。
import Switcher from “./components/Switcher”;import ‘./style.css’export default { components: { Switcher }} .main { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; gap: 12px; }
现在你的文件和代码应该像下面这样:
按下 Cmd Option P 运行插件,可以看到它显示了三个图标,说明我们的图标引用成功。
好了,现在该给 Switcher 组件添加样式了。我们通过 class 给元素添加样式,CSS 可以按照 HTML 一致的结构嵌套书写。
import { MoonIcon, ArrowRightIcon, SunIcon } from ‘vue-feather-icons’export default { components: { MoonIcon, ArrowRightIcon, SunIcon }}; .switcher { display: flex; align-items: center; gap: 8px; .mode { width: 40px; height: 40px; padding: 8px; border-radius: 20px; color: #333; background-color: #EEE; &.dark { color: #EEE; background-color: #333; } } .arrow { padding: 4px; border-radius: 12px; color: #999; cursor: pointer; transition: background-color 0.24s; &:hover { background-color: #EEE; } } }
在 Figma 中运行插件,它和我们的设计稿一样了。
现在我们来添加一些 JS 代码,实现点击中间的箭头可以互换左右两侧图标,就像在购买机票的 App 中快速互换出发地和目的地一样。因为这三个图标的布局方式是 flex,而 flex 布局有一个 [flex-direction](https://css-tricks.com/snippets/css/a-guide-to-flexbox/#aa-flex-direction) 属性可以让里面的元素反向排列,所以我们可以加一个 flex-direction: row-reverse 来让实现快速顺序互换。
那我们的 JS 逻辑就是:点击箭头,给 switcher 容器添加或移除 reversed 类。在 Vue 中,可以动态绑定元素的 class。我们要做的就是在这个组件的 data 中增加一个响应式数据 isReversed,通过它来控制这个容器元素(带有 switcher 类的 div)是否带有 reversed 类。
isReversed 是一个布尔值,点击中间的箭头时它会由 true 变为 false,由 false 变为 true,这样就可以控制容器是否带有 reversed 这个类。
事件处理这个就不在多说了,我们看一下 :class=”{ reverded: isReversed }” 这一行代码。以冒号开头的属性表示数据绑定,它后面的值是动态计算的。比如 class=”switcher” 表示这个容器带有 switcher 这个类,siwtcher 就是一个静态的字符串,而 :class=”{ reverded: isReversed }” 冒号中是一个对象,它表示当 isReversed 为 true 时,这个元素就会带有 reversed 这个类,否则就不带有。更多关于动态绑定元素 class 的内容可以看 Vue 的官方文档。
Button
按钮组件的结构比较简单,我们着重介绍一下插槽(Slots)和事件外传。我们先写出组件的结构和样式,代码如下:
切换 .button { position: relative; width: 100px; height: 32px; padding: 8px 16px; border-radius: 8px; text-align: center; line-height: 16px; font-size: 12px; border: 1px solid #0035FF; color: white; background: #0035FF; text-decoration: none; &:disabled { opacity: 0.32; cursor: not-allowed; } }
然后在 App.vue 中引入它,并且我们给它加上一个点击事件。
切换主题 import Button from “./components/Button”;import Switcher from “./components/Switcher”;import ‘./style.css’export default { components: { Button, Switcher }, methods: { swap () { console.log(‘切换’) } }}
运行插件,点击按钮,你会发现在控制台中没有发生任何事情,说明点击事件没有生效。
给自定义组件直接添加事件是不行的,我们需要在组件内部把需要外传的事件传出去才行。我们可以使用 this.$emit 将事件暴露出去。
“`html
搭建项目脚手架安装 Node.js 和 npm运行项目目录结构构造界面Vue 的基本概念模板语法事件处理组件式开发开始写我们的插件界面SwitcherButton
在上一节我们一起编写了第一个 Figma 主题切换插件,但是它是通过变体属性来切换的,我们需要给每个组件做一亮一暗两个变体,这太麻烦了。今天,我们来优化一下这个插件,通过样式来切换主题,这样我们只需要一亮一暗两套样式就可以了。创建好一亮一暗两套样式后,我们让插件根据样式名自动替换样式,比如将 Light/Text 换成 Dark/Text。
顺便一提,今天我们将使用一个更加现代化的前端框架 Vue 来编写这个插件,因为它是组件式的,后期维护起来会更加容易。
搭建项目脚手架
Vue 并不能直接被浏览器识别运行,所以我们需要一些额外的东西把它转换为浏览器可以识别运行的 HTML、JS 和 CSS,这个东西就是现代化的前端脚手架。如今我们写前端项目早已经不是新建几个 HTML、JS 或 CSS 文件就完事了,而是需要一个完整的脚手架来编译运行代码。
💡 以下操作皆是针对 macOS 的,Windows 用户需要自行查阅相关资料。
下面我将简单介绍一下如何搭建这个项目的脚手架,如果你不明白也没关系,先跟着做就可以了。一般来说前端脚手架会包含下面几个东西:
编译器,负责将代码转换为浏览器可以识别的 HTML、JS 和 CSS,我们使用 Webpack;UI 框架,用于组件化搭建界面,比较流行的就是 React 和 Vue,这里我们选 Vue;package.json 文件,用于描述项目的基本信息和依赖项(包);Node.js 环境,它是可以在操作系统中运行的 JS,它是我们项目运行的基础;包管理器 npm,它上面有很多别人写的包,比如图标、UI 组件,我们可以直接引用而不必自己写。
接下来我们一起搭建这个脚手架。
安装 Node.js 和 npm
进入 Node.js 网站,直接点击下载稳定版,然后跟着步骤安装即可。可以看到,npm 是包含在内的,所以不需要额外安装。
安装完成后,在你的启动台找到终端,并打开它。
在里面输入 node -v 回车,再输入 npm -v 回车,如果看到像我这样输出了版本号,则代表安装成功,可以使用它们了。
其实,Node.js 和 npm 也是软件,只不过它们是没有界面的软件,需要我们在终端中通过命令来使用它们。
运行项目
现在我们可以开始运行插件项目了,我已经为你写好了基础的项目代码,下载这个压缩包并解压即可使用。
theme-swapper.zip
用 VSCode 打开它,项目文件夹,然后打开 VSCode 自带的终端(Terminal)。你可以通过菜单栏中的菜单打开,也可以直接拖拽底部的分界线打开。
在终端中运行 npm install,它的意思是按照 package.json 中的清单安装第三方包。运行完成后,你的项目中会多一个 node_modules 文件夹,这里面就是我们安装的各种第三方包。这里的文件非常多,都是通过 npm 来安装或者删除的,一般情况下不要去管它们。
安装了所需要的依赖,现在在终端输入 npm run dev,回车,就可以成功运行项目啦。此时文件夹下会多一个 dist 目录,这就是编译成的最终插件代码(manifest.json 中也指定了)。
此时,我们可以去 Figma 中通过 manifest.json 添加这个插件,按下 Shift I 之后切换到 Plugins 这一栏,点击右边的加号,点击 Import plugin from manifest 并选中文件夹中的 manifest.json 文件即可。
然后运行插件,效果如下。我们的界面中只有一个简陋的按钮,点击它,Figma 底部会显示一个提示。你可以尝试将 src/ui/App.vue 中的“打招呼”修改为其他词,然后回到 Figma 按下 Cmd Option P,插件重新运行,可以看到按钮中的文字也变了。
也就是说,npm run dev 运行起来后会自动监听文件的变化,并将其实时编译为 dist 文件夹中的插件代码。如果你要退出开发模式,按下快捷键 Control C 即可。
目录结构
现在,让我们来看看整个项目的目录结构。需要我们关心的是 src/ui 和 src/figma 这两个目录下的文件,前者是我们构建插件界面的代码,后者是运行后台任务的代码。其他每个文件的作用我都写在下面啦,现在可以先不关心它们。
theme-swapper├── README.md # 项目说明├── manifest.json # Figma 插件清单├── package.json # 项目清单├── postcss.config.js # 样式框架配置├── src # 项目源代码,我们写的代码都在这里面│ ├── figma # 后台任务代码│ │ ├── code.js # 入口文件,负责接收界面发来的消息并分配任务│ │ └── main.js # 具体的任务执行代码│ └── ui # 前台界面代码│ ├── App.vue # 主界面入口│ ├── helper.js # 辅助函数│ ├── index.html│ ├── style.css│ └── ui.js├── tsconfig.json # ts 项目配置文件└── webpack.config.js # Webpack 配置文件,告诉 webpack 如何编译打包构造界面
接下来我们就可以构造界面啦,我们的界面也很简单,用太阳和月亮两个图标表示亮色和暗色模式,中间有一个箭头可以快速互换。
Vue 的基本概念
在写界面之前,先简单介绍一下 Vue 的基本概念,如果你对 Vue 很熟悉了可以直接看下一小节。一个 Vue 文件分为三部分:负责界面结构的 HTML,负责行为的 JS,和负责样式的 CSS,它们分别由 、 和 标签包围,和传统的前端写法类似。
但是 Vue 有自己的模板语法,以及响应式更新 DOM (文档对象模型,可以简单理解为 HTML 结构,虽然不太准确)结构的基本理念。下面我们通过一个简单的示例讲解一下。
模板语法
不同于传统的前端概念,Vue 是由数据驱动界面的,也就是说你有什么样的数据就有什么样的界面。如果需要更改界面元素状态,不需要直接操作 HTML 元素,而是更新数据。
比如下面的代码,在 中我们默认导出(export default)了一个对象,这个对象中有一个叫做 data 的方法,在这个方法里面又返回了一个对象,它只有一个属性 message,它的值是 Hello。
这个 message 其实就是 Vue 中数据的一个响应式数据,我们可以在 HTML 结构中显示它,只需要用一对大括号就可以了。
你可以打开这个演练场,尝试修改 message 的值,就会发现右边显示的文字会跟着改变,这就是我们所说的数据驱动界面变化。
事件处理
我们也可以给元素添加事件,比如当用户点击按钮时,改变显示的消息。如下所示的代码,我们新增了一个按钮,并且在 中增加了一个 methods 的属性,在 methods 内我们写了一个叫做 sayHi 的方法,它把 message 的值变为 Hi。最后,我们给按钮添加点击事件 @click=”sayHi”,HTML 元素上以 @ 开头的属性都表示事件。
需要注意的是,在 sayHi 方法内我们要通过 this.message 才能访问到 message 属性,不像在 HTML 中可以直接写 data 中的属性名(message)或 methods 中的方法名(sayHi)。
现在,你可以再去这个演练场中尝试一下,点击按钮消息会变成 Hi。
组件式开发
Vue 是组件式的,每个 vue 文件都可以看做一个组件,我们可以互相引用并组合它们。在演练场中,我们新建一个文件 Say.vue 并将刚才写的代码复制进去,在 App.vue 中写下如下代码,它的运行结果如右边所示。
我来解释一下,比如我们在 App.vue 中我们先通过 import Say from “./Say” 先将这个组件引入;然后在 中注册这个组件,也就是增加一个 components 属性,它是一个对象,可以在里面罗列出你要在这里使用的组件,在我们的例子里就是 Say;最后,我们就可以在 HTML 结构中使用它了。在使用时组件时直接用尖括号把它括起来,就像一个普通的 HTML 标签,不过一般自定义组件以大写字母开头,以便和常规的 HTML 标签作为区分。
开始写我们的插件界面
使用 Vue 的组件式开发,我们可以把一个界面分解成很多小块,然后像拼积木一样组合它们。对于我们的插件来说,它的界面很简单,我们可以把上面的亮暗色模式切换部分写成一个组件,把下面的按钮写成一个组件。
Switcher
在 src/ui/components 文件夹下新建一个 Switcher.vue 文件,它的 HTML 结构如下。
现在,我们需要在里面加三个图标,分别是太阳、月亮和箭头。在 npmjs.com 上面有很多图标库可供我们使用,在这里我决定使用 vue-feather-icons。要使用第三方库需要先安装它,在 VSCode 中打开终端执行 npm install vue-feather-icons 就可以安装了。
💡 需要注意的是,执行该命令一定要确定处于这个项目的根目录下。如果不确定,可以输入 ls 回车,看看终端输出的文件目录是不是这些项目文件。
如果一切正常,可以看到 package.json 中会多一行 vue-feather-icons,表明它已经被安装到 node_modules 文件夹中了。
现在,我们就可以在文件中引用图标了。就像前面介绍的一样,先使用 import 引用图标,再注册图标,最后在 HTML 结构中使用图标。
import { MoonIcon, ArrowRightIcon, SunIcon } from ‘vue-feather-icons’export default { components: { MoonIcon, ArrowRightIcon, SunIcon }};
现在打开 src/ui/App.vue,在里面引用一下 Switcher 组件。
import Switcher from “./components/Switcher”;import ‘./style.css’export default { components: { Switcher }} .main { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; gap: 12px; }
现在你的文件和代码应该像下面这样:
按下 Cmd Option P 运行插件,可以看到它显示了三个图标,说明我们的图标引用成功。
好了,现在该给 Switcher 组件添加样式了。我们通过 class 给元素添加样式,CSS 可以按照 HTML 一致的结构嵌套书写。
import { MoonIcon, ArrowRightIcon, SunIcon } from ‘vue-feather-icons’export default { components: { MoonIcon, ArrowRightIcon, SunIcon }}; .switcher { display: flex; align-items: center; gap: 8px; .mode { width: 40px; height: 40px; padding: 8px; border-radius: 20px; color: #333; background-color: #EEE; &.dark { color: #EEE; background-color: #333; } } .arrow { padding: 4px; border-radius: 12px; color: #999; cursor: pointer; transition: background-color 0.24s; &:hover { background-color: #EEE; } } }
在 Figma 中运行插件,它和我们的设计稿一样了。
现在我们来添加一些 JS 代码,实现点击中间的箭头可以互换左右两侧图标,就像在购买机票的 App 中快速互换出发地和目的地一样。因为这三个图标的布局方式是 flex,而 flex 布局有一个 [flex-direction](https://css-tricks.com/snippets/css/a-guide-to-flexbox/#aa-flex-direction) 属性可以让里面的元素反向排列,所以我们可以加一个 flex-direction: row-reverse 来让实现快速顺序互换。
那我们的 JS 逻辑就是:点击箭头,给 switcher 容器添加或移除 reversed 类。在 Vue 中,可以动态绑定元素的 class。我们要做的就是在这个组件的 data 中增加一个响应式数据 isReversed,通过它来控制这个容器元素(带有 switcher 类的 div)是否带有 reversed 类。
isReversed 是一个布尔值,点击中间的箭头时它会由 true 变为 false,由 false 变为 true,这样就可以控制容器是否带有 reversed 这个类。
事件处理这个就不在多说了,我们看一下 :class=”{ reverded: isReversed }” 这一行代码。以冒号开头的属性表示数据绑定,它后面的值是动态计算的。比如 class=”switcher” 表示这个容器带有 switcher 这个类,siwtcher 就是一个静态的字符串,而 :class=”{ reverded: isReversed }” 冒号中是一个对象,它表示当 isReversed 为 true 时,这个元素就会带有 reversed 这个类,否则就不带有。更多关于动态绑定元素 class 的内容可以看 Vue 的官方文档。
Button
按钮组件的结构比较简单,我们着重介绍一下插槽(Slots)和事件外传。我们先写出组件的结构和样式,代码如下:
切换 .button { position: relative; width: 100px; height: 32px; padding: 8px 16px; border-radius: 8px; text-align: center; line-height: 16px; font-size: 12px; border: 1px solid #0035FF; color: white; background: #0035FF; text-decoration: none; &:disabled { opacity: 0.32; cursor: not-allowed; } }
然后在 App.vue 中引入它,并且我们给它加上一个点击事件。
切换主题 import Button from “./components/Button”;import Switcher from “./components/Switcher”;import ‘./style.css’export default { components: { Button, Switcher }, methods: { swap () { console.log(‘切换’) } }}
运行插件,点击按钮,你会发现在控制台中没有发生任何事情,说明点击事件没有生效。
给自定义组件直接添加事件是不行的,我们需要在组件内部把需要外传的事件传出去才行。我们可以使用 this.$emit 将事件暴露出去。
“`html