将整个网页保存为图片是一个十分有趣的功能,常见于H5活动页的结尾页分享。以下则是项目中的一些小结和汇总。
实现html页面保存为图片
现有已知能够实现网页保存为图片的方案包括:
-
方案1:将DOM改写为canvas,然后利用canvas的toDataURL方法实现将DOM输出为包含图片展示的
data URI
-
方案2:使用
html2canvas.js
实现(可选搭配Canvas2Image.js
实现网页保存为图片) -
方案3:使用
rasterizeHTML.js
实现
解决方案的选择
-
方案1:需要手动计算每个DOM元素的
Computed Style
,然后需要计算好元素在canvas的大小位置等属性。
方案一的难点:
-
相当于完全重写了整个页面的布局样式,增加了工作量。
-
由于canvas中没有的对象概念,对于元素丰富、布局复杂的页面,不易重
构。
-
所有DOM元素改写进canvas会带来一些困难,例如:难以支持响应式,图片元素清晰度不佳和文字点击区域识别问题等。
-
方案2:该类功能中Github上stars最多(至今仍在维护),Stack Overflow亦有丰富的讨论。只需简单调用html2canvas方法并设定配置项即可。
-
方案3:该方案的限制较多,目前仅支持3类可转为canvas的目标格式
: 页面url,html字符串和document对象。
小结
: html2canvas是目前实现网页保存为图片功能的综合最佳选择。
html2canvas的使用方法
我的需求是,将项目的网址生成一个二维码,然后把二维码放到生成的海报中间。
// 生成二维码
// 使用qrcode.js,使用方法参考官方文档
// http://code.ciaoca.com/javascript/qrcode/
getQrCode:function(){
let codeEle = document.getElementById('code'); // dom元素
var typeNumber = 4;
var errorCorrectionLevel = 'L';
var qr = qrcode(typeNumber, errorCorrectionLevel);
qr.addData('http://resume.ethan.pub');
qr.make();
codeEle.innerHTML = qr.createImgTag();
}
html转canvas
html2canvas.js
可将一个元素渲染为canvas,只需要简单的调用html2canvas(element[, options]);
即可。下列html2canvas
方法会返回一个包含有<canvas>
元素的promise
:html2canvas(document.body).then(function(canvas) {
document.body.appendChild(canvas);
});
canvas转image
上一步生成的canvas即为包含目标元素的<canvas>
元素对象。实现保存图片的目标只需要将canvas转image即可。
这里的转换方案有2种
:
-
方案1:基于原生canvas的
toDataURL
方法将canvas输出为data: URI
类型的图片地址,再将该图片地址赋值给
<image>
元素的src属性即可 -
方案2:使用第三方库
Canvas2Image.js
,调用其convertToImage
方法即可
实际上,Canvas2Image.js
也是基于canvas.toDataURL
的封装,相比原生的canvas API对于转为图片的功能上考虑更为具体(未压缩的包大小为7.4KB),适合项目使用。
清晰度优化
生成图片之后会发现图片稍微有些模糊,最终图片的清晰度取决于第一步中html转换成的canvas的清晰度。
现有解决方案参考:
-
html5 canvas在高倍屏下变模糊的处理办法
-
html5 canvas绘制图片模糊的问题
其基本原理是,将canvas
的属性width
和height
属性放大为2倍(或者设置为devicePixelRatio
倍),最后将canvas的CSS样式width和height设置为原先1倍的大小。
含有跨域图片的配置
由于canvas对于图片资源的同源限制
,如果画布中包含跨域的图片资源则会污染画布,造成生成图片样式混乱或者html2canvas方法不执行等问题。
解决方案:
-
要求CDN的图片配置好
CORS
。CDN
配置好后,通过chrome开发者工具可以看到响应头中应含有Access-Control-Allow-Origin
的字段。 -
开启
html2canvas
的useCORS
配置项。即如下设置:
var opts = {useCORS: true};
html2canvas(element, opts);
完整demo代码
<div id="share">
<div class="content">
<div class="card-top">
<div class="text-wrap">
<p>Ethan — 前端工程师</p>
</div>
</div>
<div class="card-bottom">
<div class="info">洛阳亲故如相问,就说我在敲代码。</div>
<div class="code-con">
<div id="code"></div>
<div class="code-me">扫描二维码访问更多内容</div>
</div>
</div>
</div>
</div>
#share{
position: absolute;
-1; :
.content{
width: 300px;
height: auto;
#fff; :
0; :
0; :
}
.card-top{
width: 100%;
height: 210px;
background: url(http://ethanwp.oss-cn-shenzhen.aliyuncs.com/wp/2018/12/wBHYXkfkJ_E.jpg) center no-repeat;
100% 100%; :
1; :
display: flex;
flex-end; :
.text-wrap{
100%; :
height: 30px;
background: linear-gradient(360deg, rgba(0,0,0,0.3),rgba(0,0,0,0));
p{
width:100%;
height: 30px;
center; :
color: #fff;
16px; :
}
}
}
.card-bottom{
width: 100%;
.info{
width: 100%;
height: 20px;
20px; :
center; :
10px; :
color: #8692a4;
}
.code-con{
height: auto;
10px; :
center; :
#code{
width: 100%;
center; :
height: 100px;
img{
width: 100px;
height: 100px;
}
}
.code-me{
width: 100%;
text-align: center;
color: #8692a4;
}
}
}
}
handleShare(){
const _self = this;
//定义查找元素方法
function $(selector) {
return document.querySelector(selector);
}
var main = {
init:function(){
_self.show = true;
main.getQrCode();
},
//设置监听事件
getQrCode:function(){
let codeEle = document.getElementById('code');
var typeNumber = 4;
var errorCorrectionLevel = 'L';
var qr = qrcode(typeNumber, errorCorrectionLevel);
qr.addData('http://resume.ethan.pub');
qr.make();
codeEle.innerHTML = qr.createImgTag();
main.html2Canvas();
},
//获取像素密度
getPixelRatio:function(context){
var backingStore = context.backingStorePixelRatio ||
context.webkitBackingStorePixelRatio ||
context.mozBackingStorePixelRatio ||
context.msBackingStorePixelRatio ||
context.oBackingStorePixelRatio ||
context.backingStorePixelRatio || 1;
return (window.devicePixelRatio || 1) / backingStore;
},
//绘制dom 元素,生成截图canvas
html2Canvas: function () {
var shareContent = $("#share");// 需要绘制的部分的 (原生)dom 对象 ,注意容器的宽度不要使用百分比,使用固定宽度,避免缩放问题
var width = shareContent.offsetWidth; // 获取(原生)dom 宽度
var height = shareContent.offsetHeight; // 获取(原生)dom 高
var offsetTop = shareContent.offsetTop; //元素距离顶部的偏移量
var canvas = document.createElement('canvas'); //创建canvas 对象
var context = canvas.getContext('2d');
var scaleBy = main.getPixelRatio(context); //获取像素密度的方法 (也可以采用自定义缩放比例)
canvas.width = width * scaleBy; //这里 由于绘制的dom 为固定宽度,居中,所以没有偏移
canvas.height = (height + offsetTop) * scaleBy; // 注意高度问题,由于顶部有个距离所以要加上顶部的距离,解决图像高度偏移问题
context.scale(scaleBy, scaleBy);
context.translate(0, 0);
var opts = {
useCORS:true,//允许加载跨域的图片
scale:scaleBy, // 添加的scale 参数
canvas:canvas, //自定义 canvas
width:width, //dom 原始宽度
height:height //dom 原始高度
};
html2canvas(shareContent, opts).then(function (canvas) {
var context = canvas.getContext('2d');
// 【重要】关闭抗锯齿
context.mozImageSmoothingEnabled = false;
context.webkitImageSmoothingEnabled = false;
context.msImageSmoothingEnabled = false;
context.imageSmoothingEnabled = false;
_self.canvas = canvas;
var img = Canvas2Image.convertToImage(canvas, canvas.width, canvas.height);
img.style.width = canvas.width / scaleBy + "px";
img.style.height = canvas.height / scaleBy + "px";
document.getElementById('content').appendChild(img);
if (_self.percent > 0) {
_self.percent = 0
}
let sid = window.setInterval(() => {
_self.percent++
if (_self.percent >= 100) {
_self.hasShare = true;
window.clearInterval(sid)
}
}, 10)
$('#share').style.display = 'none'
});
}
};
//最后运行代码
if(this.isTap){
return;
this.isTap = false;
} else {
this.isTap = true;
if(this.hasShare){
this.show = true
} else {
main.init();
}
}
},
效果图
文章评论