纯前端如何实现批量dom转图片,并下载成压缩包

往期精彩文章:

用云编译器半小时完成轮播组件紧急开发!被公司奖励500!

直接使用git pull拉取代码,被同事狠狠地diss了!

快收藏!超实用标签title属性重写,让同事对你刮目相看

需求背景

这几天捣鼓一些小东西,需要实现这样一个功能:将页面指定的dom批量下载成压缩包。功能大致如下:

将每条评论信息转成图片,然后放到一个压缩包里下载下来。

压缩包内容

这篇文章就简单介绍下,我是如何实现的。

技术思路

要将dom转成图片,我们直接使用html2canvas就可以;要将所有图片放到压缩包里,我们首先获取到每个图片的

base64Data编码信息,最后借助jszip就可以实现。

技术方案

这篇文章就不适用vue了,用react写demo吧,函数是一样的。最后我会放上vue版本的完整代码。

使用云编译器进行调试

以下demo,大家可以直接在豆包云vscode编译器中调试,云编译器免配置任何环境,可以直接使用。

juejin.cn/post/738875... (见文末)

必要依赖安装

js 复制代码
npm i file-saver
npm i html2canvas
npm i jszip

这三个包分别提供了在浏览器端实现文件保存、网页截图和 ZIP 文件处理的功能。

基础代码框架搭建

js 复制代码
import { useRef, useState } from "react";
import html2canvas from "html2canvas";
import JSZip from "jszip";
import FileSaver from "file-saver";
function App() {
  // 绑定dom 和vue的ref一样
  const commentRefs = useRef([]);

  // 图片导出
  const exportImages = async () => {};

  const mockData = [
    { name: "张三", age: 18 },
    { name: "李四", age: 18 },
    { name: "王五", age: 18 },
    { name: "石小石", age: 18 },
  ];
  return (
    <>
      <div>
        {mockData.map((item, index) => (
          <div key={index} ref={(el) => (commentRefs.current[index] = el)}>
            {/* 自定义的内容实现 */}
            {item.name}
          </div>
        ))}
      </div>
      <span onClick={() => exportImages()}>导出图片</span>
    </>
  );
}

export default App;

为了便于理解,上述代码只保留了核心逻辑。

dom转图片base64Data

首先,我们要借助html2canvas遍历所有dom生成base64Data数据

js 复制代码
const exportImages = async () => {
    // 循环遍历 commentRefs.current 数组中的每一个元素
    for (let i = 0; i < commentRefs.current.length; i++) {
        // 获取当前索引处的引用
        const ref = commentRefs.current[i];
        // 检查引用是否存在且索引 i 小于 commentRefs.current 的长度
        if (ref && i < commentRefs.current.length) {
            try {
                // 使用 html2canvas 库将 ref 元素转换为 Canvas 元素
                const canvas = await html2canvas(ref, { useCORS: true });
                // 将 Canvas 元素转换为 base64 格式的 PNG 图片数据 URL
                const dataUrl = canvas.toDataURL("image/png");
                // 从数据 URL 中提取 base64 编码的图片数据部分
                const base64Data = dataUrl.split(",")[1];
            } catch (error) {
                // 捕获任何导出图片时可能出现的错误,并输出到控制台
                console.error("Error exporting the comment as an image:", error);
            }
        }
    }
};

上述代码中,base64Data就是我们获取到的数据。

将base64Data写入压缩包

借助JSZip,我们可以把一些流数据进行打包,也可以将base64Data进行打包。

js 复制代码
const exportImages = async () => {
    // 创建一个新的 JSZip 实例
    const zip = new JSZip();

    for (let i = 0; i < commentRefs.current.length; i++) {
        const ref = commentRefs.current[i];
        if (ref && i < commentRefs.current.length) {
            try {
                const canvas = await html2canvas(ref, { useCORS: true });RL
                const dataUrl = canvas.toDataURL("image/png");
                const base64Data = dataUrl.split(",")[1];
                // 将图片数据添加到 ZIP 文件中,使用 image{i+1}.png 作为文件名
                zip.file(`image${i + 1}.png`, base64Data, { base64: true });
            } catch (error) {
                console.error("Error exporting the comment as an image:", error);
            }
        }
    }

    // 生成 ZIP 文件并将其作为 Blob 对象返回
    const blob = await zip.generateAsync({ type: "blob" });
  FileSaver.saveAs(blob, "images.zip");
};

这段代码扩展了之前的 exportImages 函数,添加了将生成的图片数据打包成 ZIP 文件的功能。关于JSZip的用法,大家可以看npm官网。这里我简单介绍下zip.file的用法。

zip.file 是 JSZip 库中用于向 ZIP 文件中添加文件的方法。它的基本用法是将数据添加到 ZIP 文件中,并指定文件的名称和数据。

基本语法

js 复制代码
zip.file(fileName, data, options);
  • fileName: 要添加到 ZIP 文件中的文件名称,可以包含路径,例如 "images/image1.png"
  • data: 要添加的文件数据,可以是字符串、ArrayBuffer、Uint8Array 等,具体取决于你要添加的文件类型和数据格式。
  • options (可选): 是一个对象,可以包含以下选项:
    • base64 (boolean): 指定 data 是否为 base64 编码,默认为 false。如果 data 是 base64 编码的字符串,则需要设置为 true

实现文件下载

要实现文件下载,我们一般借助FileSaver。FileSaver 是一个用于在浏览器中保存文件的 JavaScript 库。它通常与生成的文件内容(如通过其他库生成的 Blob 对象)一起使用。

基本用法

js 复制代码
import FileSaver from "file-saver";

FileSaver.saveAs(blob, fileName);
  • blob: 要保存的文件内容,通常是通过其他操作生成的 Blob 对象。
  • fileName: 下载文件的名称,可以包含文件扩展名,例如 "example.txt"

因此,我们只需要在exportImages方法最后添加下面代码即可

js 复制代码
  FileSaver.saveAs(blob, "images.zip");

完整代码

react版本

js 复制代码
import { useRef, useState } from "react";
import html2canvas from "html2canvas";
import JSZip from "jszip";
import FileSaver from "file-saver";
function App() {
  const commentRefs = useRef([]);
  const mockData = [
    { name: "张三", age: 18 },
    { name: "李四", age: 18 },
    { name: "王五", age: 18 },
    { name: "石小石", age: 18 },
  ];
  const exportImages = async () => {
    const zip = new JSZip();
    for (let i = 0; i < commentRefs.current.length; i++) {
      const ref = commentRefs.current[i];
      if (ref && i < commentRefs.current.length) {
        try {
          const canvas = await html2canvas(ref, {
            useCORS: true,
            backgroundColor: "rgba(255, 255, 255, 0.6)",
          });
          const dataUrl = canvas.toDataURL("image/png");
          const base64Data = dataUrl.split(",")[1];
          zip.file(`image${i + 1}.png`, base64Data, { base64: true });
        } catch (error) {
          console.error("Error exporting the comment as an image:", error);
        }
      }
    }
    const blob = await zip.generateAsync({ type: "blob" });
    FileSaver.saveAs(blob, "images.zip");
  };

  return (
    <>
    <div>
    {mockData.map((item, index) => (
      <div key={index} ref={(el) => (commentRefs.current[index] = el)}>
                  {/* 自定义的内容实现 */}
                  {item.name}
</div>
))}
  </div>
  <span onClick={() => exportImages()}>导出图片</span>
  </>
);
}

export default App;

vue版本

js 复制代码
<template>
<div>
  <div v-for="(item, index) in mockData" :key="index" ref="setRef">
  <!-- 自定义的内容实现 -->
  {{ item.name }}
</div>
  <span @click="exportImages">导出图片</span>
  </div>
  </template>

<script>
import { ref } from 'vue';
import html2canvas from 'html2canvas';
import JSZip from 'jszip';
import FileSaver from 'file-saver';

export default {
  setup() {
    // 使用 ref 创建响应式数据
    const commentRefs = ref([]);
    const mockData = [
      { name: '张三', age: 18 },
      { name: '李四', age: 18 },
      { name: '王五', age: 18 },
      { name: '石小石', age: 18 }
    ];

    // 导出图片的方法
    const exportImages = async () => {
      const zip = new JSZip();
      for (let i = 0; i < commentRefs.value.length; i++) {
        const ref = commentRefs.value[i];
        if (ref) {
          try {
            const canvas = await html2canvas(ref, {
              useCORS: true,
              backgroundColor: 'rgba(255, 255, 255, 0.6)'
            });
            const dataUrl = canvas.toDataURL('image/png');
            const base64Data = dataUrl.split(',')[1];
            zip.file(`image${i + 1}.png`, base64Data, { base64: true });
          } catch (error) {
            console.error('Error exporting the comment as an image:', error);
          }
        }
      }
      const blob = await zip.generateAsync({ type: 'blob' });
      FileSaver.saveAs(blob, 'images.zip');
    };

    // 将引用存入 commentRefs 数组中
    const setRef = (el) => {
      commentRefs.value.push(el);
    };

    return {
      mockData,
      exportImages,
      setRef
    };
  }
};
</script>

总结

这篇文章介绍了如何使用html2canvs将多个dom转成base64Data图片数据,然后借助jszip进行打包,最后通过file-saver下载到本地的简单技术方案。

借助这一套方案,其实也可以实现纯前端将网页转成pdf,最后下载成压缩包。

这篇文章中,没有做demo图片中的进度条功能,这个很简单,大家可以结合业务自行实现。

相关推荐
aPurpleBerry10 分钟前
JS常用数组方法 reduce filter find forEach
javascript
GIS程序媛—椰子39 分钟前
【Vue 全家桶】7、Vue UI组件库(更新中)
前端·vue.js
DogEgg_0011 小时前
前端八股文(一)HTML 持续更新中。。。
前端·html
ZL不懂前端1 小时前
Content Security Policy (CSP)
前端·javascript·面试
乐闻x1 小时前
ESLint 使用教程(一):从零配置 ESLint
javascript·eslint
木舟10091 小时前
ffmpeg重复回听音频流,时长叠加问题
前端
王大锤43911 小时前
golang通用后台管理系统07(后台与若依前端对接)
开发语言·前端·golang
我血条子呢1 小时前
[Vue]防止路由重复跳转
前端·javascript·vue.js
黎金安1 小时前
前端第二次作业
前端·css·css3
啦啦右一1 小时前
前端 | MYTED单篇TED词汇学习功能优化
前端·学习