前言:
如今朋友们对“h5js上传图片”都比较讲究,我们都想要剖析一些“h5js上传图片”的相关资讯。那么小编同时在网上汇集了一些有关“h5js上传图片””的相关资讯,希望大家能喜欢,看官们一起来了解一下吧!需求背景
一句话描述需求:答题并根据答案生成你是什么食物,可长按保存图片。
体验地址:
问题1:字体文件太大
活动页用到了特殊的中文字体“汉仪小麦”,完整的字体文件约 6.9MB,而实际活动页只用到了很少的一部分字体,全部加载会增加整个页面的加载时间,需要对字体进行按需裁剪。
使用 fontmin 工具进行字体裁剪,将要裁剪的文字集合保存到文本文件keep.txt中(有重复文字没关系会自动去重),通过下面命令裁剪字体文件:
# input.ttf 输入的字体文件# output 裁剪后的字体输出目录,包含了多种字体格式fontmin -t "$(cat keep.txt)" input.ttf output
在 CSS 中引入裁剪后的字体文件,下面这段代码是 fontmin 自动生成的,其根据每个平台所支持的字体格式选择最合适的:
活动页中用到了约1100个不同汉字,裁剪后大小从 6.9MB 降为 284KB 左右,体积缩减了约 96.5%,从实际体验上看,可以接受了。
-rw-r--r-- 1 whincwu staff 284K Jul 24 15:57 hyxm.eot-rw-r--r-- 1 whincwu staff 710K Jul 24 15:57 hyxm.svg-rw-r--r-- 1 whincwu staff 284K Jul 24 15:57 hyxm.ttf-rw-r--r-- 1 whincwu staff 284K Jul 24 15:57 hyxm.woff问题2:保存网页快照到本地图片
如果可以调用原生的截图接口,实现这个需求应该是比较简单的。但这是一个纯 H5 页面,要求使用 web 技术实现,有两种思路:
方案一:调用后台接口,后台渲染网页成图片返回给前端
优点:前端不需要太多工作量;后台可以利用成熟的网页渲染库处理;不存在跨域等问题。
缺点:增加了后台工作量和复杂度;调用接口返回需要时间,前端会有一定的等待期,这会造成体验下降;前端页面发生需求变化时,需同时更新后台渲染模板,可能会造成更新不及时,增加了维护成本。
方案二:前端遍历 DOM 结构将页面绘制到画布上后,导出成 base64 图片
优点:不依赖后台;及时性好;体验好。
缺点:对前端技术挑战比较大(好在有现成的第三方库完成这件工作);图片跨域会有一些问题。
我选择了第二种方案,使用 html2canvas 库对网页的部分进行“快照”,html2canvas 这个库是一个纯前端的库,其原理是读取 DOM 上的样式信息,在 canvas 上绘制按照 W3C 的 CSS 规范渲染 DOM,只支持部分 CSS 属性,不过也足够一般的使用场合了。”截图“的代码如下:
去年在做 Webank App 存本取息存款心意卡时也用过这个库,当时是 0.5-beta 版,那个版本存在不少问题,例如不支持高清屏导致图片模糊,好在截止做这个活动页时,版本已经来到了 1.0-alpha 版,解决了那一版的很多问题,节省了不少时间。
问题3:微信分享网页无反应
开发完后给测试同学体验时,发现微信通过右上角分享出去时,没有任何反应或者发送失败。
以前听过微信分享图片有限制,怀疑是画布导出的图片过大,超出了微信分享缩略图大小限制。
控制台打印图片大小出来确实比较大,在我的小米 MIX2 上大概 300 KB 左右(这个值在不同分辨率手机上会不同,分辨率越高越大),解决办法就是降低图片大小,canvas 的 toDataURL 接口有两个参数,第一个参数是导出的图片类型,第二个参数是图片质量(仅当图片类型是 jpeg 时有效)。下调导出图片质量,再尝试分享是否成功,如此返回下调多次后,在图片尺寸降低到几十KB 后,图片终于可以分享成功了。
img.src = canvas.toDataURL('image/jpeg', 0.92)
分享的问题虽然解决,但是图片清晰度严重不足,这个解决办法行不通。
在查阅部分网上资料后,发现微信分享网页时,自动提取页面内第一张可见图片作为缩略图,缩略图最大不能超过 32KB,否则发送失败。既然微信分享时的缩略图是取第一张图片,那就放一张小于32kb的图片在页面内最前面,作为分享时的缩略图,只要它不占用空间就不会影响布局,这个方法最终实践简单可行。
注意:这个图片不能设置为display: none,否则微信取不到该图片。
上面这种解决方法最后还是没有使用,原因是有更好的处理方式了。在同事的帮助下,使用还未被和谐的微信 JSAPI 设置分享缩略图,既解决了分享缩略图的问题,还可以定制分享的标题和描述内容。
问题4:资源预加载并展示进度
活动页中用到了大量的图片,网络慢时会出现图片加载不完全的情况,而且页面中有不少动画,如果图片没有加载完,动画看起来比较诡异。所以,图片预加载是非常必要,其原理是利用浏览器的缓存机制——优先使用已经下载过的图片。
常见实现是在页面中将一些要用到的图片以<img/>标签的形式放入页面中且使其不可见,浏览器自动解析下载这些图片,后面再用到相同的图片时,无需再次下载就立即可用。也可以通过 JS 动态创建HTMLImageElement实例来下载图片,这里我使用了第二种,主要是方便 JS 操作。
图片资源通过动态创建Image并设置其src属性触发浏览器加载,监听 load 和 error 事件来统计已完成的数量,代码如下:
如果资源很多要加载比较久,可以加一个最大时长限制,超过时限后,即使没加载完也进去首页(资源依然在后台异步加载),这样可以让用户可以尽快与页面互动,减少用户在等待页的流失。
问题5:字体预加载及展示问题
字体文件和图片一样也需要预加载,不过是放在首页index.html中进行的。
这里有个坑,字体文件渲染时才会加载,如果页面预加载时没有地方用到,浏览器不会去下载,解决办法在页面看不见的地方放一个使用字体的标签,触发浏览器加载字体资源。
预加载字体后,发现 Loading 页使用了自定义字体的进度百分数一开始不显示,然后突然从大于 0 的某个百分数开始显示。
这里又是一个坑(与其说是坑,倒不如说是知识点的欠缺--!),自定义的字体在加载完成之前,字体如何展示取决于font-display属性(用法可参考这篇文章 font-display 的使用),这个属性支持 5 个值auto | block | swap | fallback | optional,大部分浏览器默认是block,表示在字体加载完成前,以一种”隐形“字体(或者说不带墨水的字体)显示文字,即文字占用空间但是不可见,这也是造成上面问题的源头。这个属性的另一个值swap,表示字体加载完成前,先以后备字体显示文字,待字体加载完后立即替换成自定义的字体。swap取值正好可以解决上面问题,代码如下:
问题6:IOS 上不支持音频自动播放
<audio> ios上无法自动播放,设置autoplay属性或者调用play()都不行,网上查了下遇到类似问题的不少,原因是 IOS 对此进行了限制,必须在用户发生交互行为时才能播放(避免了页面打开各种广告声音~~),从 Chrome 66 开始也加入了这项限制,更多细节查阅这边文章Chrome 66禁止声音自动播放之后。
比较常见的解决办法,是在用户触摸屏幕任何地方后开始播放音频,这样能在最早的时机开始音频的播放,而且兼容性好。
问题7:长按图片A分享图片B
活动页答题完成后的结果页(下面左图),用户可以长按保存图片或分享图片(下面右图)。
根据前面的解释,在结果页会对网页进行快照得到一张 base64 图片,并替换网页原来的内容,这样用户可以长按图片触发微信的操作弹窗,这里的长按的图片和被分享图片必然是同一样的图片。但是,需求有要求看到的内容和分享的图片是不一致的,很矛盾。
确实很矛盾,但还是得想办法。对比上面两个图片发现只有底部不一样,所以思路是在分享图片(下面右图)上方盖一层(下面左图),这样就可以达到预期效果:看到的是左图,长按食物图片时分享的是右图。
但是使用这种遮罩的方式,又会产生一个新的问题,长按二维码时,分享出去的是二维码而不是上面右图,这是因为长按事件是传递到了遮罩层上的二维码图片,触发了图片默认的长按行为。
解决办法是将遮罩层的根元素加上pointer-event: none属性,关于该属性的用法可参考这篇文章 pointer-events,一个神奇的css属性。这个属性可以让事件忽略某个元素及其子节点,仿佛这个元素不存在一般。添加该属性后,长按事件可以透过遮罩层直达底层的分享图片,从而触发待分享图片的长按事件。经过这样处理后,无论长按界面哪个地方(按钮除外)都分享的是上面右图,问题解决。
问题8:微信长按图片无法识别其中的二维码
该情况出现在部分 iphone 手机上,安卓出现很少。起初怀疑是二维码图片太小导致的,更换一张大图就可以识别了,正准备更换大图,发现同事用一张同等尺寸的二维码替换后,之前不能识别的手机却可以识别了,这就纳闷了。。。
对比了下我们两个生成的二维码,发现我的二维码密度较低,看了下生成二维码的工具,还有一个容错率可选,容错率越高,二维码密度越高,我的二维码容错率选的是 7%,同事选的是 30%,提高容错率后重新生成二维码替换,问题解决。
总结
上面是以我的视角列举了一些开发遇到的问题,还有一些其他小问题在这里没有列举。活动中的动效是 UI 的同事精心调制的,其中也有不少值得学习的地方。
这次活动页的开发过程中所踩过的坑,也暴露出部分知识点的欠缺,这部分后面是需要“补课"的。