前言:

本文介绍如何在前端实现视频转gif,虽然我用的是react+umi的框架,但实现该功能还是靠js,所以会不会react对本文的理解影响不大。 先看一下实现效果:选择视频文件上传,转换成gif并下载。下面这个gif就是使用本文的方法将mp4转成gif的。

实现思路:

首先介绍一下需要用到的工具——gif.js,它可以把多张图片转成gif。 所以我们要做的步骤如下:

input标签上传视频文件。使用canvas将视频内容绘制到画布上。设置定时器,从播放视频开始到结束,每隔100ms(可按需自行设置)操作步骤2,可得到连续的截图。使用gifjs把得到的截图转换成gif格式。

开始实现

(1)引入gif.js git地址:https://gitcode.com/jnordberg/gif.js/blob/master/README.md 注意文档中说的,gif.js和gif.worker.js要写到一个路径下。我放到了public的dist文件下 (2)把元素确定好,一个input来上传文件,一个button确认按钮,一个video标签来显示上传的视频。 我是用react写的,render部分如下,用原生和vue的小伙伴自行改造。直接上代码。 注意gif.worker.js的路径一定要写对,否则会报错。特别是使用umi之类的框架,运行的时候相当于是webpakce已经进行了打包,然后本地起了一个服务,如果写的是相对路径"…/…/…/…/public/dist/gif.worker.js"这样的必然是找不到的,而且在进webpack打包的时候也会对文件名进行处理。建议和我一样写在public里面,这样打包的时候文件名也不会变成hash值。如果不能确定文件的路径,可以npm run build看一下gif.worker.js所处的位置,再来填写workerScript的路径。

import {useState, useRef} from 'react'

const GIF = require('../../../../public/dist/gif')

let interval = null //定时器

let clipImgs = [] //

const delay = 200 //单位ms,时间间隔,间隔越短越流畅,但是生成的图片体积会越大。

//文件转成base64

function convertFileToBase64(file) {

return new Promise((resolve, reject) => {

const reader = new FileReader();

reader.readAsDataURL(file);

reader.onload = () => resolve(reader.result);

reader.onerror = error => reject(error);

});

}

//base64转image

function getImageFromBase64(base64Image) {

return new Promise((resolve, reject) => {

const img = new Image();

img.src = base64Image;

img.onload = () => resolve(img);

img.onerror = reject;

});

}

const Index = () => {

const fileRef = useRef();

const videoRef = useRef();

const [videoUrl,setVideoUrl] = useState() //video中source的src的值

const confirmFile = () => {

if(!fileRef.current.files?.[0]){

return console.log('请选择文件')

}

setVideoUrl('')

convertFileToBase64(fileRef.current.files?.[0])

.then(base64Data => {

setVideoUrl(base64Data) //video的src

start() //视频截图并转成gif

})

.catch(error => {

console.error('Error:', error);

});

}

const start = () => {

console.log('start');

//每次开始重置以下值

clearInterval(interval)

interval = null

clipImgs = []

let video = videoRef.current //获取video元素

video.addEventListener('canplay',function () {

video.play()

})

video.addEventListener('play', function() {

var canvas = document.createElement('canvas');

// 根据视频大小调整画布尺寸

canvas.width = video.videoWidth;

canvas.height = video.videoHeight;

// 获取上下文对象

var context = canvas.getContext('2d');

interval = setInterval(()=>{

context.drawImage(video, 0, 0); // 将视频内容绘制到画布上

var screenshotDataURL = canvas.toDataURL(); // 转成Base64

clipImgs.push(screenshotDataURL) //把所有截图放到一个数组里

},delay)

});

//监听视频播放结束后,说明截图完成。定时器停止,清除定时器缓存,开始转换。

video.addEventListener('ended', function(e) {

console.log('stop');

clearInterval(interval)

interval = null

makeGIF()

})

}

const makeGIF = async () => {

//注意gif.worker.js的路径一定要写对,否则会报错。特别是使用umi之类的框架,运行的时候相当于是webpakce已经进行了打包,然后本地起了一个服务,如果写的是相对路径"../../../../public/dist/gif.worker.js"这样的必然是找不到的,而且在进webpack打包的时候也会对文件名进行处理。建议和我一样写在public里面,这样打包的时候文件名也不会变成hash值。如果不能确定文件的路径,可以npm run build看一下gif.worker.js所处的位置,再来填写workerScript的路径。

const gif = new GIF({workers: 2, quality: 10, workerScript: '/dist/gif.worker.js'});

const imgModel = await getImageFromBase64(clipImgs[0])

let canvas = document.createElement('canvas');

const ctx = canvas.getContext('2d', { willReadFrequently: true });

canvas.width = imgModel.naturalWidth;

canvas.height = imgModel.naturalHeight;

for (let i=0;i

let img = await getImageFromBase64(clipImgs[i])

gif.addFrame(img,{delay})

}

//生成后下载gif图片到本地

gif.on('finished', function (blob) {

//生成图片链接

var url = URL.createObjectURL(blob);

let link = document.createElement('a')

link.download = Math.random().toString().replace('0.','') + '.gif'

link.href = url

link.click()

URL.revokeObjectURL(link.href) //释放URL对象

});

gif.render();

}

return

{

videoUrl?

:null

}

}

export default Index

可完善内容: 1.截取的图片我没有做压缩 2.没有限制视频时长,如果视频太长可能会造成转换失败的问题,经测试10s内的视频是没问题的。 可根据需要自行完善哦。

原创不易,请给西瓜西西西瓜点个赞,谢谢各位看官。ღ( ´・ᴗ・` )比心

好文阅读

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。