likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 18

往期精彩文章:

需求背景

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

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

大致功能如下图:

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

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

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

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

实现方案

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

ffmpeg

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

MediaRecorder

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

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

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

代码实现

使用云编译器进行调试

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

必要依赖安装

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;

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

总结

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

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

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

转载自:https://juejin.cn/post/7392143126826991625
评论
请登录