【技术篇】跟兄弟一起倒腾了一个解析PSD文件自动生成静态页面的工具,分享给你们!

前言

大家好,欢迎来到没啥用的创新技术频道。没错!你没看错,这里就是把没啥用的技术或者平时很少接触的技术进行结合。创新出一种更加没啥用的工具,提供给大家使用。因此我们称为没啥用的创新,也可以叫我们莓创

在这里,我们只有一个目的:提高开发效率,增加摸鱼时间。这也是我们来稀土掘金的目的。

成品展示

在解说自己开发学习的历程之前,先把工具分享给大家使用使用,如果想了解我们或者想学习、合作的都可以私信我哈。快速访问

开发背景

日常开发中经常与我的后端好兄弟进行友好交流,每次前端资源不够的时候,需要后端兄弟一起开发页面的时候。后端兄弟都会以前端页面样式太难开发了,我们不会的理由拒绝帮忙。有一次我直接回怼了:那我开发一个生成样式的工具,直接把psd生成页面代码,你们直接在生成代码里面改就好了吧。这时候后端兄弟直接鸦雀无声了,我心想他们一定在心里很感谢我的。

后面我就开始想有没有解析psd文件的功能?上网查了一下,很多都是后台的,如果用后台去解析,那我还研究它干嘛,我还不如乖乖的当切图仔。所以转头找了纯前端解析的,于是在Github上找到了PSD.JS插件Github地址。于是干劲十足的打算学习好这个,然后自己写一个解析PSD文件生成H5页面的解析生成器。

学习PSD.JS

看了官网的介绍。真的潦草,详细API都没有。以下代码就是官网提供的

js 复制代码
var PSD = require('psd');

// Load from URL
PSD.fromURL("/path/to/file.psd").then(function(psd) {
  document.getElementById('ImageContainer').appendChild(psd.image.toPng());
});

// Load from event, e.g. drag & drop
function onDrop(evt) {
  PSD.fromEvent(evt).then(function (psd) {
    console.log(psd.tree().export());
  }); 
}

然后加上提供了一些方法介绍,就没了,搞笑哦。

只能自己上网找资料,但是找来找去都只有这些,我给大家列一下。

可是只有上面这些基础的还远远不够,比如我们CSS中有很多属性:宽度、高度、定位位置、行高、间隙、背景颜色、文字特效等等一系列样式。这些都是我们日常开发经常使用的属性,如果不能拿到这些我们还解析它干嘛呢,还不如老老实实的写CSS跟div。

分析PSD文件

想要解析PSD文件,就要先分析PSD文件的图层结构跟H5有哪些相似度,用什么属性什么结构能够完美的形成映射关系,这步很重要,不然你就算解析出来了,在开发项目的时候也使用不了。

上图说话

可以看到基本psd的文件都是定位元素,那么基本我们就可以确定所有元素都使用相对定位来布局。再通过x跟y来确定位置。

还有我们可以看到图层也有上下级关系,但是好像定位都是单图层去定位的,而不是相对上级去定位,所以觉得还是需要改成相对定位,不然后续想改变整个文件的位置,后续还得一个一个去改下面图层的位置,这样就很不方便了。

确定完页面排版规则我们就开始调用API解析获取对应的属性了。

解析属性映射样式

我们通过官网跟网上的教程拿到了一些基本方法。本地调用一下试试看:

js 复制代码
var PSD = require('psd');

// 默认我们就通过fromURL方式去解析就好了
PSD.fromURL(psd文件地址).then(function(psd) {
    // 获取到整个psd的预览图
    const l_background = psd.image.toBase64()
    // 获取整个psd文件的实例
    var exportData = psd.tree().export()
    // 获取整个psd文件的宽度跟高度
    const { width, height } = exportData.document
    // 获取整个psd文件的图层数据
    const children = psd.tree().children()
    console.log(width, height, children)
});

看一下整体结构

通过我们打印出来的数据,发现如果_children属性有数据就代表是上下级结构,还有一些基本的widthheightlefttop。所以我也定义了跟他相同的数据结构

js 复制代码
const psdJson = {
    width: xxx,
    height: xxx,
    children: [
        {
            width: xxx,
            height: xxx,
            top: xxx,
            left: xxx
            children: [
                {
                    width: xxx,
                    height: xxx,
                    top: xxx,
                    left: xxx
                    children: [...]
                },
                ...
            ]
        },
        ...
    ]
}

继续往下走吧,还有很多属性等着我们去挖掘呢?

获取文本与图片的基础样式

js 复制代码
var PSD = require('psd');

// 默认我们就通过fromURL方式去解析就好了
PSD.fromURL(psd文件地址).then(function(psd) {
    // 获取整个psd文件的图层数据
    const children = psd.tree().children()
    children.forEach((item, index) => {
        if (item._children && item._children.length > 0) {
            // 多级图层信息
            item._children.forEach((row, i) => {
              // ...需要使用递归去遍历下级每一个图层信息
            })
        } else {
            // 单图层信息
            const typeTool = item.get('typeTool')
            if (typeOf typeTool !== 'undefined') {
                // 文本图层
                const { left, top, width, height, value, font } = typeTool.export()
                console.log(left, top, width, height, value, font)
            } else {
                // 图片图层
                const src = item.layer.image.toBase64()
                const { left, top, width, height } = typeTool.export()
                console.log(src, left, top, width, height)
            }
        }
    })
});

通过图层的typeTool属性可以判断是文字还是图片。依旧通过获取每一个图层的export属性获取实例解析:width, heightleft, top。而文字可以通过value值来获取文本信息,图片的地址则通过layer.image.toBase64()方法生成base64地址图片。

解析图片地址的时候报错了。

一开始还以为是程序方法有问题?一直怀疑自己,后面查阅了很多资料。统一都说:"psd文件里面图片图层必须栅格化,不然图片解析不了"。等到这个结果之后,我就把所有的图片图层栅格化,果然好了。

解决完图片的问题之后,我们拿到一份基础的数据,就可以渲染基础模型。

获取文本的颜色、行高、间距、字体样式

除了图层信息里面的一些基础的比较容易获取,其他的属性真的可以折磨到让你直接原地放弃。但是我们是谁:没啥用的创新人😂😂😂。就是不怕折磨。我直接给大家贴代码:

js 复制代码
var PSD = require('psd');

PSD.fromURL(psd文件地址).then(function(psd) {
    // 获取整个psd文件的图层数据
    const children = psd.tree().children()
    children.forEach((item, index) => {
        if (item._children && item._children.length > 0) {
            ...
        } else {
            // 单图层信息
            const typeTool = item.get('typeTool')
            if (typeOf typeTool !== 'undefined') {
                // 文本图层
                const { left, top, width, height, value, font } = typeTool.export()
                const { colors, styles, alignment } = font
                
                // 字体颜色
                const color = colors[0]
                // 获取文字的详细属性配置
                const StyleSheet = item.layer.adjustments.typeTool.obj.engineData.EngineDict.StyleRun.RunArray[0].StyleSheet || {}
                const { StyleSheetData } = StyleSheet
               
               // 获取实际的字体大小。存在psd设置的单位可能为pt,所以要转成px单位
                const sizes = typeTool.sizes()
                let size = 24
                if (sizes && sizes[0]) {
                  if (transform.yy !== 1) {
                    size = Math.round((sizes[0] * transform.yy) * 100) * 0.01
                  } else { // transform.yy为1时,sizes[0]的值就是字体显示大小的值,不须要计算
                    size = sizes[0]
                  }
                }
                // 字体大小
                const fontSize = size
                
                // 获取字体样式
                const fontFamily = typeTool.engineData.ResourceDict.FontSet[StyleSheetData.Font]
                
                // 获取字体的排版
                const writingMode = item.layer.adjustments.typeTool.obj.textData.Ornt.value
                
                // 获取字体间隙
                const tracking = fontSize * (StyleSheetData.Tracking / 1000)
                
                // 获取行高
                const lineHeight = StyleSheetData.Leading
                let leading = (Math.round((lineHeight * transform.yy) * 100) * 0.01) / fontSize
                // 获取字体是否加粗
                const bold = !!(fontFamily.indexOf('Bold') !== -1 || fontFamily.indexOf('Bold') !== -1)
                
                ....
                 
            } else {
                ....
            }
        }
    })
});

终于完成了,为了实现上面这些,我历经了一个月左右,发现头上的那几颗毛快扛不住了(快喝点芝麻糊补补)。严重吐槽一下psdJs的开发者,不把官网文档好好整整,一个属性还要到很多层里面去拿出来。吐槽一下不足为过,即使这么复杂都没挡住我研究的脚步。

其实有很多属性我也可以将就使用的。比如字体样式跟字体粗细都是可以通过typeTool.export()下的font属性去获取,看下面的图片,第一版本我都是直接取下面的name[0]weights[0]属性值来作为字体样式和字体粗细了。但是试多了几个psd模版之后就发现很多不是这两个值能决定的,所以不得不换,不然就是敷衍自己了。

所以上面的一些方法和属性我不得不在网上资源(不丢脸)查资料了,查阅了很多国外的资源。比如StyleSheetData文字图层详细信息,我是从网上资源查到是怎么获取的:item.layer.adjustments.typeTool.obj.engineData.EngineDict.StyleRun.RunArray[0].StyleSheet。还有文字样式是存放在这个属性里面:typeTool.engineData.ResourceDict.FontSet,但是网上资源没告诉我怎么样把这两个关联起来获取当前真实的字体样式值。通过本地不断打印不同psd模版的数据。就发现StyleSheetDataFont属性也会动态变化,而且刚好是FontSet对应下角标。所以通过typeTool.engineData.ResourceDict.FontSet[StyleSheetData.Font]就可以获取到对应的字体样式。其他样式得获取方式基本都是这样,不断查资料跟本地打印查找相关关联配置。

最搞笑的是字体的粗细,打印出所有的属性也没找到真实的值。最后不得不通过字体样式fontFamily是否存在bold相关字符串,最终得到是否是粗体。

上面基础样式就已经讲完了。基本所有属性都拿到了,那么接下来就是难点中的难点了。

获取文本的特效

一开始没注意特效这个,因为解析图片的时候,会把特效栅格化,想着文字的应该也一样处理就可以了。后面真正解析到的时候,文字图层栅格化后就会变成图片图层😂😂,这样肯定不行。但是又不能睁一只眼闭一只眼的跳这步流程,没办法只能硬上了。

查阅了官方文档相关资料,好家伙,果然没有。 打开对应Issues找一下有没有相关得问题。果然功夫不负有心人,找到相关的答案:可以通过layer.get('objectEffects').export()获取。

于是打印一下看看里面都有哪些属性?然后直接傻眼了,乍一看还以为是打印错了。不用想了,又要开始查阅资料了。

为了不浪费大家时间,我直接举一个文字阴影特效的例子吧!!

js 复制代码
var PSD = require('psd');

PSD.fromURL(psd文件地址).then(function(psd) {
    // 获取整个psd文件的图层数据
    const children = psd.tree().children()
    children.forEach((item, index) => {
        if (item._children && item._children.length > 0) {
            ...
        } else {
            // 单图层信息
            const typeTool = item.get('typeTool')
            if (typeOf typeTool !== 'undefined') {
                // 文本图层阴影特效
                const objectEffects = item.layer.get('objectEffects').export()
                const { DrSh } = objectEffects.data
                
                const clrStr = JSON.stringify(DrSh['Clr ']).split(',')
                let r = null
                let g = null
                let b = null
                clrStr.forEach(item => {
                  if (item.indexOf('Rd') !== -1) {
                    r = item.replace('"Rd  ":', '')
                  } else if (item.indexOf('Bl') !== -1) {
                    b = item.replace('"Bl  ":', '').replace('}', '')
                  } else if (item.indexOf('Grn') !== -1) {
                    g = item.replace('"Grn ":', '')
                  }
                })
                const angle = DrSh.lagl.value
                const distance = DrSh.Dstn.value
                const shadowColor = `rgba(${parseInt(r)},${parseInt(g)},${parseInt(b)},${DrSh.Opct.value / 100})`
                const x = distance * Math.tan(angle * Math.PI / 180);
                const y = distance * Math.tan((90 - angle) * Math.PI / 180);
                
                const textShadow = x + "px " + y + "px " +
                        DrSh.blur.value + "px " + shadowColor;
                ....
            } else {
                ....
            }
        }
    })
});

解析一个文字特效已经快掉光所有头发了,当初脑子是被什么门夹了吗?开弓没有回头箭,肝就完了。一开始我打印出来的属性字段名称都是:DrShGrFlSoFi等等,这些我都不知道啥意思呀?网上也查不到资料。没办法我只能从中文意思下手,我在ps软件里面设置文字阴影特效,ps称为投影。然后我试着用投影去翻译成英文,查出来的是:Drop Shadow。我对比了一下,果然是英文的简写。Drop Shadow对应的就是DrSh

难,真是太难了。后面获取阴影属性的时候,基本也是英文的缩写,不但可以学习技术,还可以学习英文,真的蟹蟹。

结束

我大致的分享已经讲完了。自从开发完这个工具之后,后端兄弟就一直很忙了,平时找他们玩游戏都没时间了。看到他们充实的样子真的很欣慰。

这个工具其实还有很多问题跟工具需要完善,而且以后我们还会做很多功能,比如:智能布局智能交互智能动画等等。感兴趣的可以私信我,或者在使用过程中有问题,都可以私信我,到时候我会拉一个群,大家都可以在里面提建议与思路

相关推荐
崔庆才丨静觅10 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606111 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了11 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅11 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅11 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅12 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment12 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅12 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊12 小时前
jwt介绍
前端
爱敲代码的小鱼12 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax