不走弯路,纯前端如何把图片导出成视频!

简介: 【10月更文挑战第3天】不走弯路,纯前端如何把图片导出成视频!

需求背景

由于项目的保密问题,文章不能写的太详细,本文只写核心实现逻辑,文中的demo也已经做脱敏处理。

最近遇到这么一个需求,需要纯前端实现将图片导出成视频

大致功能如下图:

  • 将网页的dom转成图片,导出成视频
  • 前端上传图片,导出成视频

导出的视频需要能够正常播放

实现方案

要实现图片导出成视频,我们可以借助ffmpeg或者js的原生属性 MediaRecorder

ffmpeg

使用 ffmpeg 来生成视频灵活、高效。目前有一个开源项目 ffmpeg.wasm,它将 ffmpeg 移植到了 WebAssembly,可以在浏览器中使用。但这个库对电脑性能有一定要求,也是导出视频一种比较好的方案。

MediaRecorder

使用js原生属性将图片导出成视频大致流程如下:

  • 图像生成:使用 html2canvas 将DOM元素渲染为图像流或者借助input属性将上传的图片转换为图像流。
  • 视频录制:使用 MediaRecorder 捕获 canvas 的媒体流,并逐帧绘制图像。
  • 视频输出:录制停止后,将录制的数据保存为 Blob 对象并保存为视频文件,同时在页面上播放视频。

本文将详细介绍如何使用浏览器原生属性生成视频。

代码实现

使用云编译器进行调试

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

https://juejin.cn/post/7388753413309349903 (见文末)

必要依赖安装

npm i file-saver
npm i html2canvas

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

基础框架搭建

我们主要演示如何将dom渲染成图片,导出视频

import React, {
    useRef } from 'react';
import html2canvas from 'html2canvas';
import {
    saveAs } from 'file-saver';

const App = () => {
   
  const data = [
    {
    user: 'User1', text: 'This is a text message 1' },
    {
    user: 'User2', text: 'This is a text message 2' },
    {
    user: 'User3', text: 'This is a text message 3' },
  ];

  const containerRef = useRef(null);

  const exportVideo = async () => {
   

  };

  return (
    <div>
      <button onClick={
   exportVideo}>导出视频</button>
      <div ref={
   containerRef}>
        {
   data.map((item, index) => (
          <div key={
   index} style={
   {
    border: '1px solid black', margin: '10px', padding: '10px' }}>
            <strong>{
   item.user}</strong>: {
   item.text}
          </div>
        ))}
      </div>
    </div>
  );
};

export default App;

获取canvas媒体流

const exportVideo = async () => {
   
  const frames = [];
  // 生成每个DOM元素的图像
  // 遍历containerRef中的所有子元素
  const elements = containerRef.current.children;
  for (let i = 0; i < elements.length; i++) {
   
    const element = elements[i];
    // 使用html2canvas将DOM元素渲染为canvas图像
    const canvas = await html2canvas(element);
    // 将生成的canvas图像添加到frames数组中
    frames.push(canvas);
  }

  // 创建隐藏的canvas元素
  // 这个canvas将用于绘制每一帧的图像并录制视频
  const canvas = document.createElement('canvas');
  canvas.width = 640;
  canvas.height = 480;
  const ctx = canvas.getContext('2d');

  // 设置MediaRecorder来捕获canvas流
  const stream = canvas.captureStream();

  // ...
};

上述代码中,我们使用html2canvas将dom转成了流文件,借助canvas生成了媒体流,用于后续处理。如果是上传文件,需要如下处理

import React, {
    useRef } from 'react';

const App = () => {
   
  const canvasRef = useRef(null);

  // 处理文件上传
  const handleFileUpload = (event) => {
   
    const file = event.target.files[0];
    if (file) {
   
      const reader = new FileReader();

      // 文件读取完成后的回调
      reader.onload = (e) => {
   
        const img = new Image();
        img.src = e.target.result;

        // 图像加载完成后的回调
        img.onload = () => {
   
          const canvas = canvasRef.current;
          const ctx = canvas.getContext('2d');
          canvas.width = img.width;
          canvas.height = img.height;

          // 将图像绘制到canvas上
          ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
        };
      };

      // 读取文件为DataURL
      reader.readAsDataURL(file);
    }
  };

  return (
    <div>
      <input type="file" accept="image/*" onChange={
   handleFileUpload} />
      <canvas ref={
   canvasRef}></canvas>
    </div>
  );
};

export default App;

FileReader 可以将上传的图片文件读取为数据URL,然后将其加载到 Image 对象中,并绘制到 canvas 上。

将媒体流导出为视频

const exportVideo = async () => {
   
  // ....
  // 设置MediaRecorder来捕获canvas流
  // captureStream()方法将canvas的绘制内容捕获为媒体流
  const stream = canvas.captureStream();


  // 创建MediaRecorder实例,用于录制视频
  // 设置视频的MIME类型为video/webm
  const recorder = new MediaRecorder(stream, {
    mimeType: "video/webm" });
  const chunks = [];

  // 当有数据可用时,将数据块添加到chunks数组中
  recorder.ondataavailable = (event) => {
   
    if (event.data.size > 0) {
   
      chunks.push(event.data);
    }
  };

  // 当录制停止时,将所有数据块合并为一个Blob对象,并保存为视频文件
  recorder.onstop = () => {
   
    const blob = new Blob(chunks, {
    type: "video/webm" });
    // 使用file-saver库将Blob保存为文件
    saveAs(blob, "video.webm");
  };

  // 开始录制
  recorder.start();

  // 遍历frames数组,将每一帧绘制到canvas上
  for (const frame of frames) {
   
    // 将当前帧绘制到canvas上
    ctx.drawImage(frame, 0, 0, canvas.width, canvas.height);
    // 等待1秒,以便在视频中每帧显示1秒
    await new Promise((resolve) => setTimeout(resolve, 1000));
  }

  // 停止录制
  recorder.stop();
};

增加视频预览功能

预览功能就是将生成好的流,生成一个src,使用video标签播放即可

import React, {
    useRef } from "react";
import html2canvas from "html2canvas";
import {
    saveAs } from "file-saver";

const App = () => {
   
  // ...
  const containerRef = useRef(null);
  const videoRef = useRef(null);

  const exportVideo = async () => {
   

     // ...
    recorder.onstop = () => {
   
      const blob = new Blob(chunks, {
    type: "video/webm" });
      saveAs(blob, "video.webm");

      // 视频播放核心代码
      const url = URL.createObjectURL(blob);
      videoRef.current.src = url;
      videoRef.current.play();
    };

    // ...
  };

  return (
    <div>
      <button onClick={
   exportVideo}>Export Video</button>
      <div ref={
   containerRef}>
        {
   data.map((item, index) => (
          <div
            key={
   index}
            style={
   {
   
              border: "1px solid black",
              margin: "10px",
              padding: "10px",
            }}
          >
            <strong>{
   item.user}</strong>: {
   item.text}
          </div>
        ))}
      </div>
      <video ref={
   videoRef} width="640" height="480" controls></video>
    </div>
  );
};

export default App;

完整代码

import React, {
    useRef } from "react";
import html2canvas from "html2canvas";
import {
    saveAs } from "file-saver";

const App = () => {
   
  const data = [
    {
    user: "User1", text: "This is a text message 1" },
    {
    user: "User2", text: "This is a text message 2" },
    {
    user: "User3", text: "This is a text message 3" },
  ];

  const containerRef = useRef(null);
  const videoRef = useRef(null);

  const exportVideo = async () => {
   
    const frames = [];

    // 生成每个DOM元素的图像
    // 遍历containerRef中的所有子元素
    const elements = containerRef.current.children;
    for (let i = 0; i < elements.length; i++) {
   
      const element = elements[i];
      // 使用html2canvas将DOM元素渲染为canvas图像
      const canvas = await html2canvas(element);
      // 将生成的canvas图像添加到frames数组中
      frames.push(canvas);
    }

    // 创建隐藏的canvas元素
    // 这个canvas将用于绘制每一帧的图像并录制视频
    const canvas = document.createElement("canvas");
    canvas.width = 640;
    canvas.height = 480;
    const ctx = canvas.getContext("2d");

    // 设置MediaRecorder来捕获canvas流
    // captureStream()方法将canvas的绘制内容捕获为媒体流
    const stream = canvas.captureStream();
    // 创建MediaRecorder实例,用于录制视频
    // 设置视频的MIME类型为video/webm
    const recorder = new MediaRecorder(stream, {
    mimeType: "video/webm" });
    const chunks = [];

    // 当有数据可用时,将数据块添加到chunks数组中
    recorder.ondataavailable = (event) => {
   
      if (event.data.size > 0) {
   
        chunks.push(event.data);
      }
    };

    // 当录制停止时,将所有数据块合并为一个Blob对象,并保存为视频文件
    recorder.onstop = () => {
   
      const blob = new Blob(chunks, {
    type: "video/webm" });
      // 使用file-saver库将Blob保存为文件
      saveAs(blob, "video.webm");

      // 创建视频的URL并在页面上播放视频
      const url = URL.createObjectURL(blob);
      videoRef.current.src = url;
      videoRef.current.play();
    };

    // 开始录制
    recorder.start();

    // 遍历frames数组,将每一帧绘制到canvas上
    for (const frame of frames) {
   
      // 将当前帧绘制到canvas上
      ctx.drawImage(frame, 0, 0, canvas.width, canvas.height);
      // 等待1秒,以便在视频中每帧显示1秒
      await new Promise((resolve) => setTimeout(resolve, 1000));
    }

    // 停止录制
    recorder.stop();
};

  return (
    <div>
      <button onClick={
   exportVideo}>Export Video</button>
      <div ref={
   containerRef}>
        {
   data.map((item, index) => (
          <div
            key={
   index}
            style={
   {
   
              border: "1px solid black",
              margin: "10px",
              padding: "10px",
            }}
          >
            <strong>{
   item.user}</strong>: {
   item.text}
          </div>
        ))}
      </div>
      <video ref={
   videoRef} width="640" height="480" controls></video>
    </div>
  );
};

export default App;

上述代码大家可以直接在豆包云编译器中运行查看效果:

https://juejin.cn/post/7388753413309349903 (见文末)

总结

本文介绍了如何在前端将图片或网页DOM元素导出为视频。通过使用 html2canvas 将DOM元素渲染为图像,再借助 MediaRecorder 捕获 canvas 的媒体流,逐帧绘制图像并保存为视频文件。此方法无需依赖后端服务,实现了纯前端的图片导出视频功能。

文章还示范了如何处理上传的图片,并将其转化为视频。

关注我!前端学习好活泼!

相关文章
|
2月前
|
前端开发
后端返回图片二进制流,前端转base64
本文介绍了如何将后端返回的图片二进制流转换为Base64格式,以便在前端使用。通过在axios请求中设置`responseType`为`arraybuffer`,然后使用`btoa`和`Uint8Array`进行转换。
211 5
|
29天前
|
存储 前端开发 JavaScript
🚀前端轻松实现网页内容转换:一键复制、保存图片及生成 Markdown
在现代前端开发中,提升用户的交互体验至关重要。本文将详细介绍如何使用 HTML2Canvas 和 Turndown 两个强大的 JavaScript 库,实现将网页选中文本转化为图片并保存或复制到剪贴板,或将内容转换为 Markdown 格式。文章包含核心代码实现、技术细节和功能拓展方向,为开发者提供了一个轻量级的解决方案,提升用户体验。
124 68
|
18天前
|
前端开发 API
前端界面生成PDF并导出下载
【10月更文挑战第21天】利用合适的第三方库,你可以在前端轻松实现界面生成 PDF 并导出下载的功能,为用户提供更方便的文档分享和保存方式。你还可以根据具体的需求进一步优化和定制生成的 PDF 文件,以满足不同的业务场景要求。
|
1月前
|
JavaScript 前端开发 Python
django接收前端vue传输的formData图片数据
django接收前端vue传输的formData图片数据
32 4
|
1月前
|
前端开发 小程序 Java
java基础:map遍历使用;java使用 Patten 和Matches 进行正则匹配;后端传到前端展示图片三种情况,并保存到手机
这篇文章介绍了Java中Map的遍历方法、使用Pattern和matches进行正则表达式匹配,以及后端向前端传输图片并保存到手机的三种情况。
18 1
|
1月前
|
前端开发 JavaScript
💥【exceljs】纯前端如何实现Excel导出下载和上传解析?
本文介绍了用于处理Excel文件的库——ExcelJS,相较于SheetJS,ExcelJS支持更高级的样式自定义且易于使用。表格对比显示,ExcelJS在样式设置、内存效率及流式操作方面更具优势。主要适用于Node.js环境,也支持浏览器端使用。文中详细展示了如何利用ExcelJS实现前端的Excel导出下载和上传解析功能,并提供了示例代码。此外,还提供了在线调试的仓库链接和运行命令,方便读者实践。
274 5
|
1月前
|
JavaScript 前端开发 编译器
吐血整理:纯前端如何实现批量dom转图片,并下载成压缩包
【10月更文挑战第2天】吐血整理:纯前端如何实现批量dom转图片,并下载成压缩包
47 2
|
2月前
|
前端开发
前端之图片操作
前端之图片操作
|
2月前
|
运维 前端开发
前端使用antdesign导出插件跨域问题
前端使用antdesign导出插件跨域问题
32 1
|
1月前
|
前端开发 JavaScript Java
导出excel的两个方式:前端vue+XLSX 导出excel,vue+后端POI 导出excel,并进行分析、比较
这篇文章介绍了使用前端Vue框架结合XLSX库和后端结合Apache POI库导出Excel文件的两种方法,并对比分析了它们的优缺点。
206 0