vue使用html2canvas实现保存图片功能
记录一下html2canvas实际开发使用方法和遇到的问题以及解决方案
功能需求:html生成图片(图片格式不限),长按图片能够保存到本地,主要在移动端
1、首先安装html2canvas
npm安装
npm install --save html2canvas
yarn安装
yarn add html2canvas
直接引入
<script type="text/javascript" src="http://html2canvas.hertzen.com/dist/html2canvas.min.js"></script>
2、具体使用方法
新建htmlToImg.js文件
// 导出页面为PDF格式 import html2Canvas from 'html2canvas' import JsPDF from 'jspdf' export default { install(Vue, options) { Vue.prototype.getPdf = function (idName) { var title = this.htmlTitle // 导出名称 var type = this.downType // 导出类型 true ->图片 false-> pdf var htmlID = document.getElementById(`${idName}`) // window.pageYoffset = 0; // 如果有滚动条影响,会导致导出的内容不全,可以直接设置导出前置顶 // document.documentElement.scrollTop = 0; // document.body.scrollTop = 0; html2Canvas(htmlID, { allowTaint: true, // scrollY: 50, // 偏移量吧,如果有滚动条影响,但是不想设置滚动条置顶, 可以设置这个,但是要计算滚动了多少 // scrollX:0, // x:0, // 距离左边距离 // y:10, // width: 1000, // height: 800, // 下面两个用来提高清晰度 dpi: window.devicePixelRatio * 4, //将分辨率提高到特定的DPI 提高四倍 scale: 4, //按比例增加分辨率 useCORS: true // 【重要】开启跨域配置 }).then(function (canvas) { let contentWidth = canvas.width let contentHeight = canvas.height //一页pdf显示html页面生成的canvas高度 let pageHeight = contentWidth / 592.28 * 841.89 //未生成pdf的html页面高度 let leftHeight = contentHeight //页面偏移 let position = 0 let imgWidth = 592.28 //a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高 // let imgHeight = 592.28 / contentWidth * contentHeight let imgHeight = 592.28 / contentWidth * contentHeight // 1.0 清晰度0-1 let pageData = canvas.toDataURL('image/jpeg', 1.0) if (type) { // 生成图片 //创建一个 a 标签,并设置 href 和 download 属性 const el = document.createElement("a"); // 设置 href 为图片经过 base64 编码后的字符串,默认为 png 格式 el.href = pageData; el.download = title; // 创建一个点击事件并对 a 标签进行触发 const event = new MouseEvent("click"); el.dispatchEvent(event); } else { // 生成 pdf //有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(841.89) //当内容未超过pdf一页显示的范围,无需分页 // l 横向 默认竖向 let PDF = new JsPDF('l', 'pt', 'a4') if (leftHeight < pageHeight) { PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight) } else { while (leftHeight > 0) { PDF.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight) leftHeight -= pageHeight position -= 841.89 if (leftHeight > 0) { PDF.addPage() } } } PDF.save(title + '.pdf') } }) } } }
然后在mian.js引入htmlToImg.js 或者直接在需要用到的组件内引入
import htmlToImg from '@/utils/htmlToImg.js' // 保存为图片 Vue.use(htmlToImg)
ticket.vue里面使用
<template>
<div class="ykt_app app_audit_ticket">
<!-- 准考证 -->
<div class="ticket_content_wrap" id="pdfDom">
<div class="ticket_top">
<div class="tick_title">{{ editData.recruitIssueName ? editData.recruitIssueName : "" }}准考证</div>
<div class="tick_top_content flex_bt">
<div class="card_no_wrap">
<div class="nomber_title">准考证号</div>
<div class="nomber">{{ editData.admissionTicket ? editData.admissionTicket : "" }}</div>
<div class="id_card">身份证号:{{ editData.idCard ? editData.idCard : "" }}</div>
</div>
<div class="logo_wrap">
<img src="../../../assets/images/logo_recruit.png" alt="" />
<!-- <img src="../../../../assets/images/svg/ymg_logo.svg" alt=""> -->
</div>
</div>
</div>
<div class="ticket_bottom_wrap">
<div class="content flex_bt">
<div class="top_24">
<div>姓名:{{ editData.username ? editData.username : "" }}</div>
<div class="top_16">性别:{{ editData.sex && editData.sex == 'man' ? '男' : "女" }}</div>
<div class="top_16">考场号:{{ editData.room ? editData.room : "" }}</div>
<div class="top_16">座位号:{{ editData.seatNumber ? editData.seatNumber : "" }}</div>
</div>
<div class="ks_img_wrap flex_center">
<img class="img" :src="imgChange(editData.bareheadedImage)" alt="">
</div>
</div>
<div class="content ticket_middle_content">
<div>报考单位:杭州余杭知乙新媒体发展有限公司</div>
<div class="top_16">报考岗位:{{ editData.postLabel ? editData.postLabel : "" }}</div>
</div>
<div class="content">
<div>考试时间:{{ editData.examinationTime ? editData.examinationTime : "" }}</div>
<div class="top_16">考试地点:{{ editData.address ? editData.address : "" }}</div>
</div>
</div>
</div>
<div class="recruit_btn_wrap tick_donwload" @click="getPdf('pdfDom')">下载准考证</div>
</div>
</template>
<script>
export default {
name: "Ticket",
data() {
return {
htmlTitle: "页面导出PDF文件名",
downType: true,
};
},
props: {
editData: { type: Object, required: true },
},
computed: {
imgChange() {
return (val) => {
console.log(val)
let url = "";
if (String(val).indexOf("http") > -1) {
url = val;
} else {
url = `${this.preFix}${val}`;
}
console.log(url);
return url;
};
},
},
created(){
this.preFix = localStorage.getItem("preFix");
console.log(this.preFix)
},
mounted(){
console.log(this.editData)
},
methods: {
signEditoUp() {
console.log(111111111);
},
},
};
</script>
<style scoped lang="scss">
.flex_bt{
display: flex;
justify-content: space-between;
}
.tick_donwload{
margin-top: 20px;
}
.app_audit_ticket {
background-color: #fff;
overflow: hidden;
.ticket_content_wrap {
width: 343px;
height: 564px;
margin-left: 16px;
margin-right: 16px;
margin-top: 16px;
//
background-color: #fff;
.ticket_top {
// height: 1.71rem;
background: #FCEEEE;
box-shadow: 0px 0px 14px 0px #EFEFEF;
border-radius: 8px 8px 0px 0px;
color: #303030;
font-size: 16px;
overflow: hidden;
.tick_title {
text-align: center;
margin-top: 24px;
}
.tick_top_content {
margin-left: 24px;
.card_no_wrap {
margin-top: 16px;
text-align: left;
.nomber_title {
font-size: 16px;
}
.nomber {
margin-top: 8px;
font-size: 18px;
}
.id_card {
margin-top: 17px;
margin-bottom: 24px;
font-size: 12px;
}
}
.logo_wrap{
width: 88px;
height: 88px;
img{
width: 100%;
}
}
}
}
.ticket_bottom_wrap {
width: 343px;
height: 402px;
box-shadow: 0px 0px 14px 0px #EFEFEF;
border-radius: 8px 8px 0px 0px;
color: #303030;
font-size: 14px;
text-align: left;
.content {
margin-left: 24px;
margin-right: 24px;
.ks_img_wrap {
width: 108px;
height: 136px;
background: #FCEEEE;
margin-top: 24px;
.img{
height: 100%;
width: 100%;
}
}
.top_24{
margin-top: 24px;
}
.top_16{
margin-top: 16px;
}
}
.ticket_middle_content {
padding-top: 24px;
padding-bottom: 24px;
margin-top: 24px;
margin-bottom: 24px;
border-top: 1px solid #EFEFEF;
border-bottom: 1px solid #EFEFEF;
}
}
}
}
// 按钮样式
.recruit_btn_wrap {
width: 343px;
height: 50px;
line-height: 50px;
text-align: center;
background: #CE2425;
border-radius: 4px;
margin: 0 16px;
margin-bottom: 16px;
color: #fff;
font-size: 18px;
}
.flex_center{
display: flex;
justify-content: center;
align-items: center;
}
.tick_donwload{
margin-top: 24px;
cursor: pointer;
}
</style>
3、注意事项
3.1 页面滑动时,截图不全,
解决方案:可在截图事件时,把页面滚动条置顶。比如:
document.body.scrollTop = 0 需另外引入rc4版本文件即可
3.2 不兼容CSS3
最好不要用css3样式去写需要截图的盒子,因为无法呈现。
3.3 图片跨域问题,截图区域存在动态跨域图片,导致这部分区域截图空白问题(图片跨域问题)
如遇截图中的图片无法渲染上去,则考虑是跨域问题。
// 方式1: // step1: 在img元素中添加 crossOrigin="anonymous",使得跨域图片得请求header中会带上Origin属性,但不会带上cookie和客户端ssl证书等信息 <div id="container"> 需要截图的部分 <img src="http://192.168.11.22/img/1111.png" crossOrigin = "anonymous"/> </div> <img id="imgId"> // step1: 配置 allowTaint、useCORS <script> html2canvas(document.getElementById("container"), { width: 200, // 根据需求进行配置截图的尺寸 height: 200, // 根据需求进行配置截图的尺寸 allowTaint: false, // 允许跨原始图像污染画布 useCORS: true, //尝试使用CORS从服务器加载图像 }).then(canvas => { const src = canvas.toDataURL('image/png') document.getElementById('imgId').setAttribute('src', src) }) </script> // step3:图片服务器配置 Access-Contorl-Allow-Origin: * // 方式2:直接让后台将跨域图片转成base64传给前端,然后再进行html2canvas截图
3.4 iOS13 .then无法回调的问题
解决方案:另外引入rc4版本文件就可以了
下载地址:https://github.com/niklasvh/html2canvas/releases/tag/v1.0.0-rc.4
3.5 兼容iOS遇到的问题0 iOS报 ‘maximum call stack size exceeded’
当我们用后端转过来的base64图片时,iOS会因为内容过大,超出最大调用堆栈。
解:可对base64进行转换,转换成blob,再由blob生成objectURL,代码如下:
function base64ToBlob(base64) { let parts = base64.split(';base64,'); let contentType = parts[0].split(':')[1]; let raw = window.atob(parts[1]); let rawLength = raw.length; let uInt8Array = new Uint8Array(rawLength); for (let i = 0; i < rawLength; ++i) { uInt8Array[i] = raw.charCodeAt(i); } return new Blob([uInt8Array], {type: contentType}); }, let url = URL.createObjectURL(base64ToBlob('base64字符串'))
3.6 兼容iOS遇到的问题1 IOS8只支持-webkit-flex布局,不支持flex布局
解决办法:
方式1: 在需要截图的区域不使用flex布局
方式2: 改html2canvas的源码,让 flex和-webkit-flex都设置相同的样式(即:返回 DISPLAY.FLEX)
3.7 兼容iOS遇到的问题2 html2canvas v1.3.3版本在IOS13.4.1系统中调用失败(同问题3.2)
step1: 移除 html2canvas 【npm uninstall html2canvas】
step2: 项目的package.json 中的 html2canvas 版本降低为【1.0.0-rc.4】--- "html2canvas": "^1.0.0-rc.4"
step3: 安装1.0.0-rc.4版本 【npm install --save html2canvas@1.0.0-rc.4】
3.8 兼容iOS遇到的问题3 html2canvas截图后渲染在动态生成 img 标签无效
// 不动态生成 img 标签,只是修改器src属性 <div id="container"> 需要截图的部分 </div> <img id="imgId"> <script> html2canvas(document.getElementById("container"), { width: 200, // 根据需求进行配置截图的尺寸 height: 200, // 根据需求进行配置截图的尺寸 allowTaint: false, useCORS: true, //图片跨域 }).then(canvas => { const src = canvas.toDataURL('image/png') document.getElementById('imgId').setAttribute('src', src) }) </script>
4、拓展内容
1、兼容ios注意事项:
① 不使用 flex 布局
② 不动态生成 img 标签,只是修改器src属性
③ 一定要使用html2canvas 1.0.0-rc.4.js版本 (重点注意)
④ rc4版本不支持ssr,需要head的script引入或者动态导入:import (‘html2canvas’).then(({default: html2canvas}) => {})
⑤ rc4版本在iOS端不支持css:LinearGradient! 会直接报错进入catch
2、如果截图内容过多,或者是弹框截图,加上setTimeout延时器会有效解决截图空白问题
5、配置参数表:
| 名称 | 默认 | 描述 |
|---|---|---|
| allowTaint | false | 是否允许跨域图像污染画布 |
| backgroundColor | #ffffff | 画布背景色(如果未在DOM中指定)。设置null为透明 |
| canvas | null | 现有canvas元素用作绘图的基础 |
| foreignObjectRendering | false | 如果浏览器支持,是否使用ForeignObject渲染 |
| imageTimeout | 15000 | 加载图像的超时时间(以毫秒为单位)。设置0为禁用超时。 |
| ignoreElements | (element) => false | 谓词功能,可从渲染中删除匹配的元素。 |
| logging | true | 启用日志以进行调试 |
| onclone | null | 克隆文档以进行渲染时调用的回调函数可用于修改将要渲染的内容,而不会影响原始源文档。 |
| proxy | null | 代理将用于加载跨域图像的网址。如果保留为空,则不会加载跨域图像。 |
| removeContainer | true | 是否清除html2canvas临时创建的克隆DOM元素 |
| scale | window.devicePixelRatio | 用于渲染的比例。默认为浏览器设备像素比率。 |
| useCORS | false | 是否尝试使用CORS从服务器加载图像 |
| width | Element 宽度 | canvas的宽度 |
| height | Element 高度 | canvas的高度 |
| x | Element X偏移 | 裁剪画布X坐标 |
| y | Element Y偏移 | 裁剪画布y坐标 |
| scrollX | Element X滚动 | 渲染元素时要使用的x滚动位置(例如,如果Element使用position: fixed) |
| scrollY | Element Y滚动 | 呈现元素时要使用的y-scroll位置(例如,如果Element使用position: fixed) |
| windowWidth | Window.innerWidth | 渲染时使用的窗口宽度Element,这可能会影响媒体查询之类的内容 |
| windowHeight | Window.innerHeight | 渲染时要使用的窗口高度Element,这可能会影响媒体查询之类的内容 |
官方网址:
http://html2canvas.hertzen.com/configuration
参考链接:
https://www.csdn.net/tags/Mtjacg0sNDI1NTYtYmxvZwO0O0OO0O0O.html,
https://blog.csdn.net/qq_37600506/article/details/122166962

浙公网安备 33010602011771号