在 Figma 客户端中按照如上操作即可完成插件的初始化。除了默认的三个例子之外,官方也有一个示例插件的仓库,也可以参考。
https://github.com/figma/plugin-samples├── README.md ├── code.js ├── code.ts ├── manifest.json ├── package-lock.json ├── package.json ├── tsconfig.json └── ui.htmlmanifest.json
{ "name": "figma-placeimg", "id": "1117699210834344763", "api": "1.0.0", "main": "code.js", "editorType": [ "figma" ], "ui": "ui.html" }其中重点的是 main 和 ui 两个字段:
ui: 指定插件的 UI 代码文件,该文件中的代码会运行在 iframe 中。实际上,UI 代码文件的内容会作为字符串传递给 figma 内置变量 __html__,在沙箱内可以通过 figma.showUI(__html__) 创建 iframe。
<link rel="stylesheet" href="https://unpkg.com/figma-plugin-ds@1.0.1/dist/figma-plugin-ds.css"> <style> .content { display: flex; } .icon--swap { animation: rotate 1s linear infinite; } .hide { display: none; } @keyframes rotate { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } </style> <div id="app"> <div class="field"> <label for="" class="label">请输入图片尺寸:</label> <div class="content" style="padding-left: 10px;"> <div class="input"> <input type="input" class="input__field" placeholder="宽" name="width"> </div> <div class="label" style="flex:0;">×</div> <div class="input"> <input type="input" class="input__field" placeholder="高" name="height"> </div> </div> </div> <div class="field"> <label for="" class="label">请选择图片分类:</label> <div class="content"> <div class="radio"> <input id="radioButton1" type="radio" class="radio__button" value="any" name="category" checked> <label for="radioButton1" class="radio__label">全部</label> </div> <div class="radio"> <input id="radioButton2" type="radio" class="radio__button" value="animals" name="category" > <label for="radioButton2" class="radio__label">动物</label> </div> <div class="radio"> <input id="radioButton3" type="radio" class="radio__button" value="arch" name="category" > <label for="radioButton3" class="radio__label">建筑</label> </div> <div class="radio"> <input id="radioButton4" type="radio" class="radio__button" value="nature" name="category" > <label for="radioButton4" class="radio__label">自然</label> </div> <div class="radio"> <input id="radioButton5" type="radio" class="radio__button" value="people" name="category" > <label for="radioButton5" class="radio__label">人物</label> </div> <div class="radio"> <input id="radioButton6" type="radio" class="radio__button" value="tech" name="category" > <label for="radioButton6" class="radio__label">科技</label> </div> </div> </div> <div class="field"> <label for="" class="label">请选择图片滤镜:</label> <div class="content"> <div class="radio"> <input id="radioButton7" type="radio" class="radio__button" value="none" name="filter" checked> <label for="radioButton7" class="radio__label">正常</label> </div> <div class="radio"> <input id="radioButton8" type="radio" class="radio__button" value="grayscale" name="filter" > <label for="radioButton8" class="radio__label">黑白照</label> </div> <div class="radio"> <input id="radioButton9" type="radio" class="radio__button" value="sepia" name="filter" > <label for="radioButton9" class="radio__label">老照片</label> </div> </div> </div> <div class="field" style="padding:0 10px;"> <div id="create" class="icon-button" style="width: 100%;"> <div class="icon icon--image"></div> <div class="type type--small type--medium type--inverse">插入</div> </div> <div class="icon-button loading hide" style="width: 100%;"> <div class="icon icon--swap"></div> </div> </div> </div>
<!--堆代码 duidaima.com --> <script> async function loadImage(url) { const resp = await fetch('http://localhost:3000/' + url); const buffer = await resp.arrayBuffer(); return new Uint8Array(buffer); } document.getElementById('create').onclick = async (e) => { const width = parseInt(document.querySelector('input[name="width"]').value); const height = parseInt(document.querySelector('input[name="height"]').value); const category = document.querySelector('input[name="category"]:checked').value; const filter = document.querySelector('input[name="filter"]:checked').value; const loading = document.querySelector('.icon-button.loading'); e.target.classList.add('hide'); loading.classList.remove('hide'); const imgBytes = await loadImage(`https://placeimg.com/${width}/${height}/${category}/${filter}`); parent.postMessage({ pluginMessage: { type: 'insert', bytes: imgBytes, width: width, height: height } }, '*'); loading.classList.add('hide'); e.target.classList.remove('hide'); } </script>由于 UI 线程是一个纯 Web 环境,当我们使用 XMLHttpRequest 或者 fetch 发送请求的时候,肯定会碰到跨域的问题。按照文档 https://www.figma.com/plugin-docs/making-network-requests/ 提供的解决办法,我们只能依靠服务端加层代理来解决。
// code.ts function fetch(url, options) { const html = `<script> fetch(${url}, ${JSON.stringify(options)}).then(resp => resp.json()).then(resp => parent.sendMessage({ pluginMessage: { type: 'networkRequest', data: resp } }); </script>`; return new Promise(resolve => { figma.ui.on('message', msg => msg.type === 'networkRequest' && resolve(msg.data) ); figma.ui.show(html, { visible: false }); }); }插入图片
figma.ui.onmessage = msg => { if (msg.type === 'insert') { const rectNode = figma.createRectangle(); const image = figma.createImage(msg.bytes); rectNode.name = 'Image'; rectNode.resize(msg.width, msg.height); rectNode.fills = [{ imageHash: image.hash, scaleMode: 'FILL', scalingFactor: 0.5, type: 'IMAGE' }]; figma.currentPage.appendChild(rectNode); figma.currentPage.selection = [rectNode]; figma.viewport.scrollAndZoomIntoView([rectNode]); } figma.closePlugin(); };除了需要显示的调用 figma.ui.show 来展示 UI 之外,在执行完插件后需要显示的调用 figma.closePlugin() 告知 Figma 进行关闭插件操作。
// code.ts function initSelectionState() { if (figma.currentPage.selection.length === 1 && figma.currentPage.selection[0].type === 'RECTANGLE') { const rectNode = figma.currentPage.selection[0]; figma.ui.postMessage({ type: 'update', width: rectNode.width, height: rectNode.height }); } } figma.on('selectionchange', initSelectionState); initSelectionState();通过在主线程中监听 selectionchange 事件,我们能实时获取到当前选中的元素。我们将尺寸信息发送到 UI 线程后让其填充到输入框中称为默认值。
window.onmessage = function(e) { if (e.data.pluginMessage.type === 'update') { document.querySelector('input[name="width"]').value = e.data.pluginMessage.width; document.querySelector('input[name="height"]').value = e.data.pluginMessage.height; } }最后再插入的时候,我们也需要判断如果有选中矩形的话则优先使用选中的矩形,而不是新增矩形。
let rectNode: RectangleNode; if (figma.currentPage.selection.length === 1 && figma.currentPage.selection[0].type === 'RECTANGLE') { rectNode = figma.currentPage.selection[0]; } else { rectNode = figma.createRectangle(); } // const rectNode = figma.createRectangle();
// ui.html parent.postMessage({ pluginMessage: { type: 'MAIN_CODE', code: 'console.log(figma)' } }); // code.ts figma.ui.onmessage = (msg) => { msg.type === 'MAIN_CODE' && eval(msg.code); }