纯前端如何实现批量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图片中的进度条功能,这个很简单,大家可以结合业务自行实现。

相关推荐
Random_index40 分钟前
#名词区别篇:npx pnpm npm yarn区别
前端·npm
B.-1 小时前
Remix 学习 - 路由模块(Route Module)
前端·javascript·学习·react·web
陈大爷(有低保)1 小时前
JS基础语法
开发语言·javascript·ecmascript
不修×蝙蝠1 小时前
Javascript应用(TodoList表格)
前端·javascript·css·html
加勒比海涛2 小时前
ElementUI 布局——行与列的灵活运用
前端·javascript·elementui
你不讲 wood2 小时前
postcss 插件实现移动端适配
开发语言·前端·javascript·css·vue.js·ui·postcss
前端小程2 小时前
使用vant UI实现时间段选择
前端·javascript·vue.js·ui
whyfail3 小时前
React 事件系统解析
前端·javascript·react.js
禾苗种树3 小时前
element form rules 验证数组对象属性时如何写判断规则
javascript·vue.js·elementui
liangshanbo12153 小时前
JavaScript 中的一些常见陷阱
开发语言·javascript·ecmascript