';

这个工具已经完成了:

design-enzyme.com

 


Photoshop CC 2017 终于发布了,一直有些在意 CC 2017 会不会有些革命性的更新,因为之前一直在断断续续的开发一个工具,用来增强 Photoshop 在 UI 设计工作中的“功能”,由于担心 CC 2017 会不会有大的更新和变化,所以开发一直很“谨慎”,而新的 Photoshop 发布了,新的更新并没有太大的变化,而 Adobe XD 也更偏向于交互原型的制作(而且 windows 版还没不出来),对于基本的设计功能并没有太大的增强,所以我新开发的这个工具还是有些意义的。

Photoshop 的不足

前几代 Photoshop 在都对图形设计、和 UI 设计有许多针对性的增强,但是比起 Sketch 来,还是显得有很多不足,尤其 Sketch 各种插件也越来越丰富,而为 Photoshop  开发的的 UI 设计相关插件却很少,Photoshop 好像已经被 UI 设计界抛弃了….

Image title

很期待 Photoshop 什么时候会有像 Sketch 的对象缩放这样程度的更新

 

不过 Photoshop 还是有许多 Sketch 无法替代的地方,比如位图相关功能、和更高的“自由度”,更重要的是 Photoshop 是 Windows 上唯一的选择。 所以我一直在考虑开发一款 Photoshop 的扩展用来增强 Photoshop 在 UI 设计方面的功能。

这个工具的核心理念是“便捷操作”、“控制力”、“自动化”

增强的功能

便捷操作

Photoshop 虽然有个“图形与网页”的工作区预设,但是工具面板本身并没有任何为 “图形与网页” 设计的优化,过去没有比较也就凑合用了,但和 Sketch  比起来,实在是显得操作繁琐。

比如在  UI 设计中修改图层的阴影、描边、填充本来是最常用的功能了,在 Sketch  中选中对象后就可以在属性面板里直接设置了, 但在 Photoshop 中却得打开窗口设置,

Photoshop 中设置一个阴影

 

Sketch 中阴影、填充、描边都直接可以在属性面板中设置

解决这个问题,实际很简单,为 UI 设计做一个单独的属性面板就好了,功能上和 Sketch  差不多,把图层样式的常用参数提升到面板中调节,然而至今没有这样的面板,所以我就来做了:

当然这个扩展不只是做一个类似 Sketch 的属性面板,更重要的是增加对数据的控制力。

控制力

所谓控制力就是对编辑对象各属性的控制能力,能够掌握所有图层的各种属性数据,更随心所欲的操作这些数据,在数据之上赋予逻辑实现各种功能和自动化。

比如在 Photoshop 中是不能把“色相\饱和度”工具用在图层的填充、描边、样式颜色(比如阴影的颜色)中的,而在拥有了操作图层所有属性的能力后,这就成为了可能:

改变图层属性中填充颜色、描边颜色、投影颜色的色相

 

表达式

图层的属性设置可以使用表达式,这个表达式可以是数学公式(这在 Sketch 已经有了 ):

变量系统

Photoshop 有一个变量扩展 Ditto,不过它支持的属性很有限, 而我的新扩展可以在图层几乎所有属性上使用变量(包括图层样式的各种参数),并且可以配合表达式使用,还可以动态的把图层属性值赋值给变量,变量值也可以是一个表达式,除此之外还可以使用预置的动态获取数据的变量(被称为增强子),比如获取把图层宽度设置为父级图层组宽度的一半 – 10: $parent.W /2 - 10

变量可以使用英文也可以使用中文,预置的动态变量也可以用不同语言: ¥父.W  $隣.3.W 

 

 

 

有这样的变量与表达式系统意味着,可以做到数据响应、数据绑定、图层控件化,这么说可能有些抽象,举一些例子:

用模板图层来给影响其他图层

一般用来完成像模板一样作用的在 Photoshop 里有智能对象, Sketch 和 illustrator 里有 symbol,而它们只能做到是简单的复用,而不能更自由的选择只继承模板的一部分,而下面这个例子中白色的实例图层可以只继承模板图层的尺寸属性,而且可以使用表达式进行计算,当改变模板图层的尺寸时,每个实例图层的尺寸都会相应变化:

 

把图层宽度、高度赋值给变量

把图层宽高设置为根据变量

把图层宽高设置为根据变量的一半

不仅仅是尺寸属性可以变量化,任何图层属性都可以使用表达式和变量。

制作设计标准

很多有流程规范的设计总会要制作一个设计规范,而设计规范的制作修改总是繁琐而无趣的工作:要把设计中的元素整理出来,做成一个个图层,而每当要修改设计规范,就是噩梦了。而如果把图层属性用变量连接起来,文档就从“死” 变成“活”的了,修改设计规范中的参数,变化会自动扩散到文档中每个实例:

 

修改色标图层的颜色,会改变每个使用这个变量的图层参数,文本图层内容也可以变成颜色的 HEX 值

 

这样的变量系统在 UI 设计中国际化、主题化、跨平台等等方面能提高的效率有过制作经验的应该都可以想象。

另外掌握了所有图层的属性,这样就可以方面的实现类似 craft 的自动填充功能、生成 Zeplin 那这样的自动标注页面。而 Sketch  、Adobe XD 中很多功能也可以在 Photoshop 中实现,比如 Sketch   的对象缩放:

Image title

能够在 Photoshop 中实现对象缩放,虽然可能不会有 Sketch 那样实时响应的速度

另外还可以想象的是,不仅仅在文档里的数据绑定,还可以把 Photoshop 中图层的各种数据与 CSS 文件、样式 XML 文件里的数据绑定起来,做到在 Photoshop 中编辑图层实时更新在样式中,在 Photoshop 中可视化的编辑样式。

自动化

有了变量系统,每个图层还可以有自定义属性,可以给图层赋予逻辑代码,或给图层赋予“类名”,不同类有不同的逻辑….

能完成的只有想不到了…

比如说可以给图层组增加一个 padding (内边距)属性,影响图层组里图层的位置排列。

 

路线图

UI-DNA 属性面板

这是真正开发的部分,实现的就是上面说的这功能。

 

UI-DNA 功能库

会实现自动填充功能(类似 craft )、生成自动标注页面(类似 Zeplin )之类的功能,

由于开发 UI-DNA 做了很多“基础设施”的代码,这给开发功能提供了很多的便利,所以准备制作一个“扩展的扩展”——胶囊扩展功能,简单来说就是 UI-DNA 的插件。

只要写少量的代码、可以使用 UI-DNA 现成的接口,可以在 UI-DNA 中动态载入不用重启 Photoshop。

 

UI-DNA IDE

技术实现

文档中的变量、属性数据会以文本图层的形式存储在文档中:

 

图层的自定义数据以隐藏文本图层的形式保存在文档中

 

 

面板的界面我是想要尽量与 Photoshop 原生的界面相似,因为估计扩展完成时 Photoshop CC 2017  会发布了,之前制作界面时我预测新的 Photoshop CC 2017 的界面应该会和 Adobe XD 差不多,所以按 Adobe XD 的外观来制作的,最终我失算了  Photoshop CC 2017 界面竟然没有按我预测那样更新,不过这样其实看起来不错我不打算改了。

界面 CSS 库  https://github.com/nullice/Ex-morphogen

 

在技术上实现对图层各种属性的完全控制,是这个扩展开发的一个难点,了解 Photoshop 脚本的应该知道,Photoshop 虽然给 ExtendScript 提供了 DOM 接口,然而这个接口实在是年久失修,功能缺失严重,比如连获取多个选中图层的接口都没有(只能选取一个选中图层),更别提获取图层样式了。这应该就是 Photoshop 上针对 UI 设计相关扩展缺乏很大的原因。像之前所说的扩展 Ditto 就只对 Photoshop 提供了 DOM  接口的图层属性有用。

不过好在 ExtendScript 中有能够使用 Photoshop 底层功能 ActionManger 接口,然而 ActionManger 的接口并不像 DOM 一样有官方文档,可参考资料少得可怜,这让我花了大量时间在摸索 ActionManger  各接口的使用上。

总之最后问题是已经基本解决了,我做了一个把 ActionManger 操作变成传递 json 来操作的库,并封装了许多 DOM 没有的功能,比如获取多选图层、获取图层样式、画板。

如果要写 Photoshop 脚本和扩展的也可以很方便的拿来用(但是还没完全完成,应该有些 BUG 就是了),文末有 Github 地址。

举个例子。像下面这个例子如果单纯用 ActionManger 的话,获取样式得花上 100 行以上代码,设置样式要 200 行以上代码,而使用封装的接口的话:

//使用封装的接口获取当前图层图层样式的投影参数,只要 2 行
var eOb =ki.layer.getLayerEffectsObject(Kinase.REF_ActiveLayer, null)
ki.layer.getEffectsList_universal (eOb, "dropShadow", true)

//轻松获取和设置图层样式(支持多重样式)===>
[
  {
    "enabled": true,
    "present": true,
    "showInDialog": true,
    "mode": "normal",
    "color": {
      "red": 6.00000011734664,
      "grain": 152.486380040646,
      "blue": 255
    },
    "opacity": 70,
    "useGlobalAngle": false,
    "localLightingAngle": 90,
    "distance": 7,
    "chokeMatte": 1,
    "blur": 10,
    "noise": 0,
    "antiAlias": false,
    "transferSpec": {
      "name": "线性"
    },
    "layerConceals": true
  }
]
// 使用正常人是难以看懂的 ActionManger 的话:
var idsetd = charIDToTypeID( "setd" );
    var desc2896 = new ActionDescriptor();
    var idnull = charIDToTypeID( "null" );
        var ref551 = new ActionReference();
        var idPrpr = charIDToTypeID( "Prpr" );
        var idLefx = charIDToTypeID( "Lefx" );
        ref551.putProperty( idPrpr, idLefx );
        var idLyr = charIDToTypeID( "Lyr " );
        var idOrdn = charIDToTypeID( "Ordn" );
        var idTrgt = charIDToTypeID( "Trgt" );
        ref551.putEnumerated( idLyr, idOrdn, idTrgt );
    desc2896.putReference( idnull, ref551 );
    var idT = charIDToTypeID( "T   " );
        var desc2897 = new ActionDescriptor();
        var idScl = charIDToTypeID( "Scl " );
        var idPrc = charIDToTypeID( "#Prc" );
        desc2897.putUnitDouble( idScl, idPrc, 100.000000 );
        var idDrSh = charIDToTypeID( "DrSh" );
            var desc2898 = new ActionDescriptor();
            var idenab = charIDToTypeID( "enab" );
            desc2898.putBoolean( idenab, true );
            var idpresent = stringIDToTypeID( "present" );
            desc2898.putBoolean( idpresent, true );
            var idshowInDialog = stringIDToTypeID( "showInDialog" );
            desc2898.putBoolean( idshowInDialog, true );
            var idMd = charIDToTypeID( "Md  " );
            var idBlnM = charIDToTypeID( "BlnM" );
            var idNrml = charIDToTypeID( "Nrml" );
            desc2898.putEnumerated( idMd, idBlnM, idNrml );
            var idClr = charIDToTypeID( "Clr " );
                var desc2899 = new ActionDescriptor();
                var idRd = charIDToTypeID( "Rd  " );
                desc2899.putDouble( idRd, 95.998535 );
                var idGrn = charIDToTypeID( "Grn " );
                desc2899.putDouble( idGrn, 233.000336 );
                var idBl = charIDToTypeID( "Bl  " );
                desc2899.putDouble( idBl, 179.997253 );
            var idRGBC = charIDToTypeID( "RGBC" );
            desc2898.putObject( idClr, idRGBC, desc2899 );
            var idOpct = charIDToTypeID( "Opct" );
            var idPrc = charIDToTypeID( "#Prc" );
            desc2898.putUnitDouble( idOpct, idPrc, 60.000000 );
            var iduglg = charIDToTypeID( "uglg" );
            desc2898.putBoolean( iduglg, false );
            var idlagl = charIDToTypeID( "lagl" );
            var idAng = charIDToTypeID( "#Ang" );
            desc2898.putUnitDouble( idlagl, idAng, 90.000000 );
            var idDstn = charIDToTypeID( "Dstn" );
            var idPxl = charIDToTypeID( "#Pxl" );
            desc2898.putUnitDouble( idDstn, idPxl, 3.000000 );
            var idCkmt = charIDToTypeID( "Ckmt" );
            var idPxl = charIDToTypeID( "#Pxl" );
            desc2898.putUnitDouble( idCkmt, idPxl, 0.000000 );
            var idblur = charIDToTypeID( "blur" );
            var idPxl = charIDToTypeID( "#Pxl" );
            desc2898.putUnitDouble( idblur, idPxl, 6.000000 );
            var idNose = charIDToTypeID( "Nose" );
            var idPrc = charIDToTypeID( "#Prc" );
            desc2898.putUnitDouble( idNose, idPrc, 0.000000 );
            var idAntA = charIDToTypeID( "AntA" );
            desc2898.putBoolean( idAntA, false );
            var idTrnS = charIDToTypeID( "TrnS" );
                var desc2900 = new ActionDescriptor();
                var idNm = charIDToTypeID( "Nm  " );
                desc2900.putString( idNm, """线性""" );
            var idShpC = charIDToTypeID( "ShpC" );
            desc2898.putObject( idTrnS, idShpC, desc2900 );
            var idlayerConceals = stringIDToTypeID( "layerConceals" );
            desc2898.putBoolean( idlayerConceals, true );
        var idDrSh = charIDToTypeID( "DrSh" );
        desc2897.putObject( idDrSh, idDrSh, desc2898 );
    var idLefx = charIDToTypeID( "Lefx" );
    desc2896.putObject( idT, idLefx, desc2897 );
executeAction( idsetd, desc2896, DialogModes.NO );

最后

目前的情况是,这个扩展已经基本解决了在技术上的难题,只要有足够的工作量就能完成了,目前也已经有一个可运行部分功能的雏形了。敬请期待测试版发布吧,同时欢迎提出意见。

最后,这是工具是开源、免费的。

目前定名为:设计构建工具 UI-DNA

源代码:github.com/nullice/UI-DNA

 

31条评论

  1. www.xevip.cn   •  

    写的不错哈,支持一下 诚交友链 站务申请:www.xevip.cn

  2. 菜鸟   •  

    支持

  3. 非常棒的论文,深深的期待!   •  

    非常棒的论文,深深的期待!

  4. 设计者   •  

    我也想学习一下写这些,让PS操作起来更方便,是要从javascript基础开始学习吗?

  5. pskk   •  

    赞!

  6. 懒人懒得起名   •  

    期待 加油

  7. www.3gwb.com   •  

    哎呦,不错哦!欢迎互访! 诚交友链 站务申请:***

  8. 设计者   •  

    你好,我想问一下CSInterface都有哪些方法可以用,如果我想将test.js里面的一个变量值传到到main.jsx里面,是什么方法来传递。只知道evalScript()是执行main.jsx的函数。

    • 不知语冰   •     作者

      1,CSInterface.js 文件里都可以看到。2,要传递变量值,直接把变量值作为要 evalScript 执行的函数文本的参数就行了: 比如 evalScript( “func1( “+ var1+”)” ), main.jsx 里的 func1 函数就得到了 val1 的值

      • 设计者   •  

        谢谢你,终于知道怎么传递变量了。

      • zt   •  

        那么传出数据呢,比如中main.jsx传递到Test.js里

  9. 设计者   •  

    你好,我想请教一个问题,我写了一个简单的PS表格扩展插件,用自己电脑调试的,制作完成后,用自己电脑每次打开和关闭我写的这个插件Photoshop总会卡十几秒,但是插件放到别人电脑上面运行,打开和关闭的时候却不卡,你知道是什么原因造成的吗?(电脑配置是一样的,cookie已经都清除过了,也关闭了ps的调试模式)

    • 设计者   •  

      我自己已经找到原因了,原来是百度输入法会导致打开和关闭插件卡顿,我切换成英文输入法后就不卡顿了。

  10. 设计者   •  

    你好,请教一下 有没有办法选中最后那个快照。 PS历史记录里面的数组,快照和下面的历史记录是放在一起的,好像我没有办法选中最后一个快照。

    • 设计者   •  

      我已经找到方法了,用.snapshot这个值来判断就可以了。

  11. 设计者   •  

    你好,用代码有没有办法可以控制展开和关闭选中的图层组? 图层组默认都是展开的,最终得到的效果不美观。我想把图层组都设置成关闭的状态,不知道代码如何实现

    • 不知语冰   •     作者

      没有直接的办法,不过可以通过解散图层组再新建图层组来间接实现关闭图层组(选中图层状态下新建创建图层组默认是合并的),你可以看下我做的扩展里这个功能的代码:https://github.com/nullice/UI-DNA/blob/master/DVE/Enzymes/Kinase_lib.jsx#L5331 。要先记录下解散前图层组的各种信息,新建图层组后再加上去,我的这个实现没有记录蒙版,所以会丢失图层组的蒙版,你需要用的话得加上。

      • 设计者   •  

        谢谢,这样操作应该可以关闭选中的图层组了。

  12. 设计者   •  

    你好,PS里面有没有办法得到选中路径的所有描点的坐标和控制点的坐标,我想对这这些点的坐标进行变量控制,就是不知道怎么得到这些点的坐标。

  13. 设计者   •  

    我想问一下,用什么方法来监听鼠标在PS界面里面双击事件呢,我想用鼠标双击事件来执行函数

    • 不知语冰   •     作者

      Photoshop 的界面里是指图像窗口还是面板里,面板里是可以的,但是图像窗口里没有双击事件

      • 设计者   •  

        ps在使用自由变换工具时,在图像窗口下鼠标双击就相当于确定按钮,不知道这个是不是属于图像窗口下的鼠标双击事件。

        • 不知语冰   •     作者

          这个在事件里只能捕获到完成变换的事件

  14. 设计者   •  

    你好,我想问一下获取图层组的bounds有没有高效的方法,如果一个图层组里面有400个图层,获取这个图层组的bounds大概需要4秒左右,软件要卡顿4秒,只能先合并图层组再获取bounds信息,再撤销吗。

    • 不知语冰   •     作者

      没想到什么方法。ActionManager 的方法试过没有,那个应该会快一点,但是应该也不会快太多。

  15. Yuki   •  

    感谢博主,从你这里学到了很多

  16. nullice   •     作者

    第三方

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*