前端canvas动画如何转成mp4视频的方法_html5_网页制作

这篇文章主要介绍了前端canvas动画如何转成mp4视频的方法,文中通过示例代码介绍的非常详细,对大

前端canvas动画如何转成mp4视频的方法_html5_网页制作

这篇文章主要介绍了前端canvas动画如何转成mp4视频的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

用户通过上传合适尺寸的图片,选着渲染动画的效果和音乐,可以预览类似幻灯片的效果,最后点击确认生成视频,可以放到头条或者抖音播放。

前端canvas动画如何转成mp4视频的方法_html5_网页制作插图

生成视频可能的方案

纯前端的视频编码转换(例如WebM Encoder Whammy)

图片地址只能是相对地址
音乐不能收录
生成的视频需要下载再上传

将每帧图片传给后端实现,由后端调用FFmpeg进行视频转码

截图多的时候,base64字符串形式的图片太大,在前端不好传给后端
在前端截图还依赖用户电脑性能;

最后定的方案流程

canvas动画和截图在服务器端运行,后端根据标识获取截图
利用FFmpeg将图片合并成视频,并将视频存储在server端,并返回相应下载url
前端通过请求得到视频文件

前端canvas如何截图

每帧图片生成

图片生成可以通过canvas原生接口toDataURL实现,最终返回base64形式的图像数据

function generatePng() {
var canvas = document.createElement(\’canvas\’);
let icavas = \’#canvas\’ //渲染动画的canvas id
if (wrapWidth == 2) {
icavas = \’#verticalCanvas\’
}
var canvasNode = document.querySelector(icavas)

canvas.width = canvasNode.width;
canvas.height = canvasNode.height;
var ctx = canvas.getContext(\’2d\’);
ctx.drawImage(canvasNode, 0, 0);
var imgData = canvas.toDataURL("image/png");
return imgData;
}

canvas动画截图的方法

用setInterval定时执行图片生成的方法,当然也可以用requestAnimationFrame

setInterval(function() {
imgsTemp.push(generatePng())
}, 1000/60)

后端如何获取每帧图片

方案一:无头浏览器运行前端canvas动画js,然后js截图

最初设想:

截图用console.log打印出来,canvas截图是base64格式的,一个15秒的动画,截图有100多张,直接导致服务器运行崩溃(被否了);

试运行方案:

截图存储在js变量中,动画播放完成,在页面中加一个标识,然后后端去取这个变量,代码如下:

const pages = {
imageZoomOut: import (\’./image_zoom_inout.js\’), //缩放
imageArt: import (\’./image_art.js\’), //擦除
imageGrid: import (\’./image_grid.js\’), //网格
imageRotate: import (\’./image_rotate.js\’), //开合
imageFlash: import (\’./image_flash.js\’), //图文快闪
imageVerticalArt: import (\’./image_vertical_art.js\’), //竖版擦除
imageVerticalGrid: import (\’./image_vertical_grid.js\’), //竖版网格
imageVerticalRotate: import (\’./image_vertical_rotate.js\’), //竖版开合
imageVerticalFlash: import (\’./image_vertical_flash.js\’), //竖版图文快闪
imageVerticalZoomOut: import (\’./image_vertical_zoom_inout.js\’), //竖版缩放
imageVertical: import (\’./image_vertical.js\’), //竖版通用
};
var isShow = false
var imgsBase64 = []
var imgsTemp = []
var cutInter = null
var imgsTimeLong = 0
function getQuerys(tag) {
let queryStr = window.location.search.slice(1);
let queryArr = queryStr.split(\’&\’);
let query = [];
let spec = {}
for (let i = 0, len = queryArr.length; i < len; i++) {
let queryItem = queryArr[i].split(\’=\’);
let qitem = decodeURIComponent(queryItem[1])
if (queryItem[0] == tag) {
query.push(qitem);
} else {
spec[queryItem[0]] = qitem
}
}
return { list: query, spec: spec };
}
var getQuery = getQuerys(\’images\’)
var effectTag = getQuery.spec.tid
var wrapWidth = getQuery.spec.templateType
let num = 0
let imgArr = []
function creatImg() {
var images = getQuery.list
let newImg = []
let vh = wrapWidth == 1 ? 360 : 640
let vw = wrapWidth == 1 ? 640 : 360
if (effectTag.indexOf(\’Flash\’) > -1) {
images.map(function(item, index) {
if (11 === index || 13 === index || 16 === index) {
var temp = new Image(vw, vh)
temp.setAttribute(\’crossOrigin\’, \’anonymous\’);
temp.src = item;
newImg.push(temp)

} else {
newImg.push(item)
}
})
imgArr = newImg
renderAnimate(effectTag)
} else {
images.map(function(item) {
var temp = new Image(vw, vh)
temp.setAttribute(\’crossOrigin\’, \’anonymous\’);
temp.src = item;
temp.onload = function() {
num++
if (num == images.length) {
renderAnimate(effectTag)
}
}
newImg.push(temp)
})
imgArr = newImg
}
}
async function renderAnimate(page) {
//await creatImg()
let me = this
const pageA = await pages[page];
let oldDate = new Date().getTime()
let icavas = \’#canvas\’
if (wrapWidth == 2) {
icavas = \’#verticalCanvas\’
}
let innerCanvas = document.querySelector(icavas)
isShow = false
pageA[page].render(null, {
canvas: innerCanvas,
images: imgArr
}, function() {
//动画播完
isShow = true;
imgsTemp.push(generatePng())
imgsBase64.push(imgsTemp)
let now = new Date().getTime()
window.imgsTimeLong = now – oldDate

clearInterval(cutInter)
document.getElementById(\’cutImg\’).innerHTML = \’done\’//页面标识
})
cutInter = setInterval(function() {
imgsTemp.push(generatePng())
if (imgsTemp.length >= 50) {
imgsBase64.push(imgsTemp)
imgsTemp = []
}
}, 130)
}
function getImgs() {
return imgsBase64
}
function generatePng() {
var canvas = document.createElement(\’canvas\’);
let icavas = \’#canvas\’
if (wrapWidth == 2) {
icavas = \’#verticalCanvas\’
}

var canvasNode = document.querySelector(icavas)
canvas.width = canvasNode.width;
canvas.height = canvasNode.height;
var ctx = canvas.getContext(\’2d\’);
ctx.drawImage(canvasNode, 0, 0);
var imgData = canvas.toDataURL("image/png");
return imgData;
}
window.imgsBase64 = imgsBase64 //截图存储变量

creatImg()

试运行方案的弊端:

截图间隔130ms截一张图片,截图数量太少,导致生成的动画不流畅;
截图间隔调成1秒60帧的话,动画播放缓慢,导致生成视频时间变长;(settimeout和setinterval的机制)
图片尺寸在640×360或者360×640,生成的动画在手机端预览不清晰;
需求换成图片尺寸为1280×720或者720×1280之后,原本15秒的动画在服务器端执行变成了70多秒
canvas截图存在跨域问题,可以如下设置

var temp = new Image(vw, vh)
temp.setAttribute(\’crossOrigin\’, \’anonymous\’);

最终方案:在NODE端运行动画

用node-canvas,把每帧截图用 fs.writeFile 写到指定的文件夹里

const {
createCanvas,
loadImage
} = require("canvas");
const pages = {
imageZoomOut: require(\’./image_zoom_inout.js\’), //缩放
imageArt: require(\’./image_art.js\’), //擦除
imageGrid: require(\’./image_grid.js\’), //网格
imageRotate: require(\’./image_rotate.js\’), //开合
imageFlash: require(\’./image_flash.js\’), //图文快闪
imageVerticalArt: require(\’./image_vertical_art.js\’), //竖版擦除
imageVerticalGrid: require(\’./image_vertical_grid.js\’), //竖版网格
imageVerticalRotate: require(\’./image_vertical_rotate.js\’), //竖版开合
imageVerticalFlash: require(\’./image_vertical_flash.js\’), //竖版图文快闪
imageVerticalZoomOut: require(\’./image_vertical_zoom_inout.js\’), //竖版缩放
imageVertical: require(\’./image_vertical.js\’), //竖版通用
};

const fs = require("fs");
const querystring = require(\’querystring\’);
let args = process.argv && process.argv[2]
let parse = querystring.parse(args)

let vh = parse.templateType == 1 ? 720 : 1280 //canvas 高
let vw = parse.templateType == 1 ? 1280 : 720 //canvas 宽
let imgSrcArray = parse.images //图片数组
let effectTag = parse.tid //动画效果

let saveImgPath = process.argv && process.argv[3]

let loadArr = []

imgSrcArray.forEach(element => {
if (/\\.(jpg|jpeg|png|JPG|PNG)$/.test(element)) {
loadArr.push(loadImage(element))
} else {
loadArr.push(element)
}
});

const canvas = createCanvas(vw, vh);
const ctx = canvas.getContext("2d");

Promise.all(loadArr)
.then((images) => {
//初始化动画
console.log(\’开始动画\’)
let oldDate = new Date().getTime()
pages[effectTag].render(null, {
canvas: canvas,
images: images
}, function() {
clearInterval(interval)
let now = new Date().getTime()
console.log(now – oldDate, \’动画结束\’)
})

const interval = setInterval(
(function() {
let x = 0;
return () => {
x += 1;
ctx.canvas.toDataURL(\’image/jpeg\’, function(err, png) {
if (err) {
console.log(err);
return;
}
let data = png.replace(/^data:image\\/\\w+;base64,/, \’\’);
let buf = new Buffer(data, \’base64\’);
fs.writeFile(`${saveImgPath}${x}.jpg`, buf, {}, (err) => {
console.log(x, err);
return;
});
});
};
})(),
1000 / 60
);
})
.catch(e => {
console.log(e);
});

在iterm下执行下面命令

node testCanvas.js \’tid=imageArt&templateType=1&images=../assets/imgs/8.png&images=../assets/imgs/6.png&images=../assets/imgs/7.png&images=../assets/imgs/6.png&images=../assets/imgs/8.png&images=../assets/imgs/7.png&images=../assets/imgs/4.png&images=../assets/imgs/6.png&images=../assets/imgs/8.png&images=../assets/imgs/7.png\’ \’./images/\’

参数说明:
1)tid 是动画名称
2)templateType是尺寸:"1":1280*720;"2":720*1280
3) images是图片地址
4)变量\’./images/\’是截图保存的地址,

NODE环境下运行的弊端

参数图片地址只能是相对地址
动画过于复杂时,运行时间长,如下:当页面的图形数量达到一定时,动画每一帧就要大量调用canvas的API,要进行大量的计算,再加上图片体积很大,就会慢

每隔13秒循环一次下面的画图:   

 

for (var A = 0; 50 > A; A++)
p.beginPath(),
p.globalAlpha = 1 – A / 49,
p.save(),
p.arc(180,320,P + 2 * A, 0, 2 * Math.PI),
p.clip(),
p.drawImage(x[c], 0, 0, y.width, y.height),
p.restore(),
p.closePath();

for (var S = 0; 50 > S; S++)
p.beginPath(),
p.globalAlpha = 1 – S / 49,
p.save(),
p.rect(0, 0, d + P + 2 * S, g + b + 2 * S),
p.clip(),
p.drawImage(x[c], 0, 0, y.width, y.height),
p.restore(),
p.closePath();

因为Node.js 的事件循环模型,要求 Node.js 的使用必须时刻保证 Node.js 的循环能够运转,如果出现非常耗时的函数,那么事件循环就会陷入进去,无法及时处理其他的任务,所以导致有些动画还是慢

后期优化的可能

尝试用go语言,来截图;

重写canvas动画;

番外

视频码率

视频码率就是数据传输时单位时间传送的数据位数,一般我们用的单位是kbps即千位每秒。通俗一点的理解就是取样率,单位时间内取样率越大,精度就越高,处理出来的文件就越接近原始文件。举例来看,对于一个音频,其码率越高,被压缩的比例越小,音质损失越小,与音源的音质越接近。

FPS 每秒传输帧数(Frames Per Second))

FPS是图像领域中的定义,是指画面每秒传输帧数,通俗来讲就是指动画或视频的画面数。FPS是测量用于保存、显示动态视频的信息数量。每秒钟帧数愈多,所显示的动作就会愈流畅。通常,要避免动作不流畅的最低是30。例如电影以每秒24张画面的速度播放,也就是一秒钟内在屏幕上连续投射出24张静止画面。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持3399IT网。

本文为网络共享文章,如有侵权请联系邮箱485837881@qq.com

作者: atlas_design

为您推荐

返回顶部