这次主要记录有关前端截图和拼图等处理。
缘由:接了一个活动需求,要求页面打开之后,可以长按触发保存图片,并且图片下方需要带上图和二维码的内容,以方便图片分享到朋友圈后可以长按识别二维码打开页面。(有时候纯分享页面链接到朋友圈,好友未必会点进去,图片的分享方式比点链接高一些吧~)
接到需求后
虽然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在使用的时候要加一下参数,以及页面的背景色要处理一下。
这块实际上简单化,在页面渲染完后生成图片覆盖在页面上方,用绝对定位处理,并透明展示。
img.style.cssText = 'width:100%;height:100%;position:absolute;top:0;left:0;right:0;bottom:0;opacity:0;z-index:9;';
综上,基本完成了截图+拼图的实现,其实回想起来实现上很简单,就是实现过程要解决各种各样的问题,不过方法总比问题多~
其实期间还遇到过一些坑,以及还没解决但最后和需求无关紧要的问题:
最后补充一下文中没提及的参考资料:
canvas渐变
关于drawImage
原文:https://segmentfault.com/a/1190000022419187
html2canvas 传入的是 dom对象。这是一个异步函数。可以截图指定元素区域。html2canvas 的 useCORS 默认是False(跨域)。如果不跨域,则截图中无法加载跨域图片。
在开发中需要对某个网站进行自动截图,然后保存到服务器的操作,自己写一个截图功能太浪费时间,精准像素推荐大家使用这种免费的网页截图接口网站来完成,简单快捷。
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!