sketch是什么?
sketch是一款用来制作矢量绘图的软件,矢量绘图也是进行网页,图标以及界面设计的最好方式。但除了矢量编辑的功能之外,sketch同样添加了一些基本的位图工具,比如模糊和色彩校正。是Mac系统才有的软件,可以理解为精简版的PS ,比PS更适合UI设计。该软件的特点是容易理解,上手简单,对于有设计经验的设计师来说,入门门槛很低。
sketch是用Objective-C构建的,是一套原生Objective-C开发的软件。Objective-C类通过Bridge (CocoaScript/mocha) 提供Javascript API调用。
sketch基本结构
sketch只有文字和图形的概念。MSTextLayer文字控件,MSShapePathLayer图形控件。
sketch插件
介绍
sketch插件是按照特定方式管理的一个文件夹,是一个或多个scripts的集合,每个script定义一个或多个commands。sketch插件是以
.sketchplugin
扩展名的文件夹,包含文件和子文件夹。sketch插件主要使用Javascript 语言编写,支持ES6语法,但运行环境既不是浏览器也不是Nodejs,而是Hybrid SketchAPI for macOS Native运行环境。
用JavaScript 编写一个sketch插件。利用ES6,访问macOS框架并使用sketch API,无需学习Objective-C或Swift。所有macOS 框架和内部sketch API都由CocoaScript提供给 JavaScript。
可以做什么?
- 根据复杂规则选择文档中的图层
- 操纵图层属性
- 创建新图层
- 以所有支持的格式导出资源
- 与用户交互(询问输入,显示输出)
- 从外部文件和Web服务获取数据
- 与剪贴板交互
- 操纵sketch的环境(编辑指南,缩放等…)
- 通过调用插件中的菜单选项自动执行现有功能
- 设计规范
- 内容生成
- 透视变化
设计师常用的sketch插件
功能完整的sketch插件包括哪些部分
一个功能完善成熟的sketch插件包括三个部分:
- 工具栏
- webview容器
- 业务数据
通过下面这张图看下:
工具栏
开发工具栏主要使用NSStackView、NSButton、NSImage以及NSFont这几个类,可以类比iOS开发中以UI作为前缀的控件类,NS前缀主要是AppKit以及Foundation的相关类,MS前缀则是skecth的相关类,CA、CF前缀为核心动画库和核心基础类。
webview容器
sketch插件使用webview创建复杂的UI。不用于一般的插件页面,可以使用webview模块加载一个复杂的Web应用,使其与sketch进行交互。通过webview的Bridge桥接传递用户操作到插件侧代码,之后调用sketch API对图层进行操作。
快速创建webview容器有两种:
-
通过CocoaScript创建原生NSPanel
// 原生方式加入webview const panel = NSPanel.alloc().init(); panel.setFrame_display(NSMakeRect(0, 0, panelWidth, panelHeight), true); const wkwebviewConfig = WKWebViewConfiguration.alloc().init() const webView = WKWebView.alloc().initWithFrame_configuration( CGRectMake(0, 0, panelWidth, panelWidth), wkwebviewConfig ) panel.contentView().addSubview(webView); webview.loadFileURL_allowingReadAccessToURL( NSURL.URLWithString(url), NSURL.URLWithString('file:///') )
-
官方的sketch-module-web-view快速创建WebView容器,它提供了丰富的API对窗口的展示样式和行为进行定制,包括Frameless Window、Drag等,同时还封装了WebView与插件层的通信的Bridge,可以轻松在"frontend" (the WebView)和"backend" (the plugin running in Sketch)之间发送消息。
// 使用官方的BrowserWindow import BrowserWindow from "sketch-module-web-view"; const browserWindow = new BrowserWindow(options); const webViewContents = browserWindow.webContents; webViewContents .executeJavaScript(`someGlobalFunctionDefinedInTheWebview(${JSON.stringify(someObject)})`) .then(res => { // do something }) browserWindow.loadURL(require('./index.html'))
这里建议通过官方的方式创建webview,后面会对webview做具体说明,此处不做过多阐述。
业务数据
结合具体业务通过接口获取数据。
插件开发技术方案
sketch插件开发大概有如下三种方式:
- 纯使用CocoaScript脚本进行开发
- 通过Javascript + CocoaScript的混合开发模式
- 通过AppKit + Objective-C进行开发
我们看一张图:
官方推荐方案对于前端会更友好,原生开发方案插件性能会更好,两种都不是很简单,有一定的学习成本,前端同学开发的话建议使用官方推荐方案。即混合开发模式进行sketch插件开发,具体流程参考下图:
插件技术分析
sketch插件系统可以完全访问应用程序的内部结构和macOS中的核心框架。
sketch官方针对sketch Native API封装了一套JS API,目前还未涵盖所有场景,比如UI界面、组件拖放等,若需要更丰富的底层 API需结合CocoaScript进行实现。
sketch插件为什么支持使用JS开发?
因为它使用CocoaScript作为插件的开发语言。它就像是一座桥(Bridge),能让我们在插件中写OC和JS,然后sketch将基础方法进行了封装,实现了一套JS API,这样我们就能使用JS开发Sketch插件了。
Objective-C是什么?
在iOS的开发中使用的是Objective C语言,它是一种通用、高级、面向对象的语言,是C语言的严格超集,广泛用于IOS开发。Objective-C,通常写作ObjC或OC和较少用的Objective C或Obj-C,是扩充C的面向对象编程语言。具体可参考官方文档。
CocoaScript是什么?
是一种bridge,实现JavaScript运行环境到Objective-C运行时的桥接功能,可通过桥接器编写JavaScript外部脚本访问内部sketch API和macOS 框架(AppKit)底层丰富的API功能。可以从CocoaScript访问所有Cocoa和sketch API。
Mocha 实现提供JavaScript运行环境到Objective-C运行时的桥接功能已包含在CocoaScript中。
CocoaScript建立在Apple的JavaScriptCore之上,而JavaScriptCore是为Safari提供支持的JavaScript引擎,使用CocoaScript编写代码实际上就是在编写JavaScript。CocoaScript包括桥接器,可以从 JavaScript访问Apple的Cocoa框架。
CocoaScript中的Mocha实现JS到Objective-C的Bridge,虽然Mocha包含在CocoaScript中,但文档仍保留在原始Github中。因此,在CocoaScript的Readme中看不到任何语法教程。这里一个诀窍是,如果你想了解Mocha将原生的Sketch Objects通过bridge,从Objective-C传递到JavaScript层的属性、类或者实例方法的信息,可以将其打印出来。
语法
借助CocoaScript使用JavaScript调Objective-C语法
- 方法调用用 '.'语法
- Objective-C 属性设置:Getter: object.name();Setter:
object.setName('Sketch')
,object.name='sketch'
- 参数都放在 ‘()’里
- Objective-C 中 ' : '(参数与函数名分割符) 转换为' _ ',最后一个下划线是可选的
- 返回值,JavaScript 统一用
var/const/let
设置类型
// Objective-C
[executeOperation:withObject:error:]
// CocoaScript
executeOperation_withObject_error()
AppKit是什么?
构建sketch的一个主要Apple框架,是为macOS应用程序构建和管理事件驱动的图形用户界面的。
技术实现
通过UI层和逻辑层两部分实现。
- UI层:可以通过webview内嵌实现,可以使用各种前端开发框架,比如React或者Vue等。UI层将用户的操作反馈传递给逻辑层,使其调用sketch API更新Layers。
- 逻辑层:负责调用sketch API,显然不在WebView中,因此需要通过CocoaScript Bridge进行通信,逻辑层将从服务器获取到的数据传递给UI层展示。
具体sketch通信原理可参考下图:
技术难点
- sketch更新速度很快,官方文档简单陈旧,底层API功能薄弱,更深入的话需要了解掌握Objective-C、CocoaScript、Appkit、Sketch-Headers
- 官方提供的API能实现的功能有限,需要结合Appkit、Objective-C IOS开发技术以及桌面开发相关技术,开发技术栈混乱
- 成熟项目一般还未开源,而开源的项目基本上没有特别大的参考价值,需要深入文档结合具体业务项目学习。
sketch插件开发
插件术语
-
Plugin(插件): 一组 Scripts、Commands和其他资源组合在一起作为一个独立单元
-
Plugin Bundle(插件包): 磁盘上的文件夹,其中包含组成 Plugin的文件
-
Action(行为): 用户所做的事情(选择菜单或更改文档)触发Command
-
Command(命令): 一个插件可以定义多个命令; 通常每一个都与不同的菜单或键盘快捷键相关联,并导致执行不同的Handler程序
-
Handler(操作): 执行一些代码来实现Command的函数
-
Script(脚本): 一个 JavaScript文件, 包含一个或多个用来实现一个或多个Commands的Handlers
插件位置
sketch中插件的位置如下图所示:
安装的sketch插件位于下面目录:
/Users/用户名/Library/Application Support/com.bohemiancoding.sketch3/Plugins
我们可以通过sketch内置脚本编辑器编写一个简单的脚本,脚本编辑器提供对 JavaScript API 和内部 API 的完整访问。
开发环境
skpm
skpm(Sketch Plugin Manager)是Sketch提供的用于Plugin创建、Build以及发布的官方工具是一个打包工具,用于快速上手插件开发。
官方提供的打包工具skpm, 它集插件的创建、开发、构建、发布等多项功能于一体,用于快速上手sketch插件开发。它基于webpack,项目根目录下存放webpack.skpm.config.js, 用于工程配置修改。skpm默认采用Webpack作为打包工具。终端运行npm run build
会生成xxx.sketchplugin
目录,该目录就是最终的插件目录。双击该目录,或者将该目录拖拽到Sketch界面上就成功安装插件了。运行 npm run watch
对监听文件变化实时编译,在开发中非常有帮助。
注: 不要使用
npm start
进行开发,它携带的--run
命令会使得构建速度特别慢。在官方未修复该问题前还是不建议大家使用。
创建插件模版
1. cnpm install -g skpm // 全局安装skpm脚手架
2. skpm --help // 查看使用说明以查看所有可用的命令
3. skpm create my-plugin
4. npm run build/yarn build
插件结构
- sketch插件是按照特定方式管理的一个文件夹,包含一个或多个scripts,每个script含有若干扩展sketch用途的命令。
- 插件主要使用Javascript编写,支持ES6语法,但运行环境既不是浏览器也不是Nodejs,而是Hybrid SketchAPI for macOS Native运行环境。
插件目录
.
├── README.md
├── assets
│ └── icon.png
├── my-plugin.sketchplugin
│ └── Contents
│ ├── Resources
│ │ └── icon.png
│ └── Sketch
│ ├── manifest.json
│ ├── my-command.js
│ └── my-command.js.map
├── node_modules
├── package-lock.json
├── package.json
└── src
├── manifest.json
└── my-command.js
// resources文件夹
其中还有一个resources,用于存放资源文件,包括css、js、html.
目录说明:
文件夹 | 描述 |
---|---|
*.sketchplugin | skpm生成过程生成skpm 插件捆绑包。不要编辑此文件夹中的任何文件,任何更改都将用下一个版本覆盖 |
assets | 与插件捆绑的任何资源文件的文件夹。要使用不同的路径或添加文件夹,请修改package.json skpm.assets 设置 |
src | 编写程序目录 |
resources | 用于存放资源文件,包括css、js、html. |
.appcast.xml | manifest.json 有两个比较重要的字段,就是version 和appcast 。version 就是用来表示当前插件的版本的。而appcast 它的值是一个XML的URL地址,该XML里面包含了该插件所有的版本以及该版本对应的下载地址。sketch会将version 对应的版本和appcast 对应的XML进行对比,如果发现有新的版本了,会使用该版本对应的下载地址下载插件,执行在线更新插件。通过skpm publish 命令去发布插件的话,会自动在根目录生成一个.appcast.xml 文件 |
mainifest.json
该文件提供有关插件的信息,例如作者,描述,图标、从何处获取最新更新、定义的命令commands、调用菜单menu项等等。
{
"name": "Mock",
"description": "Mock some content",
"author": "QCM",
"$schema": "https://raw.githubusercontent.com/sketch-hq/SketchAPI/develop/docs/sketch-plugin-manifest-schema.json",
"icon": "icon.png", // 插件图标
"commands": [ // 可以有多个执行命令
{
"name": "Name", // sketch插件命令名称,显示在Sketch Plugin菜单中
"identifier": "my-plugin.my-command-identifier", // 唯一标识,建议用 com.xxxx.xxx 格式,不要过长
"script": "./name.js" // 插件执行脚本,实现命令功能的函数所在的脚本
"shortcut": "", // 命令的快捷键
"handler": "", // 函数名,该函数实现命令的功能。Sketch 在调用该函数时,会传入 context 上下文参数。若未指定 handler,Sketch 会默认调用对应 script 中 onRun 函数
}
],
"menu": {
"title": "Name", // sketch插件名称
"items": [
"my-plugin.my-command-identifier"
]
}
}
commands
表示命令具体的执行操作
- script:实现具体命令功能的函数所在的文件
- handler : 函数名,该函数实现命令的功能。sketch在调用该函数时,会传入
context
上下文参数。若未指定handler,sketch会默认调用对应script中onRun
函数 - shortcut:命令的快捷键
- name:显示在sketch插件菜单中
- identifier : 唯一标识,一般建议用
com.xxxx.xxx
格式,不要过长
总结
- 需要参与webpack打包的脚本文件必须在resources目录下声明,否则不会参与编译
- assets目录需要配置在skpm.assets下,是资源文件夹,如需更改需在package.json中的skpm.assets中设置
- 常用的命令可以定义在scripts中方便直接调用
- src下是需要被webpack打包的脚本文件以及manifest清单文件
- my-plugin.sketchplugin是skpm构建过程生成的插件包
resource
配置中配置的文件会走webpack
的编译打包,并输出到xxx.sketchplugiin/Contents/Resources
目录中- 点击插件菜单后什么都没有发生,此时还需要更改一下配置,即需要安装
html-loader
和@skpm/extract-loader
两款Loader,前者是用来解析处理html代码中可能存在的资源关联情况,后者是将html文件拷贝到xxx.sketchplugin/Contents/Resources
目录并返回对应的file:///
格式的文件路径URL,用来在插件中进行关联。然后在webpack.skpm.config.js中进行配置
插件开发前设置
崩溃保护
// 当 Sketch 运行发生崩溃,它会停用所有插件以避免循环崩溃。对于使用者,每次崩溃重启后手动在菜单栏启用所需插件非常繁琐。因此可以通过如下命令禁用该特性
defaults write com.bohemiancoding.sketch3 disableAutomaticSafeMode true
插件缓存
// 通过配置启用或禁用缓存机制:
defaults write com.bohemiancoding.sketch3 AlwaysReloadScript -bool YES
webview调试
// 如果插件实现方案使用 WebView 做界面,可通过以下配置开启调试功能
defaults write com.bohemiancoding.sketch3 WebKitDeveloperExtras -bool YES
编写一个简单的插件
实现一个demo,将选中图层文本修改为随机数
import sketch from 'sketch'
import Mock from 'mockjs'
const Random = Mock.Random
// 将选中图层文本修改为随机数
export default function(context) {
const doc = sketch.getSelectedDocument(); // 整个文件就是一个document
const layers = doc.selectedLayers.layers; // 获取图层
console.log(layers, layers.length, 111);
layers.forEach(layer => {
const randName = Random.cname();
// if(layer.type === "ShapePath"){ // 图形
// layer.style.fills[0].color = Random.hex(); // 随机修改选中图层颜色
// }
// layer.text = (Math.round(Math.random()*100)).toString(); // 将选中图层文本修改为随机数
layer.text = randName; // 将选中图层文本修改为随机姓名
});
}
看下运行结果:
插件调试
Sketch DevTools,是包含在Sketch中的CLI工具。
发布插件
通过skpm发布插件,如果插件托管在GitHub 上skpm
允许管理发布,并从命令行自动提交到插件列表,实现以下步骤:
// 在插件文件夹中运行skpm
skpm publish
// 查看使用说明
skpm publish --help
// 如果尚未使用gitHub存储库skpm则需要首先使用个人访问令牌从命令行登录,从而授予对存储库范围的访问权限
skpm login
// 更新插件
skpm publish <version>
sketch的主要对象
所有的关于sketch对象的操作,都是通过context来的。context的document对象,oc对应的是MSDocument对象 。可以通过命令打印出来查看
// commands.js
export function importIcons(context) {
log(context, 'context')
}
context上下文包括6个部分,分别是:
- command: MSPluginCommand对象,当前执行命令
- document: MSDocument对象 ,当前文档
- plugin: MSPluginBundle对象,当前的插件bundle,包含当前运行的脚本
- scriptPath:
NSString
当前执行脚本的绝对路径 - scriptURL: 当前执行脚本的绝对路径,跟scriptPath不同的是它是个
NSURL
对象 - selection: 一个
NSArray
对象,包含了当前选择的所有图层。数组中的每一个元素都是MSLayer对象
webview
sketch插件使用webview创建复杂的UI。不用于一般的插件页面,可以使用webview模块加载一个复杂的Web应用,使其与Sketch进行交互,想要在插件中加载网页,需要安装sketch封装好的sketch-module-web-view
插件。
sketch-module-web-view
// 安装
npm install sketch-module-web-view --save-dev
BrowserWindow
创建和控制浏览器窗口
// In the plugin.
const BrowserWindow = require('sketch-module-web-view')
let win = new BrowserWindow({ width: 800, height: 600 })
win.on('closed', () => {
win = null
})
// Load a remote URL
win.loadURL('https://github.com')
// Or load a local HTML file
win.loadURL(require('./index.html'))
webContents
渲染和控制网页。webContents
是一个事件。它负责渲染和控制网页,是BrowserWindow
对象的属性。访问webContents
对象的示例:
const BrowserWindow = require('sketch-module-web-view')
let win = new BrowserWindow({ width: 800, height: 1500 })
win.loadURL('http://github.com')
let contents = win.webContents
console.log(contents)
sketch和webview通信
创建UI时,您可能需要在"前端"(Webview)和"后端"(Sketch 中运行的插件)之间进行通信。
从sketch插件命令向webView发送消息
如果要在sketch中发生某些更改(例如,当选择发生变化)中更新UI,则需要向WebView发送消息。
// webview中
window.someGlobalFunctionDefinedInTheWebview = function (arg) {
console.log(arg)
}
// 插件中
browserWindow.webContents
.executeJavaScript('someGlobalFunctionDefinedInTheWebview("hello")')
.then((res) => {
// do something with the result
})
从webView向sketch插件发送消息
当用户与webView交互时,您可能需要与插件进行通信。您可以通过收听webView将发送的事件来做到这一点
// 插件上
var sketch = require('sketch')
browserWindow.webContents.on('nativeLog', function (s) {
sketch.UI.message(s)
return 'result'
})
// webview中
window.postMessage('nativeLog', 'Called from the webview')
// 传递单个参数
window.postMessage('nativeLog', {
a: b,
})
// 传递多个参数
window.postMessage('nativeLog', 1, 2, 3)
// `window.postMessage` returns a Promis with the array of results from plugin listeners
window.postMessage('nativeLog', 'blabla').then((res) => {
// res === ['result']
})
如果想查看console.log来自webview内部的消息,则可以看到Safari > Develop > {{Your Computer}} > {{Your Plugin}},Safari 浏览器开发工具可用于插件的Javascript代码调试,Developer > name of your machine > Automatically Show Web Inspector for JSContexts,同时启用选项 Automatically Pause Connecting to JSContext。
webivew demo
// sketch插件中代码
import BrowserWindow from 'sketch-module-web-view'
/** 生成webview **/
let win = new BrowserWindow({ width: 800, height: 600 });
win.on('closed', () => {
win = null
});
// Load a localhost URL
const Panel = `http://localhost:8080`;
win.loadURL(Panel);
/** 监听webview的事件:webview->plugin **/
var contents = win.webContents;
//监听webview的事件:webview->plugin
contents.on('fromwebview', function(s) {
console.log(s, 'fromwebview');
});
/** 主动执行webview代码,发送数据 **/
var data = {
name: "json",
type: "come from plugin object data!"
};
//执行webview的代码
const getData = () => {
contents
.executeJavaScript(`someGlobalFunctionDefinedInTheWebview(${JSON.stringify(data)})`)
.then(res => {
console.log(res, "send data success,from plugin to webview!")
})
};
//// 从webView向sketch插件发送消息,插件中
//主动执行webview代码,发送数据
contents.on("did-start-loading", () => getData());
// webview,使用vue框架
mounted(){
window.someGlobalFunctionDefinedInTheWebview = function (arg) {
console.log(arg, 'arg------')
}
// webview中
window.postMessage('fromwebview', {
a: 'webview fromwebview---',
})
}
打印结果如下:
官方API
Action API
一种让插件对应用程序中的事件做出反应的方法。使用它,插件作者可以编写在触发某些操作时执行的代码,例如“打开文档”、“保存”、“添加画板”……
该API用于监听用户操作行为而触发事件,是应用程序中发生的事件,通常是用户交互的结果,操作的名称类似于OpenDocumen(打开文档)、CloseDocument(关闭文档)、Shutdown(关闭插件)、TextChanged(文本变化)、TogglePresentationMode等。您可以告诉您的插件在触发这些操作时运行一些代码,该API未来会被新的 Events API 替代。它代表了 sketch App 内部触发的事件。
如何注册我的插件来“监听”一个动作?
简单:您只需在manifest.json
插件已有的文件中添加一个处理程序,我们将添加一个新的处理程序,用于OpenDocument
操作
"commands" : [
...
+ {
+ "script" : "my-action-listener.js",
+ "name" : "My Action Listener",
+ "handlers" : {
+ "actions": {
+ "OpenDocument": "onOpenDocument"
+ }
+ },
+ "identifier" : "my-action-listener-identifier"
+ }
...
],
告诉我们的插件我们希望onOpenDocument
在文档打开时运行该函数,所以让我们将其添加到my-action-listener.js
export function onOpenDocument(context) {
context.actionContext.document.showMessage('Document Opened')
}
保存所有内容,构建插件,现在每当您在sketch中打开文档时,您都应该看到一个小的Toast 横幅,上面写着“文档已打开”。
Javascript API
是针对Native API的封装,目前还未涵盖所有场景,官方承诺未来将覆盖90%,若需要更丰富的底层API需结合CocoaScript进行实现,JavaScript API涵盖不同的领域并包含不同的包,通过Javascript API可以很方便的对sketch 中 Document
、Artboard
、Group
、Layer
进行相关操作以及导入导出等,不过可能需要考虑兼容性。
- 文档对象模型sketch/dom:访问、修改和创建文档——从颜色到图层和符号的所有内容
- 设置和状态保存sketch/settings:保存图层或文档的自定义数据并存储插件的用户设置
- 用户界面sketch/ui:无需构建即可显示通知并获取用户输入
- 数据供应商sketch/data-supplier:直接在sketch中提供图像或文本数据。数据供应商直接与sketch用户界面集成,使内容在整个设计过程中随时可用
- sketch/async:默认情况下,插件命令的JavaScript上下文在成功执行后会被销毁。对于异步操作,JavaScript API提供了异步作为延长JavaScript上下文生命周期的方法
- export/import从磁盘导入和导出图层:将文件作为图层导入并将对象导出为支持的文件格式
sketch中node模块
- child_process
- console
- events
- fetch
- fs
- os
- process
- querystring
- stream
- String_decoder
- timers
- util
插件开发流程总结
- 首先利用JavaScript或CocoaScript开发操作面板
- 使用NPM或yarn安装所需依赖
- 通过CocoaScript Bridge传递用户操作到插件逻辑侧,通过调用skecth API对文档进行处理
- 使用webpack进行打包
- 通过测试后发布插件更新
具体可以参考下面这张图: