前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >手把手教你写一个sketch插件

手把手教你写一个sketch插件

原创
作者头像
陆陆
修改2019-12-06 10:56:44
2K1
修改2019-12-06 10:56:44
举报
文章被收录于专栏:雷雨到来之时雷雨到来之时

序言

sketch是一款轻量、易用的矢量设计工具。尽管如此,在使用过程中有些功能还是未能满足,亦或者在设计或开发流程中有些工作还略显繁琐,所幸sketch有提供API供我们开发一些插件来解决使用过程中遇到的问题。本着能用工具解决就用工具解决的懒癌患者原则,我们这个课程就来学习如何开发sketch 插件。

sketch插件的本质就是一些脚本的集合,且官方提供的是JavaScript API,因此这个课程希望你有JavaScript的基础。整个课程通过手把手教你开发一个sketch插件,来达到学习和熟悉开发流程、常用API的目的。

sketch插件结构

那么sketch插件究竟包含了什么东西呢,我们来看看。右键点击-查看包内容

可以看到以下目录结构

  • Resources

用来放icon图片等静态资源。

  • manifest.json

这是一个json文件,它包含了名称,描述和作者姓名等信息。定义了插件的命令名称、在sketch显示的菜单选项等。

  • identifier

指定插件的唯一标识符。Sketch在内部使用此字符串来跟踪插件,为其存储设置等。

  • commands

是一个数组,定义用户执行的一个或多个命令。定义的每项命令具有以下属性:

1.name

命令的显示名称。此值在插件菜单中使用。

2.identifier

一个字符串,指定命令的唯一标识符。这用于将命令映射到操作,而不论命令名称如何更改。

3.shortcut

一个可选的字符串,用于指定该命令的快捷键,例如:ctrl t,cmd t,ctrl shift t。

4.script

插件包的Sketch文件夹中用于实现此命令的脚本的相对路径。

5.handler

此命令调用的函数。如果未指定,则一般直接运行export的函数

  • menu

嵌套地定义插件在sketch展示的菜单列表。

1.title

一个字符串,为子菜单的标题。

2.items

包含次级子菜单项目的数组,它可以包含两种类型:

(1)命令标识符的字符串;

(2)数组(相当于次次级子菜单)。

开发一个插件

接下来我们尝试做一个批量切图的插件。主要的交互功能是这样的。选择需要导出切片的图层,点击使用插件,弹出导出图片参数设置,输入宽高、选择图片类型和倍数,点击确定,选择保存路径,导出图片。批量切图的交互流程大致是这样。

1.选择需要切图的图层

2.使用插件

3.输入需要批量导出切片的尺寸以及倍数

4.导出

这个插件的完整代码 https://github.com/lulu0729/sketch-slice-plugin

初始化项目

为了初始化我们的整个插件项目,将使用到skpm——一个用于创建,构建和发布插件的管理器。

首先安装skpm

命令行输入以下命令

代码语言:txt
复制
npm install -g skpm

然后创建一个插件,命令行输入

代码语言:txt
复制
skpm create sketch-slice-plugin --template=skpm/with-webview

这个表示是要创建一个带webview模板的插件,

我们会有一个输入导出图片参数用的弹窗,这个弹窗就是用webview实现。

创建完毕后,得到这样一个目录

目录结构

assets

我们可能需要放一些图片或HTML等资源文件,可以在放在assets文件夹里,这样在构建插件的时候,会一并打包进去。

而最后生成插件的目录是这样的:

assets里的资源文件将放在Resources里,因此在编写时要以路径"../Resources/xx"来引入资源。

src

主要就是js脚本文件集合以及前文提到的mainifest.json。

查看log

官方提到有3种方法可以查看log。

1.使用 sketch-dev-tools(https://github.com/skpm/sketch-dev-tools)。这个是一个sketch插件,然而它会监听用户的所有操作,所以十分耗费性能。我在使用时候经常闪退,所以暂时不推荐

2.用mac 自带的Console.app,输入你的插件名称,可以筛选出对应的log。

3.打开~/Library/Logs/com.bohemiancoding.sketch3/Plugin Output.log这个文件,可以查看到完整的log。

调试

命令行输入

代码语言:txt
复制
defaults write ~/Library/Preferences/com.bohemiancoding.sketch3.plist AlwaysReloadScript -bool YES 

这样在修改保存脚本代码的时候都会自动重新安装加载插件。

Hello World

在./src目录下创建脚本my-command.js,

写入

代码语言:txt
复制
const UI = require("sketch/ui");//引入sketch自带的toast模块 

export default function(context){ 

UI.message("Hello World"); 

} 

在./src/manifest.json中写入运行插件时运行my-command.js具体如下

代码语言:txt
复制
{ 

"compatibleVersion": 3, 

"bundleVersion": 1, 

"commands": [ 

{ 

"name": "my-command", 

"identifier": "sketch-slice-plugin.my-command-identifier", 

"script": "./my-command.js", 

"handlers": { 

"run": "onRun", 

"actions": { 

"Shutdown": "onShutdown" 

} 

} 

} 

], 

"menu": { 

"title": "sketch-slice-plugin", 

"items": [ 

"sketch-slice-plugin.my-command-identifier" 

] 

} 

命令行输入

代码语言:txt
复制
defaults write ~/Library/Preferences/com.bohemiancoding.sketch3.plist AlwaysReloadScript -bool YES 

打开sketch,选择插件运行,就可以看到一个'Hello World'的toast。

接下来我们正式进入插件的开发。

构建一个webview操作界面

首先开始写插件的操作界面。如果用object-c来写可能会比较复杂,skpm提供了一个模块sketch-module-web-view,用于创建一个webview,以便更方便地写出操作界面。

安装

命令行输入

代码语言:txt
复制
npm install -S sketch-module-web-view 

创建webview

在./resources目录下打开webview.html,编写一个HTML的操作界面,具体代码如下

代码语言:txt
复制
<!DOCTYPE html> 

<html> 

<head> 

<meta charset="utf-8" /> 

<title>sketch-slice-plugin</title> 

<link rel="stylesheet" href="./style.css" /> 

</head> 

<body> 

sketch-slice-plugin 

<div> 

<div class="box"> 

<h1>请输入切片大小</h1> 

<label>宽度:</label> 

<input type="number" placeholder="单位:px" id="inputWidth" /> 

<br /> 

<label>高度:</label> 

<input type="number" placeholder="单位:px" id="inputHeight" /> 

<br /> 

<label class="attr-name">导出图片类型</label> 

<br /> 

<input 

id="png" 

class="formats" 

type="checkbox" 

checked="value" 

value="png" 

/> 

<label>png</label> 

<br /> 

<input 

id="jpg" 

class="formats" 

type="checkbox" 

checked="value" 

value="jpg" 

/> 

<label>jpg</label> 

<input 

id="svg" 

class="formats" 

type="checkbox" 

checked="value" 

value="svg" 

/> 

<label>svg</label> 

<br /> 

<input 

id="scale1x" 

class="scales" 

type="checkbox" 

checked="value" 

value="1" 

/> 

<label>@1x</label> 

<br /> 

<input 

id="scale2x" 

class="scales" 

type="checkbox" 

checked="value" 

value="2" 

/> 

<label>@2x</label> 

<input 

id="scale3x" 

class="scales" 

type="checkbox" 

checked="value" 

value="3" 

/> 

<label>@3x</label> 

<br /> 

<button id="btnExport">导出切片</button> 

</div> 

</div> 

<div id="answer"></div> 

<!-- notice the "../" here. It's because webview.js will be compiled in a different folder --> 

<script src="../webview.js"></script> 

</body> 

</html> 

在同目录下的style.css写一下样式~

代码语言:txt
复制
/\* some default styles to make the view more native like \*/ 

html { 

box-sizing: border-box; 

background: transparent; 

/\* Prevent the page to be scrollable \*/ 

overflow: hidden; 

/\* Force the default cursor, even on text \*/ 

cursor: default; 

} 

\*, \*:before, \*:after { 

box-sizing: inherit; 

margin: 0; 

padding: 0; 

position: relative; 

/\* Prevent the content from being selectionable \*/ 

-webkit-user-select: none; 

user-select: none; 

} 

input, textarea { 

-webkit-user-select: auto; 

user-select: auto; 

} 

body { 

background-color: #fff; 

} 

一个基本的操作界面就写好了。接下来要在sketch中打开一个webview,以便能访问到我们写的html界面。

在my-command.js中写入

代码语言:txt
复制
/\*my-command.js\*/ 

//引入依赖的webview模块 

const BrowserWindow = require("sketch-module-web-view"); 

const { getWebview } = require("sketch-module-web-view/remote"); 

const webviewIdentifier = "sketch-slice-plugin.webview"; 

export default function(context) { 

const options = { 

identifier: webviewIdentifier, 

width: 400, 

height: 380 

};//设置webview视窗大小等参数 

const browserWindow = new BrowserWindow(options); 

const webContents = browserWindow.webContents; 

//加载html 

browserWindow.loadURL(require("../resources/webview.html")); 

} 

运行一下插件,可以看到webview界面。

webview和plugin间的通信

有了webview界面,需要让webview与plugin进行交互通信。

而browserWindow提供了API。我们先来学习下。

plugin调用webview的函数

在webview定义了一个函数

代码语言:txt
复制
window.updatePreview = function (text) { 

console.log(text); 

}; 

在plugin调用这个函数并传入参数

代码语言:txt
复制
let text = "send a message"; 

win.webContents.executeJavaScript( 

"updatePreview('" + text + "')" 

); 
从WebView向插件传递信息

webview

代码语言:txt
复制
pluginCall('nativeLog', unit , value); 

plugin

代码语言:txt
复制
win.webContents.on('nativeLog', (key, value) => { 

Settings.setSettingForKey(key,value); 

}); 

获取选择的图层

sketch提供了API让我们能获取到选择的图层,以便对图层进行一些处理

在my-command.js写入

代码语言:txt
复制
// we will also need a function to transform an NSArray into a proper JavaScript array 

// the `util` package contains such a function so let's just use it. 

const { toArray } = require("util"); 

export default function(context) { 

... 

// 通过context.selection获取到选择的图层,并用toArray函数转成JavaScript数组,以便后续我们进行处理 

const selection = toArray(context.selection); 

... 

} 

获取输入的参数

我们要获取到在webview输入的切片参数。

在./resources目录下新建webview.js,写入代码

代码语言:txt
复制
//webview.js 

// call the plugin from the webview 

//监听button点击事件 

document.getElementById("btnExport").addEventListener("click", () => { 

//获取输入的宽高值、倍数、输出图片类型 

let width = document.getElementById("inputWidth").value || "", 

height = document.getElementById("inputHeight").value || "", 

scales = document.getElementsByClassName("scales"); 

formats = document.getElementsByClassName("formats"); 

let scalesArray = [], 

formatsArray = []; 

// 对倍数、图片类型的参数处理成数组 

Array.prototype.filter.call(scales, scale => { 

if (scale.checked) { 

scalesArray.push(scale.value); 

} 

}); 

Array.prototype.filter.call(formats, format => { 

if (format.checked) { 

formatsArray.push(format.value); 

} 

}); 

let formatsStr = Array.prototype.join.call(formatsArray); 

// 向plugin通信 

window.postMessage("getOptions", { 

width: width, 

height: height, 

formats: formatsStr, 

scales: scalesArray 

}); 

}); 

plugin

代码语言:txt
复制
//my-commond.js 

export default function(context) { 

... 

// add a handler for a call from web content's javascript 

webContents.on("getOptions", options => { 

//这样就能拿到webview传来的options搞事情了 

handlerSelection(selection, options); 

}); 

... 

} 

处理图层handlerSelection

下面来写整个插件的核心部分handlerSelection,用于接收选择图层selection和参数options后处理图层并导出想要的切片。

我们先在./src目录下新建selection.js

代码语言:txt
复制
//selection.js 

module.exports = { 

handlerSelection(selection, options) { 

let slices = []; //初始化切片数组 

let opt = handlerExportOpt(options); //处理导出切片的参数,后续会详解如何实现这个函数 

selection.forEach(layer => { 

let slice = handlerSlice(layer, options);//生成切片,,后续会详解如何实现这个函数 

//slice push进数组 

slices.push(slice); 

}); 

//用sketch自带的api 将切片批量导出 

sketch.export(slices, opt); 

} 

}; 

并在my-commond.js导入这个模块

代码语言:txt
复制
//my-commond.js 

... 

const { handlerSelection } = require("./selection.js"); 

... 

处理导出切片的参数

我们导出切片的路径需要打开一个对话框来进行选择:

代码语言:txt
复制
//selection.js 

/\*\* 导出路径的panel \*/ 

function setSavePanel() { 

//使用object-c的api,打开一个保存路径的对话框 

let savePanel = NSSavePanel.savePanel(); 

//设置对话框的标题等参数 

savePanel.setTitle("Export"); 

savePanel.setNameFieldLabel("Export to"); 

savePanel.setShowsTagField(false); 

savePanel.setCanCreateDirectories(true); 

if (savePanel.runModal() != NSOKButton) { 

//如果点击了取消按钮,则返回false 

log("cancel save"); 

return false; 

} else { 

//否则返回选择的路径 

return savePanel.URL().path(); 

} 

} 

接下来讲解下怎么处理导出切片的参数。

代码语言:txt
复制
//selection.js 

/\*\* 处理导出切片的参数 \*/ 

function handlerExportOpt(options) { 

let url = setSavePanel();//获取导出切片的保存路径 

//返回所需的参数对象 

return { 

output: url,//导出路径 

formats: options.formats || "png",//导出图片的类型 

scales: options.scales[0] || [1],//导出图片的倍数 

"group-contents-only": true//去除背景 

}; 

} 

总结一下整个步骤就是,获取导出路径和处理好切片的参数,并将这个对象返回。

生成切片

回顾前面的代码,在处理切片参数后,对选择的图层依次生成一个切片,并将切片push进slices数组中。

代码语言:txt
复制
//selection.js 

handlerSelection(selection, options) { 

... 

selection.forEach(layer => { 

let slice = handlerSlice(layer, options); 

//slice push进数组 

slices.push(slice); 

}); 

... 

} 

而生成切片的函数实现如下:

代码语言:txt
复制
//selection.js 

/\* 生成切片 \*/ 

function handlerSlice(layer, options) { 

//根据宽高计算并新建切片 

//新建一个包含图层的group,用于包含图层和切片 

let group = MSLayerGroup.groupWithLayer(layer); 

let groupName = toJSString(layer.name()); //获取图层的名称 

group.setName(groupName); //将group名设置为图层的名称 

let slice = MSSliceLayer.sliceLayerFromLayer(layer); //用sketch提供的object-c API创建一个切片 

let layerFrame = layer.frame(); 

let sliceFrame = slice.frame(); 

//切片设置为输入的宽高,若未输入宽高,则按照图层的实际大小设置切片宽高 

sliceFrame.setWidth(options.width || layerFrame.width()); 

sliceFrame.setHeight(options.height || layerFrame.height()); 

//计算切片与图层的位置差 

let sliceX = Math.floor((layerFrame.width() - sliceFrame.width()) / 2); 

let sliceY = Math.floor((layerFrame.height() - sliceFrame.height()) / 2); 

// let sliceXFloor = Math.floor(sliceX); 

// let sliceYFloor = Math.floor(sliceY); 

//按照位置差移动切片位置,使图层居中于切片中心 

sliceFrame.setX(sliceX); 

sliceFrame.setY(sliceY); 

//返回这个切片 

return slice; 

} 

批量导出切片

最后一步是用sketch提供的API批量导出切片

代码语言:txt
复制
module.exports = { 

handlerSelection(selection, options) { 

... 

//slice批量导出 

sketch.export(slices, opt); 

} 

}; 

总结

至此,一个批量截图的插件便完成了。

sketch 官方还提供了很多其他API,可以在https://developer.sketch.com/reference/api/ 查看,但这只是官方放出的javascript API,可能还不能完全满足开发需求,其他一些开发经验可以在这个社区交流学习:

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 序言
  • sketch插件结构
  • 开发一个插件
    • 初始化项目
      • 目录结构
      • 查看log
      • 调试
    • Hello World
      • 构建一个webview操作界面
        • 安装
        • 创建webview
        • webview和plugin间的通信
      • 获取选择的图层
        • 获取输入的参数
          • 处理图层handlerSelection
            • 处理导出切片的参数
              • 生成切片
                • 批量导出切片
                • 总结
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档