这次主要记录有关前端截图和拼图等处理。
缘由:接了一个活动需求,要求页面打开之后,可以长按触发保存图片,并且图片下方需要带上图和二维码的内容,以方便图片分享到朋友圈后可以长按识别二维码打开页面。(有时候纯分享页面链接到朋友圈,好友未必会点进去,图片的分享方式比点链接高一些吧~)
接到需求后
toDataURL
可以获取到图片的dataUrl
虽然html2canvas好用,但是它还是有一些需要注意到的地方,开发过程中遇到的主要是两个问题:a. 涉及的图片跨域 b. 不支持渐变色字的截图
先说一下html2canvas的基本使用:它接受一个Element参数和Options对象参数,前者指的是将要截图的DOM,后者则是截图的相关配置.可以看这里>>
目前html2canvas方法返回的是一个promise(在前期在线预研的时候,用cdn引了一个比较旧的版本,那时的html2canvas还不够成熟,不会promise,导致用的时候报then is not a function)
实际使用的代码如下,很简单。
const contentDom: HTMLElement | null = document.querySelector('.draw-target'); if (contentDom) { html2canvas(contentDom, { x: 0, y: 0, // 支持跨域访问 useCORS: true, // 移除不需要汇入的元素,越少越好 ignoreElements: element => { // 这里指定了忽略.no-pic的元素 if (element.className.indexOf('no-pic') !== -1) { return true; } return false; } }) .then(canvas => { canvas.getContext('2d').imageSmoothingEnabled = false; resolve(canvas.toDataURL('image/png')); }) .catch(error => { console.log('生成报错', error); reject(error); }); } else { reject('error'); }
代码很简单,但是页面涉及到图片资源跨域的时候,就得配合服务端解决图片跨域的问题了,这里要注意:其他域图片虽然能被页面展示出来,但不代表它能被脚本跨域访问。所以考虑截图需求的时候一定要判断好是否有跨域的图片资源,要让图片那边接受js的跨域调用,通常这个只需要nginx上配一下就好了。
首先已知,我们通过上面提到的截图,拿到了图片的data url,然后手里还有一个要拼接上的图片,可以用canvas和它的drawImage搞定。
已知,手里有两个图片的地址,实际上,我们要做的只是创建img并且给img.src给填上图片地址,在img.onload那里把img交给canvas的drawIamge去绘图即可,同样这里绘图也是考虑跨域问题的,不过我的需求里两个图片,第一个本身就是一个data url可以说没跨域了,或者说前面已经解决了,第二个可以考虑就和脚本放一起,那就没有跨域问题啦。
然后拼图要考虑到,图片之间尺寸大小不一,在我的需求里:A图(页面截图) 和 B图(纯图片),最终拼成一张C图,一般C图的宽度就按A图的宽度来用即可,也就是A图为基础图,B图适应A图的宽度来重新调整绘入时的长宽(widthFinalB = widthA base; heightFinalB = widthFinalB / widthB heightB) 这里简单起见,B图在设计稿上base为1.
先把img赋值部分封装成一个promise
const getImg2Draw = (src): Promise<ImageObj> => { return new Promise((resolve, reject) => { const img = new Image(); img.onload = () => { resolve({ img, width: img.naturalWidth, height: img.naturalHeight }) }; img.onerror = (err) => { reject(err) } img.src = src; }) }
这样最后promise会给我返回图片以及它的实际长宽,方便计算。
然后分别canvas将A图和B图画上canvas
// 获取A图 getContentImg().then(base64 => { // 获取截图部分 Promise.all([getImg2Draw(base64), // 获取二维码部分 getImg2Draw('./public/extra.png')]) .then((imgs) => { const [mainImg, extraImg] = imgs const $canvas = document.getElementById('canvas') as HTMLCanvasElement // 获取截图宽度,截图是基于html的所以也是实际页面宽度 const mainWidth = mainImg.width; // 获取截图的高度 const mainHeight = mainImg.height; // B的原始宽度 const extraWidth = extraImg.width; // B的原始高度 const extraHeight = extraImg.height; // 设置canvas的宽高 $canvas.width = mainWidth // 计算出基于A图宽的比例B图最终高度 const appendHeight = mainWidth / extraWidth * extraHeight // 计算整体高度 const allHeight = mainHeight + appendHeight $canvas.height = allHeight // 清空旧内容 canvas.clearRect(0, 0, mainWidth, allHeight) // 先把A图怼上去 canvas.drawImage(mainImg.img, 0, 0, mainWidth, mainHeight) // 再把B图部分怼上去 canvas.drawImage(extraImg.img, 0, mainHeight, mainWidth, appendHeight) // 得到最终绘图 const img = new Image(); img.src = $canvas.toDataURL('image/png'); document.body.appendChild(img); }).catch(err => { console.error(err) })
这个时候遇到另外一个问题:拼图的背景色,由于我这边拼图是两块图直接拼在一起,如果各自有各自的背景色倒是没什么关系,拼图的时候各自保留各自背景色即可,但我这次用的是统一背景色,而且还是渐变的背景色。如果两张图各自掌管各自颜色,很容易出现拼接时有一条线,除非画图的时候把两边接触面的背景色计算好,以便无缝衔接。
这里直接由最终绘图的canvas来自行画背景色,两张图处理时都不要自己的背景色,也就是先画布画好背景,然后再两张图往画布贴上即可。
渐变背景的画法:
// 背景色用渐变色自己画 const gradientBg = canvas.createLinearGradient(0, 0, 0, allHeight) gradientBg.addColorStop(0, '#5D79A7') gradientBg.addColorStop(1, '#304D7C') canvas.fillStyle = gradientBg canvas.fillRect(0, 0, mainWidth, allHeight)
把这段画背景色放在绘AB图之前即可,但是可能html2canvas在使用的时候要加一下参数,以及页面的背景色要处理一下。
backgroundColor: null
这块实际上简单化,在页面渲染完后生成图片覆盖在页面上方,用绝对定位处理,并透明展示。
img.style.cssText = 'width:100%;height:100%;position:absolute;top:0;left:0;right:0;bottom:0;opacity:0;z-index:9;';
综上,基本完成了截图+拼图的实现,其实回想起来实现上很简单,就是实现过程要解决各种各样的问题,不过方法总比问题多~
其实期间还遇到过一些坑,以及还没解决但最后和需求无关紧要的问题:
最后补充一下文中没提及的参考资料:
canvas渐变
关于drawImage