最终效果图
需求
html2canvas生成图片,不会随着屏幕窗口rize大小变化而变化,但页面上是正常显示 。无论移动端或pc端都生成固定宽度(1300px),且生成内容完全展示,这个宽度根据自己的需求变化。
实现思路
- 父页面写两个一样的兄弟组件OriginalBoard和FinalBoard2
- OriginalBoard组件默认正常显示
- FinalBoard2组件隐藏(使用z-index)
- OriginalBoard组件触发点击按钮,把值传给父组件index,再通过index传给FinalBoard2
- 两个兄弟组件的样式共用一份,如对生成图片的页面显示有要求的话,可以再单独修改。
用html2canvas生成图片需要特别注意以上几点:
生成图片是基于dom节点来生成的。
因为是基于dom节点来生成的,所以样式千万不能写display: none,visibility: hidden,以及使用变量去控制是否展示。只能用z-index属性来控制,因为z-index只是隐藏起来,显示在图层下面,所以不影响生成图片。
-
生成图片大小及展示的内容是根据你当前屏幕拖动来决定的,也就是说你当前屏幕宽是450px,那么生成的图片宽度就是450px,超出宽度部分的内容则不会显示在图片里。而且因屏幕拖动挤压的内容也不会显示(前提是你echarts做了resize图表自适应,或是页面做了自适应)
拖动屏幕时会挤压数据,展示不全 生成图片的内容是根据你当前id范围来显示的。
1 安装插件
https://www.html2canvas.cn/html2canvas-about.html
npm install html2canvas
目录模块
2 页面引入使用
import html2canvas from "html2canvas";
- 全局生成图片
<!-- 父组件 index -->
template>
<div>
<!-- 默认生成尺寸 -->
<OriginalBoard @dataForFinalBoard="downloadImg" />
<!-- 固定宽度1270px -->
<FinalBoard2 :dataFromOriginalBoard="dataFromOriginalBoard"/>
</div>
</template>
<script>
import { OriginalBoard, FinalBoard2 } from './modules/index'
export default {
name: 'currentVipUser',
components: {
OriginalBoard,
FinalBoard2
},
data() {
return {
dataFromOriginalBoard: '', // 用于存储从originalBoard接收的数据,并传递给FinalBoard
}
},
methods: {
downloadImg(data){
this.dataFromOriginalBoard = data; // 更新数据,以便通过props传递给FinalBoard
}
}
}
</script>
<!-- OriginalBoard -->
<div
class="hour_box"
ref="oneHourImageDom"
>
<div class="current1">
<div class="current_title">
每小时支付情况
<span>(单位:万)</span>
</div>
<div
class="gen_img"
@click="oneHourImg()"
v-if="!isShowSearch"
>
<span class="gen_title">生成图片</span>
</div>
</div>
<!-- 柱状图 -->
<OneHourChart ref="oneHourChartData" />
</div>
<script>
import OneHourChart from './OneHourChart'
export default {
props: {
dataForFinalBoard: {
type: String,
default: ''
}
},
components: { OneHourChart },
created() {
this.init()
},
methods:{
init() {
// 这个入参每个接口都要传参,所以拿出来单独写,不然每个接口都得重写一遍
if (!this.timeRange) {
// 明分秒没有值时,日期后面加上23:59:59
this.queryParams.queryTime = moment(this.queryParams.queryTime).format('YYYY-MM-DD 23:59:59')
} else {
// 明分秒有值时,把时分秒和日期拼接起来
this.queryParams.queryTime = moment(this.queryParams.queryTime).format('YYYY-MM-DD') + ' ' + this.timeRange
}
this.$nextTick(() => {
// 调接口
this.$refs.oneHourChartData.getOneHour(this.queryParams); // 每小时支付情况
})
},
oneHourImg() {
// Math.random 随机数,可重复点击生成图片
const data = 'oneHourImg' + '_' + Math.random();
this.$emit('dataForFinalBoard', data);
}
}
}
</script>
<!-- FinalBoard2-->
<div
class="hour_box"
ref="oneHourImageDom"
>
<div class="current1">
<div class="current_title">
每小时支付情况
<span>(单位:万)</span>
</div>
</div>
<OneHourChart ref="oneHourChartData" />
</div>
<script>
import html2canvas from "html2canvas";
import OneHourChart from './OneHourChart'
export default {
props: ['dataFromOriginalBoard'], // 通过props接收来自父组件的数据
components: { OneHourChart },
// 监听到父组件index传值过来触发
watch:{
'dataFromOriginalBoard': {
immediate: true, // 如果需要组件实例化时立即触发
deep: true,
handler(newVal, oldVal) {
// 切割随机数
newVal = newVal.split('_')[0]
if(newVal == 'oneHourImg'){
this.oneHourImg();
}
}
}
},
methods:{
oneHourImg() {
html2canvas(this.$refs.oneHourImageDom, {
backgroundColor: '#202020',
}).then(canvas => {
var img = canvas.toDataURL("image/png");
var a = document.createElement("a");
a.href = img; // 将生成的URL设置为a.href属性
a.download = "每小时支付情况.png";
a.click();
a.remove(); // 下载完成移除a标签
});
}
},
}
</script>
- OneHourChart组件
<template>
<div
id="oneHour"
ref="oneHour"
class="hour_charts"
v-loading="oneHourLoading"
element-loading-background="rgba(0, 0, 0, 0.8)"
></div>
</template>
<script>
import * as echarts from 'echarts'
require('echarts/theme/macarons') // echarts theme
import { hourPayAmt } from "@/api/xxx";
export default {
props: {},
data() {
return {
oneHourLoading: false,
oneHourChart: null, // 每小时支付情况
}
},
mounted() {
// echarts图表自适应
window.addEventListener('resize', this.resize); // 添加监听
},
destroyed() {
// 移除监听,echarts自适应
window.removeEventListener('resize', this.resize)
},
methods: {
resize() {
this.oneHourChart.resize();
},
getOneHour(queryParams) {
this.oneHourLoading = true;
hourPayAmt(queryParams).then(({ code, data }) => {
if (code == 0) {
// this.$refs.oneHour这样写是因为接口要调用2次,不然会提示dom已加载渲染
this.oneHourChart = echarts.init(this.$refs.oneHour);
const option = {
tooltip: {
trigger: 'axis',
extraCssText: 'background: linear-gradient( 321deg, #FDFEFF 0%, #F4F7FC 100%);',
formatter: function (params) {//提示内容
let relVal = '<div style="margin-bottom:4px;font-size:12px;color:#1D2129;">' + params[0].name + '</div>';
for (let i = 0; i < params.length; i++) {
relVal += `
<div style="font-size:12px;min-width:164px;height:32px;background:rgba(255,255,255,0.9);box-shadow: 6px 0px 20px 0px rgba(33,87,188,0.1);margin-bottom:4px;border-radius:4px;display:flex;align-items:center;justify-content:space-between;padding: 0 8px;box-sizing:border-box;">
<span style="color:#4E5969;margin-right:10px;">
<span style="display:inline-block;width:10px;height:10px;border-radius:50%;background: #0D34FF;margin-right:5px;"></span>`
+ params[i].seriesName + `</span><span style="font-size:13px;color:#1D2129;">` + params[i].data + `</span>
</div>`
}
return relVal;
},
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
xAxis: {
type: 'category',
boundaryGap: true, // 坐标轴两边留白策略
data: data.map(item => item.hour + ':00'),
axisLine: {
lineStyle: { color: '#717579' },
},
},
yAxis: {
type: 'value',
axisLine: {
lineStyle: { color: '#8E8EA1' },
},
splitLine: { //网格线
lineStyle: {
color: '#2B2B2B',
type: 'dashed', // 线型为虚线
}
},
},
series: [
{
name: '支付金额',
type: 'bar',
barWidth: '20%', // 可以是具体像素值 '20px' 或百分比 '50%'
data: data.map(item => item.amt),
label: {
show: true, // 显示数据
position: 'top', // 数据显示的位置
textStyle: { // 字体样式
color: '#FFFFFF', // 标签字体颜色
fontSize: 14, // 标签字体大小
}
},
itemStyle: {
borderRadius: [10, 10, 0, 0], //(顺时针左上,右上,右下,左下)
color: new echarts.graphic.LinearGradient(0, 0, 0.7, 1, [
{ offset: 0, color: '#0D34FF' },
{ offset: 0.2, color: '#0DC2FF' },
{ offset: 1, color: '#202020' }
]),
},
},
],
};
this.oneHourChart.setOption(option);
}
}).finally(() => {
this.oneHourLoading = false;
});
},
}
}
</script>
<style src="@/assets/styles/salesBoard.scss" lang="scss" scoped></style>
待补充:
- 针对echarts图表生成图片(生成固定宽度,并且内容全部展示),可以在echarts图表里设置
getOneHour(queryParams) {
this.oneHourLoading = true;
hourPayAmt(queryParams).then(({ code, data }) => {
if (code == 0) {
this.oneHourChart = echarts.init(this.$refs.oneHour);
const option = {
tooltip: {
trigger: 'axis',
extraCssText: 'background: linear-gradient( 321deg, #FDFEFF 0%, #F4F7FC 100%);',
formatter: function (params) {//提示内容
let relVal = '<div style="margin-bottom:4px;font-size:12px;color:#1D2129;">' + params[0].name + '</div>';
for (let i = 0; i < params.length; i++) {
relVal += `
<div style="font-size:12px;min-width:164px;height:32px;background:rgba(255,255,255,0.9);box-shadow: 6px 0px 20px 0px rgba(33,87,188,0.1);margin-bottom:4px;border-radius:4px;display:flex;align-items:center;justify-content:space-between;padding: 0 8px;box-sizing:border-box;">
<span style="color:#4E5969;margin-right:10px;">
<span style="display:inline-block;width:10px;height:10px;border-radius:50%;background: #0D34FF;margin-right:5px;"></span>`
+ params[i].seriesName + `</span><span style="font-size:13px;color:#1D2129;">` + params[i].data + `</span>
</div>`
}
return relVal;
},
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
xAxis: {
type: 'category',
boundaryGap: true, // 坐标轴两边留白策略
data: data.map(item => item.hour + ':00'),
axisLine: {
lineStyle: { color: '#717579' },
},
},
yAxis: {
type: 'value',
axisLine: {
lineStyle: { color: '#8E8EA1' },
},
splitLine: { //网格线
lineStyle: {
color: '#2B2B2B',
type: 'dashed', // 线型为虚线
}
},
},
series: [
{
name: '支付金额',
type: 'bar',
barWidth: '20%', // 可以是具体像素值 '20px' 或百分比 '50%'
data: data.map(item => item.amt),
label: {
show: true, // 显示数据
position: 'top', // 数据显示的位置
textStyle: { // 字体样式
color: '#FFFFFF', // 标签字体颜色
fontSize: 14, // 标签字体大小
}
},
itemStyle: {
borderRadius: [10, 10, 0, 0], //(顺时针左上,右上,右下,左下)
color: new echarts.graphic.LinearGradient(0, 0, 0.7, 1, [
{ offset: 0, color: '#0D34FF' },
{ offset: 0.2, color: '#0DC2FF' },
{ offset: 1, color: '#202020' }
]),
},
},
],
};
this.oneHourChart.setOption(option);
if (queryParams.imgFlag) {
var image = document.getElementById('oneHour').style.width;
document.getElementById('oneHour').style.width = '1300px';
// 然后,立即调用 resize 方法更新图表大小
this.resize()
this.oneHourLoading = false;
var imageURL = this.oneHourChart.getDataURL({
// 指定图片导出的格式,'png', 'jpeg' 等
type: 'png',
// 导出的图片分辨率比例,默认为 1
pixelRatio: 1,
// 导出的图片背景色,默认不设置为透明背景
backgroundColor: '#fff'
});
var downloadLink = document.createElement('a');
downloadLink.href = imageURL;
downloadLink.download = 'echarts-image.png';
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
document.getElementById('oneHour').style.width = image;
console.log(window.innerWidth);
// 然后,立即调用 resize 方法更新图表大小
this.resize()
}
}
}).finally(() => {
this.oneHourLoading = false;
});
},
- 生成图片后赋值给img(固定宽度)
<div
class="hour_box"
ref="oneHourImageDom"
>
<div class="current1">
<div class="current_title">
每小时支付情况
<span>(单位:万)</span>
</div>
<div
class="gen_img"
@click="oneHourImg()"
v-if="!isShowSearch"
>
<span class="gen_title">生成图片</span>
</div>
</div>
<!-- 柱状图 -->
<OneHourChart ref="oneHourChartData" />
</div>
<img id="testimg" style="width: 1300px;height: auto;" />
<!-- OriginalBoard -->
oneHourImg() {
html2canvas(this.$refs.oneHourImageDom,{
backgroundColor: '#202020',
useCORS: true, // 允许跨域图片下载
}).then(canvas => {
// 下载生成原有图片(屏宽)
var originalImg = canvas.toDataURL("image/png");
var a = document.createElement("a");
a.href = originalImg; // 将生成的URL设置为a.href属性
a.download = "每小时支付情况.png";
// 原生成的图片赋值给最终显示的canvas(width:1270px)
// const originalImgSize = new Image();
// originalImgSize.src = originalImg;
document.getElementById('testimg').src = originalImg
html2canvas(document.getElementById('testimg'),{
backgroundColor: '#202020',
useCORS: true // 允许跨域图片下载
}).then(canvass => {
var img = canvass.toDataURL("image/png");
var a = document.createElement("a");
a.href = img;
a.download = "每小时支付情况.png";
a.click();
a.remove();
})
// originalImgSize.onload = () => {
// console.log('Image Width:', originalImgSize.width);
// console.log('Image Height:', originalImgSize.height);
// }
});
},