前端和设计师一直以来都在致力于为用户提供出色的人机交互体验。在这个过程中,如何为双方提供高效的协同产品,降低设计师与前端的沟通成本,以及提升双方的工作效率,都是非常有价值的探索点。
提起「前端与设计协同」(后面简称「设计协同」),相信大家都不陌生。它伴随着互联网精细化的分工而出现,在PC互联网时代发展壮大,在移动互联网时代趋于成熟。
而所谓「设计协同」,其主要功能就是在设计师和前端的协同工作下,将产品需求转化成代码的过程。
为此,我们需要解决以下三个问题:
然而,同时解决这三件事并不容易。
因为,这三件事彼此互为关联,并不独立,在协同上下游上相互影响。如果单纯CaseByCase地解决,很容易出现【解决了问题A,但引发了问题B】的尴尬情况。
所以「设计协同」需要的不是单点方案,而是系统化的解决方案。
「原始阶段」存在的问题比较多,可以用下面这张图来说明:
首先,设计资产是通过人肉管理的,设计规范也是人肉同步,而且不同的设计团队之间设计标准不统一,设计资产存在重复建设。
此外,开发作为设计的下游,设计侧存在的问题,也会同步影响到开发侧,具体体现在:
分析之后不难得出,以上问题存在的根本原因在于:
而业界解决此问题的传统思路,一般是采取以设计系统为中心的「有损」设计协同。
作为背景知识,在这里容我先简单介绍一下设计系统。
设计系统(DesignSystem)不是系统设计(SystemDesign),前者是关于设计的系统,后者是关于系统的设计。
而以设计系统为中心的「有损」设计协同,具体而言,就是为设计系统提供两套组件实现:
基于此的工作流程一般为:
这种做法能在一定程度上解决「沟通成本」和「开发效率」的问题,但同时也会在无形中造成设计意图传递的损耗。
为什么这么说呢?
这是因为,在以设计系统为中心的解决方案之中,设计规范存在两套相互独立的实现:
这就导致二者并不同源。
所以,解决问题的关键在于,能否设法消除这种「信息损耗」?
于是,我们提出了基于C2D2C的无损设计协同。其核心思路是:通过工程化的手段,打通设计和前端,统一协作语言。
具体做法为:
整体如下图所示:
此方案的好处主要有:
为此,为了实现基于C2D2C的无损设计协同,我们构建了三个子产品:
这个三个子产品共同实现了C2D2C的闭环流程:
这里有一个完整的演示,用来说明三者是如何相互联动,C2D2C闭环的:
海豚设计系统是C2D2C的基石,但是构建起来并不容易。
首先,由于云音乐App使用的跨端技术栈有两套:
所以,海豚组件库需要同时支持ReactNative和H5。在技术选型上,有两种方案可供选择。
方案一:分别为ReactNative和H5独立实现两套组件库
鉴于需要开发的组件数量较多(50+),且开发资源有限(2人),综合考虑投入产出比后,最终选用了方案二。
好的技术架构决定产品的生命力,特别是像组件库这种生命周期长的产品。在选用了方案二后,摆在我们面前的问题有:
为了解决以上问题,我们设计了海豚组件库的三层架构:
它主要特点有:
换肤是云音乐App的重要功能;此外,云音乐还存在着像直播、音街这类不同子品牌的App。
所以,为了支持以上这些场景,海豚组件库需要:
那具体要如何实现呢?
我们的核心思路是:抽象变与不变,描述组件的组成关系。
以海豚Button为例,决定Button样式变化的4个属性分别为:
所以,可以按照这四个维度,将Button拆解成变化的视觉+不变的骨架。
变化的视觉为:
不变的骨架,是由这四个属性排列组合成的正交变体:
通过将变化的视觉解构成两类token:
最后,只要配置不同的全局token和组件token,就能实现全局换肤和组件粒度的品牌定制。
Fin1.0设计插件的定位是提供给设计师的设计资产管理工具,让设计师可以利用C2D技术搭建出设计稿。
鉴于当时(2021年)Sketch还是主流设计软件,Figma在设计团队中的使用也比较多,这就要求我们要同时支持Sketch和Figma。
为了降低研发成本,我们设计了跨平台的插件架构,其核心思路就是用Web来承载UI和业务逻辑。
我们可以把插件分成端容器Client和Webview:
这样拆分后,Sketch和Figma便能完全复用UI,仅需要针对「端容器」和「Webview」通信方式、设计稿渲染逻辑上的不同,在Client上做差异化处理即可。
C2D的本质是代码转设计稿。
在业界,目前做C2D一般有两种思路:
html2figma的本质,其实就是DSL的转换:将描述网页的html,转换成描述Figma设计稿的Schema。
具体而言,就是将html的元素,比如div标签、p标签、svg标签,映射成figma的frame节点、文字节点和矢量节点。
举个:我们有一个div元素,长和宽为80px,圆角为40px,背景色为红色。
我们可以将其转换成FigmaFrameSchema:长和宽分别为80px,填充色为红色,圆角为40px。
转换之后,通过Figma的PluginAPI,就可以将其渲染到画布上,可以看到,二者在视觉上完全一样。
海豹D2C是C2D2C的最后一步:将设计稿转换成代码。
其实,不一定。
因为D2C的本质是将设计意图还原成代码,所以D2C的关键就在于如何让机器理解设计意图。
对于一张图片而言,由于其是非结构化的,它的所有信息完全包含在其二维像素平面内。对于这种场景,用基于CV技术的AI模型做组件识别,然后基于识别结果做D2C是非常合适的,但实现成本会比较高,因为会涉及到大量的数据标记和模型训练工作,整体ROI会较低。
但是,对于Sketch或Figma设计稿而言,因为其本身是结构化的,所以将其转换成代码是完全可行的,社区也有很多插件能做到这点,但真正的难点在于组件识别,也就是如何识别图层,将其与已有的组件库进行关联。
而海豹D2C的优势就在于,以较低的成本,实现了基于元数据的组件识别方案。
在「Fin1.0设计插件」的介绍中,我们知道,通过Fin1.0C2D产出的设计稿,会默认注入组件元数据,所以在D2C的过程中,只需要检测当前图层是否包含元数据,便能实现组件识别功能。
具体的处理流程为:
由于设计稿是设计师在画布上通过拖拉拽搭建出来的,受设计师作图习惯的影响,设计稿中的元素一般都是平铺的。
如果不进行布局优化,那么整个页面将是一个扁平的结构,生成的将是绝对定位的代码,虽然还原度能够保证,但是可读性会比较差,比如:
而布局优化的过程,则是对ABCD进行分组,首先将页面分为ABC和D两行,然后将ABC分为A和BC两列,最后将BC分为B和C两行。
分好组后,通过新增三个布局容器,形成行列嵌套结构,这样最终生成的代码将符合开发者的直觉,具备较好的可读性:
从上面优化的过程可知,布局优化,其实就是在做行列分割,完整的流程如下图所示:
第一步,获取待处理的所有节点坐标。
第二步,对所有节点做节点关系处理,判断它们是处于包含、还是相交还是相离关系。处理逻辑为:
第三步,对处理完成的节点做二维空间投影,找到行列分割的依据,例如:
第四步,依据二维投影得到的信息,对节点做行列分割,然后添加布局节点,进行行列分组。
最后一步,就是计算样式,生成包括Flex布局、绝对定位以及Margin偏移量等。
工程化阶段2.0是对工程化阶段1.0的补充和完善。
为什么要做设计工程化阶段2.0呢?那肯定是1.0存在某些问题。(笑
随着Fin1.0(C2D)和D2C落地的深入,一些问题也慢慢暴露出来。
在2022年中的时候,在线协同类设计软件慢慢崛起,Sketch已是明日黄花,云音乐的设计团队已基本全面拥抱Figma/MasterGo这类在线协同类设计软件。
随着设计师对此类工具了解的加深,他们发现使用Fin1.0C2D来做设计稿存在以下问题:
在D2C最初的产品设计中,我们将云音乐的页面类型分为两种:
我们的判断是:
所以,我们认为C2D2C非常优雅地解决了组件识别的问题。
但随着业务落地的深入,我们发现,对于营销活动页面而言,虽然没有既定的设计规范,但也会用到一些通用的UIPattern,比如弹窗:
对于此类场景,由于弹窗的样式并不稳定,无法沉淀成规范,这就导致:
最后造成前端需要基于已有组件库(比如antd)进行大量的样式复写,工作量大且低效。
另外,虽然D2C在出码阶段进行了布局优化,但是用户反馈生成的代码可读性还是存在一些问题,特别是生成的className:
总结一下,工程化2.0面临的问题主要有3个:
在回答这个问题前,我们需要做一些拆解。
对设计师友好的C2D=对设计师友好的工作方式+C2D
那什么是对设计师友好的工作方式呢?通过用户调研后发现,对设计师友好的工作方式,有以下几个特点:
具体到设计稿生产,就是能利用Library来做设计。
且通过原生提供的属性配置面板,能非常高效便捷地完成组件的配置!
因此,对设计师友好的C2D,就是为设计师提供一套海豚组件的Library,但是这套Library是通过C2D生成的!
相应地,我们做C2D的思路,就从「运行时动态生成并注入元数据」变成了「预构建Library并注入元数据」,由于设计稿元数据的格式没变,所以后续的D2C流程完全不受影响,完美!
对于这个问题,现有D2C难以解决的原因是:
那有没有既能解决UI定制化的问题,又能保留组件的交互逻辑的方案呢?
于是,我们提出了基于HeadlessUI的D2C方案。
作为背景知识,首先简单介绍下HeadlessUI。
所以,利用HeadlessUI,将「样式与逻辑分离」的思想,应用在D2C上,不就可以实现了吗?!
对于这个问题,若在前LLM时代,是非常难解的。
但随着LLM时代的到来,GPT3.5、GPT4等大模型的成熟,这个问题变得非常简单了:直接丢给大模型做语义优化即可。
当然,由于大模型的黑盒性质+结果不稳定,需要通过一些工程实践来规避由此带来的不确定性。
如果要构建Button的Library,则需要为Button的每一种不同的样式组合,在Library中提供一个对应的变体(Variant)。
组合而成的变体数量为:3*3*2*6*2*7*7*2=21168个。
是不是很震惊?
一个Button就有上万个变体,50多个组件全加在一起,数量将是巨大的。
人工来做完全不现实。
所以,肯定要借助工程化的手段,通过脚本来批量生产。
得益于我们在Fin1.0中C2D技术上的积累,我们通过html2figma实现了Library的自动化生产。
以Button为例:
视频演示为:
这是我们采用此方案构建的海豚组件库Library:
由于营销活动的组件非常业务化,所以最好是可以将此能力开放出来,让用户自行定义、自行处理。
为此,我们设计了D2C的微插件方案,通过为用户暴露D2C生命周期各阶段的Hook,让用户可以实现:
然后,基于微插件,业务开发利用我们提供的HeadlessUI微插件脚手架,适配到自己的业务场景即可,具体的使用流程为:
视频演示:
如果直接将D2C生成的JSX和CSS输入给LLM,让其对className进行语义化,并输出JSX和CSS,在大部分情况下能正常work,但是存在两个潜在的问题:
为此,我们对此过程进行了改造:
具体的Prompt为:
Youareafront-endtechnologist.HelpmeprocesstheincomingJSXcodesothattheclassNameiswellsemanticisedandoverallreadable.ThenoutputthemappingrelationshipbeforeandaftertheclassNametomeinJSON(directJSONoutput).Forexample:Input:```jsximportReactfrom'react';import'./index.css';constApp=()=>{return(