2020年8月

  转眼间,来到公司已经两个月了,做的第一个项目刚刚上线,因为项目用到的react、taro、ts都是第一次使用,在这个过程中,遇到了许许多多的问题,所以在这里记录一下。

1 项目概述

  日行一善是腾讯公益下的一个子项目,一期需求主要有三个页面,列表页、个人列表页、详情页。

  列表页展示很多从公益导入的活动,点击列表进入详情页。

  进入详情页时如果没有登录,则跳转登录页面登录,详情页有项目信息,邀请人信息、打卡信息、打卡日历、邀请弹窗、荣誉弹窗、打卡按钮等。点击打卡后进入支付界面,支付成功后打卡信息和打卡日历做相应的改变。

  已经开始打卡的活动会出现在个人列表页中,项目下方会显示对应的打卡信息。

  咋一看,好像没有几个页面,应该会很简单,但是真正开发的时候还是有比较多的问题的。

2 框架选型

  最开始使用的是cdc比较成熟前端开发流水线webshop,但是因为后面要出小程序版本,所以转成了taro框架,第一次使用taro框架,初始化时会选择jsx还是tsx,我之前读到过一篇文章,说能使用ts就尽量使用ts,虽然在开始的时候可能会多花一些时间,但是越到后面他是重要性越来越大。所以我果断选择了tsx。

  虽然是第一次使用taro,但是我看过文档之后发现,taro框架还是比较人性化的,基本上就是换了标签的react,生命周期什么的基本相同,只是稍微多了一些生命周期。

3 组件拆分

  列表页和个人列表页都是常规列表,比较简单,个人列表页和详情页都有用到打卡信息,打卡信息可以抽出来做成一个组件。项目的主要难点在详情页,逻辑也比较复杂,一个是因为项目存在多种状态,还未开始、已经关联、已经打卡、已经结束等,不同的状态打卡信息和打卡日历的显示会有所不同,荣誉弹窗的状态也有所不同,还有一个涉及到是否登录,支付是否成功等。

  根据视觉可见,多个地方存在按钮,有多个弹窗,打卡日历和打卡信息内部比较复杂等,所以将按钮、弹窗、日历、打卡信息分别抽出作为组件。登录和支付也需要作为单独的组件,一个是显得简洁,还有一个方便后面转小程序时写适配代码。

4 项目难点和问题

  在做项目时遇到的问题还是比较多的,我曾几度怀疑我不适合程序员,准备提桶回家种地了,但是脸皮太厚,舍不得我这盛世美颜,现在回头一看,好像又没那么多问题了,我想,可能是年纪大了,记忆力下降了吧。

4.1 难点

  ①  打卡日历,之前没有做过日历相关的项目,感觉无从下手,还是要感谢神奇的网络呀,只有你想不到的,没有网上找不到的,主要在不知道如何判断一个月有多少天,额,好像这是小学的知识没有学好,网上找了之后才知道,4,6,9,11有30天,2月一般是28天,如果闰年是29天,其他月份都是31天。闰年分为普通闰年(year % 4 === 0 && year % 100 !== 0)和世纪闰年(year % 400 === 0)。因为日历需要横向滚动,但是taro标签在h5设置scrollLeft不生效,最终是通过判断,非小程序的话使用div代替。

  ② 邀请卡和荣誉卡,这个需要用户可以保存为图片,这是在我写好样式后才知道的,为了方便,我想直接把dom节点变成截图,找到了一个库dom-to-image,因为这个库很久没有维护了,而且导出图片有点模糊,想换成html2canvas,但是发现其官方文档写着还处于实验期,不推荐生产环境使用,所以把dom-to-image改成了es6,解决一下导出模糊的问题。

  另外一个问题是,在微信浏览器中不能通过js保存图片,只能先把图片变成img标签,让用户主动长按保存,最终,经过多方权衡,我使用了非常繁琐的canvas原生api。

  不过,在这个过程中也了解到了很多东西,详见网页截图方案总结,本来想多写一些的,但是写着写着发现网上有一模一样的,而且口才比我好,讲的比我详细,就没有动力再写下去了。

4.2 问题

  ① 做上拉加载更多时的兼容问题,写好之后在谷歌可以,但是微信浏览器中总是不行,需要用到兼容写法:const scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;

  ② 在imac中使用taro命令时需要添加sudo,不然会报权限不够。

  ③ 使用Taro.navigateTo时不会触发componentWillUnmount和componentDidMount,但是会触发componentDidShow和componentDidHide。

5 总结

  好像在做的过程中遇到的问题挺多的,但是现在就是想不起来,感觉大脑一片空白,看来下次还是遇到问题之后要马上记下来才行。

  最近在做一个项目,写好样式后,发现在微信端需要保存图片,为了方便(偷懒),第一个想到用什么办法把dom节点直接变成图片。在知识的海洋一番畅游,发现了几种方案,网上现有的文章都不是很齐全,所以在这里汇总一下。

概述

  网页截图可以在后端进行,也可以在前端进行,后端比较常用的是用无头浏览器phantomjs配合实现截图。后端截图适用于一次截图保存,多次使用的情况。如果截图次数较多,请求比较频繁的话,对服务器的压力还是比较大的,这种情况,最好的方式就是在前端实现截图,本文主要介绍前端截图方案。

1  原理

  原理1:把内容绘制到canvas上,然后通过toDataURL、toBlob等得到图像资源,再根据情况转换为不同的图片格式。

  原理2:使用svg,通过createObjectURL或encodeURIComponent处理svg得到图像资源,可以把svg绘制到canvas。

  原理3:使用免费或付费现成API,pdfcrowd、web2pdfconvert、url2png等。

2  方案

  方案1:将内容直接绘制到canvas。

  方案2:遍历dom节点绘制到canvas。

  方案3:将dom节点处理后嵌入到svg的foreignObject中。

2.1  各种方案优缺点对比

方案1(直接绘制)方案2(遍历dom)方案3(嵌入svg)
优点可完全还原设计稿简单快捷,方便复用更简单快捷
缺点不能复用,代码繁琐需要考虑各种复杂的css样式搭配,各种特殊标签,精确度问题等,可能出现部分内容丢失等情况svg中不允许外部资源(js,css,img的url等),svg中不支持执行js,需要经过处理,也不能完全还原

2.2  根据以上方案,比较成熟的解决方式有

  方案1(直接绘制):原生canvas的api(代码复杂,繁琐)、konva.js(面向对象方式,比原生好用一点)

  方案2(遍历dom):html2canvas.js(导出图片格式需要搭配canvas2image.js,虽然官方文档说处于试验环境,还有重大改版,不推荐正式使用,但是14年就被Twitter等用于生产环境)、dom-to-image.js(自带导出图片格式,但是很久没有维护了)

  方案3(嵌入svg):rasterizeHTML.js(可兼容方案2,也有很多限制详见Limitations)

3  常见问题

3.1  图片跨域问题

       js异步获取图片在不同源的情况下会存在跨域问题。

       ① 服务器设置cors,前端设置img的属性crossOrigin='anonymous'。

       ②  如果图片位于第三方,不能设置cors的话,可以通过服务器代理把图片代理到当前域下。

       注意:因为有些图片服务器设置不同,如果网页其他地方有img标签请求相同的图片,可能会缓存不允许跨域的响应头,在服务器设置是需要注意。如果图片不多的情况可以在js请求的图片url中添加参数以区别img标签,防止缓存影响跨域设置。

3.2  导出图片模糊问题

     ①  如果按照一比一绘制导出图片,一般都会有点模糊,可以在绘制时将canvas和内容等比例放大绘制,导出时再缩放为原尺寸。

     ② 建议px为单位,单位如果使用不是px的话,在计算时会出现很多小数,导致模糊问题。

     ③ 使用img标签代替背景图。

    ④ canvas的抗锯齿是默认开启的,需要关闭抗锯齿来实现图像的锐化MDN: imageSmoothingEnabled

3.3  由于资源加载不全导致的内容不完整问题

     在绘制canvas时,如果部分资源没有加载完成,绘制内容会丢失,除了设置一定的延迟外,还可以通过Promise.all配合onload事件确保资源已经加载完成。

3.4  canvas如何居中绘制

     很多时候有居中绘制的需求,可以通过ctx.measureText或者img.width获取宽度,然后x = (总宽度 - 绘制宽度) / 2。

ctx.measureText还可以用来绘制自动换行,超出省略等。

3.5  canvas绘制非正圆圆角

 // 绘制圆角并裁剪
 const drawRoundedRect = function (ctx, x, y, width, height, r) {
   ctx.save();
   ctx.beginPath();
   ctx.moveTo(x + r, y);
   ctx.arcTo(x + width, y, x + width, y + r, r);
   ctx.arcTo(x + width, y + height, x + width - r, y + height, r);
   ctx.arcTo(x, y + height, x, y + height - r, r);
   ctx.arcTo(x, y, x + r, y, r);
   ctx.closePath();
   ctx.clip();
 }