• 企业400电话
  • 微网小程序
  • AI电话机器人
  • 电商代运营
  • 全 部 栏 目

    企业400电话 网络优化推广 AI电话机器人 呼叫中心 网站建设 商标✡知产 微网小程序 电商运营 彩铃•短信 增值拓展业务
    前端canvas动画如何转成mp4视频的方法

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

    生成视频可能的方案

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

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

    最后定的方案流程

    前端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()

    试运行方案的弊端:

    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环境下运行的弊端

    每隔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张静止画面。

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

    上一篇:Html5 实现微信分享及自定义内容的流程
    下一篇:HTML高亮关键字的实现代码
  • 相关文章
  • 

    © 2016-2020 巨人网络通讯 版权所有

    《增值电信业务经营许可证》 苏ICP备15040257号-8

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