html2canvas生成固定宽度图片(内容完全显示)

最终效果图
需求

html2canvas生成图片,不会随着屏幕窗口rize大小变化而变化,但页面上是正常显示 。无论移动端或pc端都生成固定宽度(1300px),且生成内容完全展示,这个宽度根据自己的需求变化。

实现思路
  1. 父页面写两个一样的兄弟组件OriginalBoard和FinalBoard2
  2. OriginalBoard组件默认正常显示
  3. FinalBoard2组件隐藏(使用z-index)
  4. OriginalBoard组件触发点击按钮,把值传给父组件index,再通过index传给FinalBoard2
  5. 两个兄弟组件的样式共用一份,如对生成图片的页面显示有要求的话,可以再单独修改。
用html2canvas生成图片需要特别注意以上几点:
  1. 生成图片是基于dom节点来生成的。

  2. 因为是基于dom节点来生成的,所以样式千万不能写display: none,visibility: hidden,以及使用变量去控制是否展示只能用z-index属性来控制,因为z-index只是隐藏起来,显示在图层下面,所以不影响生成图片。

  3. 生成图片大小及展示的内容是根据你当前屏幕拖动来决定的,也就是说你当前屏幕宽是450px,那么生成的图片宽度就是450px,超出宽度部分的内容则不会显示在图片里。而且因屏幕拖动挤压的内容也不会显示(前提是你echarts做了resize图表自适应,或是页面做了自适应)


    拖动屏幕时会挤压数据,展示不全
  4. 生成图片的内容是根据你当前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>

待补充:

  1. 针对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;
      });
    },
  1. 生成图片后赋值给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);
        // }
      });
    },

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容