inkstone.webnovel.com 是一个面向海外原创做家和翻译的创做平台。大量的做家和原创做品,意味着大量的书封制做需求。这以前是须要设计师投入精力,或者花钱购买来解决的。而此时 浏览器端书封制做系统成为了咱们解决这个需求的一大利器。做家只须要登陆咱们 inkstone,短短几步就能制做出一个精美的书封。web
海外书封系统主要分为纯色背景和图片背景两条线。纯色背景须要四步,图片背景须要五步就能够完成咱们的书封制做。canvas
这一切也早已在去年国内起点做家助手,张鑫旭老师带领下实现了中文版的书封制做系统。而咱们海外测这边思路和国内是同样的,可是仍是有不少非中文体系下的难点须要作额外的努力。浏览器
对于中文版书封系统,张老师已经有不少内容给你们分享。本文着重会在区别于国内书封的几个点地方给你们介绍:缓存
最开始拿到这个需求,第一反应就是将不一样的图层对应成咱们 DOM 的分层,而后直接将处理好的 DOM 导出为图片便可。虽然用 DOM 操做虽然对文本的处理会比 Canvas 优秀不少,可是在图像处理这一块仍是 Canvas 可能性更高。好比要实现一个文字须要同时叠加图片纹理和背景颜色的这件事,基于 DOM 目前我好像也没有找到合理的解决方案。bash
因此选择了如上图所示的层级模式。背景层就是 Canvas 叠加滤镜以后的 Base64 图片,中间层是用 Canvas 直接绘制的文字「 书名+做者名 」,最上面一层是 LOGO 和水印的图片。 当用户点击上传按钮的时候,再用 Canvas 将这个三个层级叠加成咱们想要的书封,提交到咱们的服务器。服务器
对于图片滤镜处理的相关技术方案你们能够去张老师博客上搜索关键词:滤镜。网络
我这边会着重在滤镜缓存优化机制上给你们介绍。工具
为了体现优质的滤镜效果,和国内书封同样选择的是电影级别的 3D LUT 滤镜。但是这种级别的滤镜,大部分文件大小都接近 1M,若是在用户每次点击按钮以后,才去加载对应文件再渲染,这整个等待时间是很长的。而且更让我吃惊的是,渲染滤镜是一个可能比加载还要花时间的过程。字体
海外前端须要生成的书封的尺寸是 810*1080,而这也就对应着 874, 800 个像素点,而这种 3D LUT 滤镜几乎是须要去处理每个像素点,可想而知这整个计算量是很是大的。再看看咱们在预览状态下的书封尺寸实际上是只有 240*320 的,用户在点击切换滤镜的时候,并不表示他必定会用这张图,有可能真的只是看看。
因此这边的处理方案是在预览状态下,只会去处理 480*640「 2倍图 」这个尺寸的书封。从上图能够看到咱们总体的平均加载时间,减小了近 63% 的时间。
当用户真正点击上传的时候,才去对原始尺寸的图片作滤镜处理。而此时,由于滤镜文件已经加载过了,至关于只须要滤镜渲染的时间,一箭双雕。
而后咱们再来看看预加载逻辑,当用户进入页面的时候默认是没有应用滤镜的。在这个时候会预先加载第一个滤镜,加载完成渲染,而后加载第二个滤镜,加载完成渲染。渲染好的图片会转成 Base64 的 URL,放到右侧 IMG 列表里「 这个列表在生产环境会隐藏 」。当用户点击切换滤镜按钮的以后,只须要从右侧列表中拿出对应序号的 URL,替换左侧的展现区域中的背景图片的 URL 便可。
这样作至关于咱们老是先于用户两步,准备好处理完滤镜的图片。最大限度的减小了用户等待的时间。甚至当用户走完一圈以后,切换滤镜就等于交换两张 Base64 图片的 url,没有加载,也没有滤镜处理,完美。
首先国内对于中文字体的处理是有一套咱们本身的 y-font 「 阅文中文字体接口服务 」支撑,因此能够按需加载书封用到的几个文字的字体。可是海外和国内是不共享服务器的,致使于咱们直接无法使用这套服务。不过好在和中文字体比起来英文字体自己就比较小,再加上 Google Font 的加持,即便全量加载也不会有太大的问题。
海外书封的字体列表自己还和用户选择的做品分类有关系。一开始我是给每一个分类都准备了一个存放字体列表的 CSS 文件,当用户切换做品分类的时候,切换对应的 CSS 文件。然然后面发现这个是我想多了。
对于字体浏览器是有本身的处理逻辑了。简单的说就是,页面中若是没有任何一个文字设置了对应的字体,即便你引入了字体的 @font-face
这个字体也不会被加载。当你给某个文字设定了font-family
以后这个字体文件才会加载。当这个字体文件加载成功以后,这个设置了对应font-family
的文字会从新进行绘制。
因此最后我将不一样分类的字体文件都合并成了一个,由于不一样分类下的字体是有重叠的,反而让总体的字体 CSS 文件变小了。后续经过给文字设置 font-family
来控制字体的加载。
如图所示,我会在页面中直接输出用户选择分类下的字体列表,并只给前五个的文字设置font-family
也就是说我预加载了这五个字体。由于用户选择分类是在第一步,切换字体是在第最后一步,因此当用户到达第五步的时候,这五个字体早就已经预加载好了。
当用户切换字体的时候,我会从用户选择的字体序号开始,给后五个文字设置font-family
,至关于用户真正切换字体的时候只加载一个字体「有四个已经预加载好了」。而后用户在这切换字体的整个过程当中,也几乎不会感觉到字体的加载。这个预加载逻辑和以前处理滤镜的方式一模一样。
更值得一提的是,这种方式还解决了没法知道字体文件何时加载成功的逻辑。
咱们都知道存在 DOM 标签中的文字,若是是动态加载的字体,当字体文件加载好以后,设定了相关 font-family
的字体会自动从新渲染成对应的字体。
可是绘制在 Canvas 中的文字,在字体文件加载后,须要手动触发渲染。但是犯难的问题在于,咱们如何才能知道一个字体文件是否加载成功?对于这个问题是有一个 「 Web Font Loader 」的库能够解决的。然而当咱们采用了预加载五个字体的逻辑以后,这个库的意义就不大了。由于用户在切换字体的时候,下一个字体早就已经加载完成,也就不会出现当前字体动态加载的问题。
固然有一些极端的状况就是用户在切换字体的时候按钮屡次点击,而且这个速度超过了咱们预加载5个字体文件的速度。或者在某些网络相对没有那么好的地区,Google font 加载比较慢。这个时候会出现当前字体对应不上「 会显示默认字体 」。对于这个问题咱们的处理的方式是不处理。
由于用户其实并不知道哪一个编号对应哪一个字体,即便出现了字体编号和字体不匹配的问题,用户也是很难发现的,可能会觉得这个编号的字体就是和默认字体长得很像。当用户点完一圈再次回来的时候,这个字体就颇有可能加载好了。
context.fillText(text, x, y [, maxWidth]);
复制代码
canvas 文本绘制API
用 Canvas 绘制的文本,并不会像 DOM 标签那样超出以后会自动换行。想要实现自动换行,须要手动计算,而后追行绘制。好比你有一行文字 你是个人小苹果
, 文字大小是 16px
,行高是24px
,Canvas 画布的宽度是 4*16px
。就须要将句话按照 4 个字为一组逐行绘制。
context.font = '16px STheiti, SimHei';
context.fillText('你是个人', 0, 0);
context.fillText('小苹果', 0, 24);
复制代码
图1/ 图2/ 图3
国内和海外书封,最大的不一样就是对于文字的处理。而这不一样的缘由来自于中文和英文自己的差别。 简单的说就是中文可能一个成语搞定的事,英文须要一整句话。
因此多数状况下,做者的书名一行是放不下的。因而咱们只能反向思考,基于画布的宽度,和一行文本的个数动态去计算文字的大小「 图1 」。
字号 = 取整(画布宽度/一行单词个数);
复制代码
当用户点击底部的空格区域添加换行符的时候,选取字数最多那一行做为咱们上述公式的被除数,用来计算咱们的基础字号,其它行也基于这个字号进行绘制。
固然咱们计算的字号不可能会无限大,因此咱们会给基础的字号一个最大值,当计算出来的基础字号超过这个最大值的时候,咱们会以这个最大字号做为咱们的基础字号「 图3 」。
然而这就会出现另一个问题了,就是大多数的英文书名一行显示的时候,这个动态计算的文字的字号都偏小。这就直接致使,大多数用户在刚进入这个页面的时候,都会看到这个并不优雅的状态「 图1 」。
为了解决这个问题,在用户首次进入文字排版的页面时,会自动在文本正中间的最近的空格处,默认帮用户添加一个换行符。虽然并非完美的解决,但至少能保证大多数的用户首次进入的文本可读性「 图2 」。也能告知用户,咱们的交互形式是利用底部空格进行换行,是一个一箭双雕的办法。
单行文字起始点纵坐标 = 基准点起点纵坐标 + 行号 * 行高;
复制代码
前面有提到,咱们在绘制 Canvas 文字的时候,还须要提供起始点的横纵坐标。在字号相等的状况下咱们的起始点纵坐标只须要经过上述公式就能够实现。
行高 = 字号 * 1.5;
复制代码
原本一开始想经过这个公式来处理文字的行高「 这是网页中最经常使用对于行高的处理方式 」。然而在英文这个错综复杂的字体系列下,这个公式显得是那么的苍白。
由于英文不像中文那样是四四方方的,英文中有视觉上偏上的字母 「 l 」 , 也有视觉上偏下的「 g 」 。即便是同一个字体,一样的字号行高下,你也能够看到,第一列的文字发生了重叠,第二列显示还好。因此在大小写混排的状态下,咱们是很难给到某一个具体到行高来规避这样的问题。
此时细心的同窗可能发现,后面的两列由于都是大写好像这样的问题就行了不少。因此咱们和设计师约定书封标题默认都是大写,然而问题尚未结束。
行高 = 字号 * 1;
复制代码
在通过屡次的调整和视觉对比以后,咱们最终选择了这个公式来做为咱们行高的计算方式。固然这中间也麻烦设计师去掉了一些特别违和的字体。
原本这个对齐方式若是只有左中右三种方式的话是没有什么好讲。由于 Canvas text-align
API 自带这三种对齐方式。
难就难在第三个 AUTO 的这种对齐方式,这实际上是一种相似海报中经常使用的艺术表现手法,上面的四个图都是在 AUTO 模式下,只是调整了文字的换行实现的。能够看到这个效果是比左中右这三种常规方式更加生动的「 为优秀的设计师点赞 」。
相信你们经过上面的示意图也能够看出其中的逻辑。就是咱们每一行的字号是基于上一节提到的公式单独计算的。简单的说就是每行字数越少字号越大「 固然会有一个最大值 」。
单行文字起始点纵坐标 = 基准点起点纵坐标 + 以前每一行的行高;
复制代码
然而如今由于每一行都是单独计算,因此每一行的字号都不同,对应的行高也不同,就得把全部行高都纪录下来,在逐行绘制。因此在 AUTO 模式下文字起始点的纵坐标就变成了上述公式。
图1/图2
对于文本颜色,一开始我觉得,只须要设计师给我一个文字颜色列表就能够了。然而事实仍是想得太简单了。
好比设计师给个人颜色列表第一个颜色是白色,当用户选择了如图2那种偏向纯白的背景,若是仍是用白色做为咱们文字的颜色,就几乎看不清的。因此咱们须要有一个逻辑去针对不一样的背景切换默认的文字颜色。
这边给你们推荐的一个库是 Color Thief。如上图所示,就是给这个库的 API 提供一张图片,它就会返回给你这张图的配色表。而且这个配色表的长度你是能够定制的。
const isWhite= (r + g + b) / 3 < 128*1.3? false: true;
复制代码
咱们怎么基于这个库去判断一个背景图是偏深仍是偏浅呢?很简单,咱们取出这个配色表的第一个颜色,也就是这张图片中的主色。让后将这个颜色的 rgb 的色值取一个平均值,若是这个平均值小于 128*1.3 咱们就近似认为这个图是偏深。你们可能会问,128 不才是256 的一半吗 ?为啥咱们这里还要再乘以 1.3 。
其实这个很简单,在 rgb 平均值在 128 附近的时候,白色和黑色文字其实都是能够看得清的。可是在这个边缘,咱们更但愿用户是看到的是白色文字。至于为啥是 1.3,其实就是咱们本身一张图一张图去试,大概以为在这个阈值下,比较符合咱们指望的效果。
这里 Color thief 对于图片配色表的计算和以前滤镜逻辑有着相似计算逻辑,就是咱们提供的图片尺寸越大,这个计算的时间就会越长。原本咱们这里要的就是一个模糊的值,因此咱们提供给 Color thief 的也是一个缩略图。
到了这里,咱们只是粗略的解决了用户字体设置页面的默认字体颜色问题。对于整个颜色列表要怎么设置也是一个问题。
文字颜色列表 = 黑+白+8个配色+4个百搭颜色
复制代码
咱们用 Color thief 取整个图片的 9 个配色,而后用第一个颜色做为咱们评判默认文字颜色是黑是白的标准。而后加上剩于的 8 个配色和设计师给到的 4 个百搭的颜色,组成咱们的文字颜色列表。
你们可能会好奇,这里为啥咱们直接用背景的配色做为了咱们文字的颜色?这个方案是,张老师在国内书封项目中提出的。
不难理解,就是图片配色里面的颜色和图片自己搭配才不会有太大的违和感。其它的颜色,有可能单独看会很好看,可是放到一个不搭的背景里面反而会显得奇怪。因此后面设计师给到的颜色也是选的比较百搭的颜色。
篇幅关系,这边着重挑出了几个我认为值得给你们分享的技术难点。另外还想给你们分享的是一点点项目心得。
刚拿到项目的时候,其实我最担忧的是图片的滤镜和图片纹理的叠加不知道要怎么实现。由于这部分在我以前的项目经验里面是空白。然而在我实际使用的时候,这部分其实基本上就等同于调用一下 API 而已,固然这个和由于有张老师的加持也有很大的关系,几乎这方面的问题都能在张老师的文档中找到。真正比较难的仍是在想要提高用户体验这个点上。
一开始由于本身的一些既定思惟,误觉得在浏览器端处理英文应该会比中文容易不少。毕竟中文字体动辄几兆的文件大小在那儿摆着。然而在实际开发中,才知道由于英文语言特性,对于这种须要精细化处理的地方会有不少的坑。简单的说,在某种字体下,你这整个逻辑均可能被推翻。在这一点上,可能仍是要略微舍弃技术追求,和设计师商量,看看是否能够去掉这样的字体。
技术永远只是你从 A 点到 B 的的工具。到达 B 点才是你核心的目的。