自己做一个 Sketch 插件自动生成 Scss 代码

2,429 阅读2分钟

最近做 h5 页面重构的时候,遇上几个让我很难受的问题:

  1. 为了设配,h5 通常会使用 rem 单位,几乎所有尺寸单位,都需要手动写一遍,px2rem($pixel);
  2. 对于颜色,阴影这些属性,每次都得手动加 #, 或者 rgba;
  3. 对于一些不常用的属性,得手动查文档,比如 box-shadow linear-gradient;
  4. 对于一些不是很明显的样式,经常会出现错漏,比如 text-shadow;

这些都是小问题,然而每次 h5 开发都会遇上,影响时间,影响效率,影响心情。有没有办法可以优化呢?围绕着这些问题,我开始尝试 Sketch 插件开发。

开发环境

开发过程需要经常打印日志调试,可以通过 document.showMessage 直接答应在 Sketch 页面内,还可以通过 Console 软件,设置进程,库都为 Sketch,来查看日志打印,然后代码里面通过 log 或者 print方法写入log

console

console

Sketch 默认不会自动更新脚本代码,如果你希望每次保存Sketch自动更新,可以执行以下代码。

defaults write ~/Library/Preferences/com.bohemiancoding.sketch3.plist AlwaysReloadScript -bool YES

插件简介

Sketch 插件都是 *.sketchplugin 的形式,其实就是一个文件夹,通过右键显示包内容,可以看到最普通的内部结构式是这样的:
代码结构

代码结构

manifest.json 用来声明插件配置信息,commands 定义所有可执行命令,每条 command 有唯一标志符,identifier,menu 定义插件菜单,通过 identifier 关联到执行命令。handlers.actions 下面可以配置需要监听的事件,Sketch 会在事件被触发时执行相应的事件方法,可以在 Action List 这里看到所有的 Action 或者装一个 Action-Logger 插件可以看到事件什么时候触发。

{
  "name" : "first-plugin",
  "identifier" : "com.w3ctrain",
  "version" : "1.0",
  "description" : "first plugin",
  "author" : "w3ctrain",
  "commands" : [
    {
      "script" : "plugin.js",
      "handlers": {
        "run": "onRun",
        "actions": {
          "SelectionChanged.finish": "onSelectionChanged"
        }
      },
      "name": "Say Hello World",
      "identifier" : "com.w3ctrain.HelloWorld"
    }
  ],
  "menu" : {
    "items" : [
      "com.w3ctrain.HelloWorld"
    ],
    "title" : "first plugin"
  }
}

plugin.js 对应 manifest.json script 字段的执行脚本,这样的脚本可以有很多个,也可以随意命名。

var onRun = function (context) {
  context.document.showMessage("Hello World")
}
var onSelectionChanged = function (context) {
  var selection = context.actionContext.document.selectedLayers().layers();
  if (selection.length >= 1) {
    context.actionContext.document.showMessage(`${selection.length} layers selected`)
  } else {
    context.actionContext.document.showMessage("nothing selected")
  }
}

把插件放到 ~/Library/Application Support/com.bohemiancoding.sketch3/Plugins/ 目录下,就可以在 Plugins 菜单中执行。

Hello World

Hello World

Sketch 插件是使用 CocoaScript 编写,放心不是什么新语言,其实就是 JavaScript + ObjectC 宿主环境,也就是说你可以写 JS 来调用 ObjectC 的方法和对象。

比如

[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString: @"https://baidu.com"]]]

用 CocoaScript 写起来时这样的:

webView.loadRequest(NSURLRequest.requestWithURL(NSURL. URLWithString("https://baidu.com"))

实际上我并不关心 ObjectC 代码,我只是个前端,只要能完成创建一个 Panel 加载我的网页就行。下一个要解决的问题是 Sketch 事件触发的时候如何通知 webview 更新,其实很简单,在之前的文章中就有遇到过,ObjectC 是可以直接调用 JS 代码的。

Sketch 通知 webview

webView.js 暴露全局方法

function updatePreview () {
  // 拿到 css attributes
}

plugin.js,通过 evaluateWebScript 调用全局方法

function onSelectionChanged {
  var windowObject = webView.windowScriptObject();
   var selection = context.actionContext.document.selectedLayers().layers();
   if (selection.length >= 1) {
     	windowObject.evaluateWebScript(`updatePreview(${ayer.CSSAttributes().join('')})`);
   } else {
     	windowObject.evaluateWebScript("updatePreview(' ')");
  }
}

由于 Sketch 提供的 cssAttributes 属性并不是我们最终想要的,所以需要对这堆内容进行补充替换截取处理。

其他处理

补充文本对齐和尺寸

let layer = selection[0];
var frame = layer.frame();
var attributes = layer.CSSAttributes();
if (layer.class() == "MSTextLayer") {
if (!layer.lineHeight()) {
  attributes.addObject(`line-height: 1.4;`);
}
for (const key in TEXT_ALIGNMENT) {
  if (layer.textAlignment() === TEXT_ALIGNMENT[key]) {
    attributes.addObject(`text-align: ${TEXT_ALIGNMENT_MAP[key]};`);
  }
}
}
attributes.addObject('\n');
attributes.addObject('/* Size: */');
attributes.addObject(`width: ${frame.width()}px;`);
attributes.addObject(`height: ${frame.height()}px;`);

过滤掉不常用的属性。

function removeBlackListAttributes (codeString) {
  var blackList = ['font-family', 'letter-spacing']
  for (let i = 0; i < blackList.length; i ++) {
    codeString = codeString.replace(new RegExp(`${blackList[i]}(.*)\n`, 'ig', ''), '');
  }
  return codeString
}

进行单位替换

  var unitsFunc = (match, val) => {
    let unit = unitInput.value
    let miniPixel = miniPixelInput.value
    if (val <= parseInt(miniPixel)) {
      return `${val}px`
    }
    return `${unit}(${val})`
  }
  let result = codeString.replace(/([0-9]+)px/ig, unitsFunc)
}

一顿操作之后看到的效果是这样的:

效果

效果

实现起来比预期的开发时间要少很多,因为很多东西不需要自己处理,然而整体效果是非常显著的,文章一开头提到的通点都解决了,复制粘贴即可,动动手写几行代码就能解决自己的需求,开心。

具体的代码可以看这里

相关文章

如需转载,请注明出处: w3ctrain.com/2017/12/07/…

我叫周晓楷

我现在是一名前端开发工程师,在编程的路上我还是个菜鸟,w3ctrain 是我用来记录学习和成长的地方。