<万博manbetx平台>前端canvas动画如何转成mp4视频的方法 - 万博manbetx平台中文网

前端canvas动画如何转成mp4视频的方法

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

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

生成视频可能的方案

纯前端的视频编码转换(例如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  -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').inner万博manbetx平台 = '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的机制)
  • 图片尺寸在640x360或者360x640,生成的动画在手机端预览不清晰;
  • 需求换成图片尺寸为1280x720或者720x1280之后,原本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-600`, 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-600&images=../assets/imgs/6.png-600&images=../assets/imgs/7.png-600&images=../assets/imgs/6.png-600&images=../assets/imgs/8.png-600&images=../assets/imgs/7.png-600&images=../assets/imgs/4.png-600&images=../assets/imgs/6.png-600&images=../assets/imgs/8.png-600&images=../assets/imgs/7.png-600' './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张静止画面。

以上就是前端canvas动画如何转成mp4视频的方法的详细内容,更多请关注万博manbetx平台中文网其它相关文章!

赞(0) 打赏
未经允许不得转载:万博manbetx平台中文网首页 » 万博manbetx平台5 答疑

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

前端开发相关广告投放 更专业 更精准

联系我们

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏