所见即所得——HTML转图片组件开发

发表于 2年以前  | 总阅读数:684 次

前言

在我们日常开发中一定会遇到"所见即所得"的需求,如导出查询表格中的内容为 Excel 表格——《前端导出 Excel,让后端刮目相看》(https://juejin.cn/post/7030291455243452429)、通过后台网页配置实现配置预览页与实际页面展示的统一——《从零开发一款可视化大屏制作平台》(https://juejin.cn/post/6937257727106220040)。

今天我们也来实现一个"所见即所得"的需求:将用户所见网页提取为图片。

方案 1:最短步骤实现结果

第一个想到的方案就是通过浏览器自带的网页另存为图片去实现。

但这种方法显然是不可行的。第一需要提示用户操作进行繁琐的操作,第二无法达到局部提取为图片的效果。

方案 2:达成初步可行方案

通过调研发现,可以使用 html2canvas(http://html2canvas.hertzen.com/) 将网页先转换为 canvas 数据。再将其转换为图片的方法,最终实现我们想要的功能。

引入 html2canvas

cnpm install --save html2canvas

HTML

<div class="box">
    <!-- 将可保存为图片的内容 通过一个 标签 框起来 -->
    <div id="screenshot-box">
      <!-- 内容可以随意 -->
      <p>名称:<a-input v-model="formState.name" /></p>
      <p>年龄:<a-input v-model="formState.age" /></p>
      <p>
        班级:
        <a-radio-group v-model="formState.class">
          <a-radio value="1">班级1</a-radio>
          <a-radio value="2">班级2</a-radio>
        </a-radio-group>
      </p>
    </div>
    <a-button  @click="onSaveCanvas">保存为图片</a-button>
  </div>

JS

// 点击保存为 Canvas
    onSaveCanvas(){
       // 这里的类名要与点击事件里的一样
        const canvas = document.querySelector('#screenshot-box');
        let that = this;
        html2canvas(canvas,{scale:2,logging:false,useCORS:true}).then(function(canvas) {
          const type = 'png';
          let imgData = canvas.toDataURL(type);
          // 图片格式处理
          let _fixType = function(type) {
          type = type.toLowerCase().replace(/jpg/i, 'jpeg');
          let r = type.match(/png|jpeg|bmp|gif/)[0];
          return 'image/' + r;
        };
            imgData = imgData.replace(_fixType(type),'image/octet-stream');
            let filename = "htmlImg" + '.' + type;
            // 保存为文件
            //  以bolb文件下载
 that.downFileToLocal(filename,that.convertBase64ToBlob(imgData))
        });
    },

如此我们便实现了初步的功能

当然,我们也可以设置一个预览图片来预览我们将要导出的图片

HTML

<img :src="previewPic" alt="预览图片">

JS

this.previewPic = URL.createObjectURL(that.convertBase64ToBlob(imgData));

展示效果

将方案进行拓展并升级

需求止步于此,但秉承着"将事情做的更好"的我们岂能止步于此。

实现 HTML 导出为 Word

我们需要通过 html-docx 来实现导出为 Word(导出 Word 目前只支持原生 HTML + CSS)。

引入 html-docx

cnpm install --save html-docx-js

HTML

<div id="export-word">
      <table border >
        <tr>
          <th>姓名</th>
          <th>年龄</th>
        </tr>
        <tr>
          <td>贾维斯</td>
          <td>2</td>
        </tr>
      </table>
    </div>
    <a-button  @click="onWordExport">导出为word</a-button>

JS

onWordExport(){
        var contentHtml = document.getElementById("export-word").innerHTML;
        const cssHTML = `table {
                          width: 200px;
                          border: 1px solid #ccc;
                          color:red;
                        }`
        var content = `<!DOCTYPE html><html>
            <head>
                <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
                <style>
                    ${cssHTML}
                </style>
            </head>
            <body>
                ${contentHtml}
            </body>
            </html>`
        var converted = htmlDocx.asBlob(content,{orientation:"landscape"});
        this.downFileToLocal('word文件名.docx',converted)
    }

展示效果

如此我们便实现了导出 HTML 为 Word。

实现 HTML 导出为 PDF

目前市面上 HTML 导出 PDF 的实现方式有多种,如 jsPDF (https://github.com/parallax/jsPDF)、iText (https://blog.csdn.net/weixin_43897590/article/details/124729389)、wkhtmltopdf (https://github.com/wkhtmltopdf/wkhtmltopdf) 等。在不同情况下我们应该使用不同的解决方案:

方案 优点 缺点 分页 图片 表格 链接 中文 特殊字符
jsPDF 1、整个过程在客户端执行(不需要服务器参与),调用简单 1、生成的 pdf 为图片形式,且内容失真 支持 支持 支持 不支持 支持 支持
iText 1、功能基本可以实现,比较灵活 2、生成 pdf 质量较高 1、对 html 标签严格,少一个结束标签就会报错;2、后端实现复杂,服务器需要安装字体;3、图片渲染比较复杂 支持 支持 支持 支持 支持 支持
wkhtmltopdf 1、调用方式简单;2、生成pdf质量较高 1、服务器需要安装 wkhtmltopdf 环境;2、根据网址生成 pdf,对于有权限控制的页面需要在拦截器进行处理 支持 支持 支持 支持 支持 支持

今天我们使用在客户端执行(不需要服务器参与)的方式——jsPDF。

导入 jsPDF

npm install --save jspdf

HTML

 <a-button  @click="onPDFExport">导出为PDF</a-button>

JS

// 导出为 PDF
    onPDFExport(){
      const canvas = document.querySelector('#screenshot-box');
      html2canvas(canvas).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;
            //a4 纸的尺寸[595.28,841.89],html 页面生成的 canvas 在 pdf 中图片的宽高
            let imgWidth = 595.28;
            let imgHeight = 592.28/contentWidth * contentHeight;

            let pageData = canvas.toDataURL('image/jpeg', 1.0);

            let pdf = new jsPDF('', 'pt', 'a4');

            //有两个高度需要区分,一个是 html 页面的实际高度,和生成 pdf 的页面高度(841.89)
            //当内容未超过 pdf 一页显示的范围,无需分页
            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('content.pdf');
      })
    }

展示效果

如此我们便实现了导出 HTML 为 PDF。

将功能封装为组件

实现一次 HTML 导出图片需要写的代码太多,很多参数也需要按需定制。是否能够将其封装成组件呢?

我们可以通过 Vue 的插槽 (https://v2.cn.vuejs.org/v2/api/#v-slot) 将我们导出的内容进行插入

完整组件

<template>
  <div class="box">
    <!-- 将可保存为图片的内容 通过一个 标签 框起来 -->
    <div :id="id">
        <!-- 需要导出的内容 -->
        <slot></slot>
    </div>
  </div>
</template>
<script>
//引入 html2canvas
import html2canvas from 'html2canvas';
export default {
  name: "Html2Image",
  props: {
    id: { // id
      type: String,
      default: 'export-box',
    },
    filename: { // 导出文件名称
      type: String,
      default: 'htmlPic',
    },
    outType:{ // 导出类型
      type: String,
      default: 'png',
      validator(value) {
        return ['png', 'jpg', 'bmp','jpeg','gif'].includes(value)
      }
    },
    isFile: { // 是否为导出文件
      type: Boolean,
      default: false,
    },

  },
  methods: {
    //点击保存为 Canvas
    onSaveCanvas(){
       // 这里的类名要与点击事件里的一样
        const canvas = document.querySelector(`#${this.id}`);
        let that = this;
        html2canvas(canvas,{scale:2,logging:false,useCORS:true}).then(function(canvas) {
          const type = that.outType;
          let imgData = canvas.toDataURL(type);

          // 图片格式处理
          let _fixType = function(type) {
            type = type.toLowerCase().replace(/jpg/i, 'jpeg');
            let r = type.match(/png|jpeg|bmp|gif/)[0];
            return 'image/' + r;
          };
          imgData = imgData.replace(_fixType(type),'image/octet-stream');
          if(!that.isFile){
              const retn = URL.createObjectURL(that.convertBase64ToBlob(imgData))
              that.$emit('onExport',retn)
              return retn
          }
          //  以 bolb 文件下载
          that.downFileToLocal(that.filename + '.' + type,that.convertBase64ToBlob(imgData))
        });
    },
    // base64 转化为 Blob 对象
    convertBase64ToBlob(imageEditorBase64) {
      let base64Arr = imageEditorBase64.split(",");
      let imgtype = "";
      let base64String = "";
      if (base64Arr.length > 1) {
        //如果是图片 base64,去掉头信息
        base64String = base64Arr[1];
        imgtype = base64Arr[0].substring(
          base64Arr[0].indexOf(":") + 1,
          base64Arr[0].indexOf(";")
        );
      }
      // 将 base64 解码
      let bytes = atob(base64String);
      //let bytes = base64;
      let bytesCode = new ArrayBuffer(bytes.length);
      // 转换为类型化数组
      let byteArray = new Uint8Array(bytesCode);

      // 将 base64 转换为 ascii 码
      for (let i = 0; i < bytes.length; i++) {
        byteArray[i] = bytes.charCodeAt(i);
      }
      // 生成 Blob 对象(文件对象)
      return new Blob([bytesCode], { type: imgtype });
    },
    // 下载 Blob 流文件
    downFileToLocal(fileName, blob) {
      // 创建用于下载文件的 a 标签
      const d = document.createElement("a");
      // 设置下载内容
      d.href = URL.createObjectURL(blob);
      // 设置下载文件的名字
      d.download = fileName;
      // 界面上隐藏该按钮
      d.style.display = "none";
      // 放到页面上
      document.body.appendChild(d);
      // 点击下载文件
      d.click();
      // 从页面移除掉
      document.body.removeChild(d);
      // 释放 URL.createObjectURL() 创建的 URL 对象
      window.URL.revokeObjectURL(d.href);
    },
  },
};
</script>

Attributes

参数 类型 说明
id String 唯一 ID,盒子内容将作为导出内容
filename String 导出的文件名称(不带文件类型后缀)
outType String 导出文件类型
isFile Boolean 是否导出为文件,true 将下载文件,false 返回 bolb 路径

Methods

方法 名称 说明
onSaveCanvas 执行导出或保存方法 若 isFile 为 true 则保存文件,若 isFile为false 则返回 bolb 路径

Events

方法 名称 说明
onExport 导出方法 当触发导出/下载时会触发该方法输出 Bolb 路径 类型 String

使用组件

HTML

<Html2Image ref="html2Image" @onExport="exportPic" >
      <div class="export-content" style="border: 1px solid #F0F;width:200px">
        <div>1</div>
        <div>2</div>
        <div>3</div>
      </div>
    </Html2Image>
<a-button  @click="onExportImgByComponent">组件导出为图片</a-button>

JS

// 引入组件
import Html2Image from '@/components/Html2Image/Html2Image.vue'
// 使用组件
components: { 
    Html2Image
},

// methods
onExportImgByComponent(){
      this.previewPicComponent =this.$refs.html2Image.onSaveCanvas()
    },
exportPic(baseUrl){
       // 赋值导出图片的 blob 路径
      this.previewPicComponent = baseUrl;
    },  

Q&A

Q. 为什么外网图片展示不出?

A: 设置 html2canvas 方法中 useCORS 为 true 即可。

最后要说的

项目地址: https://github.com/FireSmallPanda/vuexDemo.git

HTML导出为图片组件地址: https://github.com/FireSmallPanda/vuexDemo/tree/master/src/components/ImageUpload

根据需求目前只封装了 HTML 导出为图片,相信大家可以依葫芦画瓢将导出 Word 和 PDF 也一并封装为组件。

参考文章

《前端实现将页面保存成图片功能》(https://blog.csdn.net/liuzemtx/article/details/109307540)

《base64 字串转 Blob 文件流,Blob 文件流再下载到本地》(https://blog.csdn.net/xiao970615/article/details/126305092)

《render-html-to-pdf》(https://gitcode.net/mirrors/linwalker/render-html-to-pdf?utm_source=csdn_github_accelerator) 《HTML 页面导出为 PDF》(https://blog.csdn.net/lisheng19870305/article/details/124158191)

本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/UZD2sICXbeeJu2TvkLR5AA

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237267次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8108次阅读
 目录