纯Javascript制作制作BadApple字符动画视频

樵夫2021-12-03 11:27

  是这样的,最近B站的粉丝在我某个视频下评论,让我分享一下,这个BadApple动态效果怎么做。好家伙,我开始还不知道是什么,结果我一看,这很简单啊,其实只要使用ASCII VIDEO作为关键词搜索的话,你能找到各种语言的各种写法。

纯Javascript制作制作BadApple字符动画视频

  不过,既然有小伙伴提出了这个问题,我也以自己的方式写一次,下次再有小伙伴问,我就直接发这篇文章了,顺便我还会出一个视频配套教程,最近正愁不知道做点什么视频内容。

  先拆解需求

  1、播放视频

  2、将视频每一帧的画面转为点阵/像素RGB值

  3、将RGB转灰度值

  4、按照灰度值填充字符

  需求很简单,稍微复杂的部分只有RGB转灰度,那我们直接开撸代码,使用vanilla.js框架(这是个梗,不明白的话自己搜)来完成开发。

  1. 播放视频

  使用JS创建一个video标签,并为它设置视频源路径

var videoDom = document.createElement("video");
videoDom.src = "./video/badapple.mp4";
videoDom.style.width = "900px";
videoDom.style.height = "675px";

  由于我们最终的效果并不需要看到这个视频原画面,所以我们也不用将这个dom添加到网页body当中去。

  添加一个控制视频播放和暂停的按钮

var btnPlayAndPause = document.createElement("div");
btnPlayAndPause.style.color = "#fff";
btnPlayAndPause.style.textAlign = "center";
btnPlayAndPause.style.position = "absolute";
btnPlayAndPause.style.top = btnPlayAndPause.style.left = "0px";
btnPlayAndPause.style.width = videoDom.style.width;
btnPlayAndPause.style.height = btnPlayAndPause.style.lineHeight = videoDom.style.height;
btnPlayAndPause.style.cursor = "pointer";
btnPlayAndPause.style.fontSize = "30px";
btnPlayAndPause.style.zIndex = 2;
btnPlayAndPause.innerText = "play";
document.body.appendChild(btnPlayAndPause);

  当按钮点击的时候,切换videoDom的播放/暂停状态

btnPlayAndPause.addEventListener("click",function(){
        if(btnPlayAndPause.innerText === "play"){
                videoDom.play();
        }else{
                videoDom.pause();
        }
})

  监听videoDom的canplay事件,并渲染第一帧

videoDom.addEventListener('canplay',function(){
    renderVideoFrame(videoDom);
});

  监听videoDom的play(播放),pause(暂停),stop(停止)事件在播放时启动字符画面渲染,暂停或停止时也停止掉字符画面渲染。

videoDom.addEventListener('play',function(){
    console.log("开始播放");
    btnPlayAndPause.innerText = "";

    startRender();
});

//监听播放结束
videoDom.addEventListener('pause',function(){
    console.log("播放暂停");
    btnPlayAndPause.innerText = "play";

    stopRender();
}); 

//监听播放结束
videoDom.addEventListener('ended',function(){
    console.log("播放结束");
    btnPlayAndPause.innerText = "play";

    stopRender();
});

  画面渲染的绘制频率和浏览器的绘制频率保持一致,这样不会丢掉任何一个画面,但算力消耗会更大。

var timerId;
function startRender() {
        timerId = requestAnimationFrame(updateRender);
}
function updateRender(){
        renderVideoFrame(videoDom);
        timerId = requestAnimationFrame(updateRender);
}
function stopRender(){
        cancelAnimationFrame(timerId);
}

  2. 将视频每一帧的画面转为点阵/像素RGB值

  这里我们要利用html5的canvas标签,首先将视频的画面原封不动的绘制到canvas上。

function renderVideoFrame(videoDom) {
    var videoSize = {width:parseFloat(videoDom.videoWidth),height:parseFloat(videoDom.videoHeight)};

    var canvas = document.querySelector("#canvas");
    if(!canvas){
            canvas = document.createElement("canvas");
            canvas.id = "canvas";
            canvas.style.width = videoDom.style.width;
            canvas.style.height = videoDom.style.height;
            canvas.style.position = "absolute";
            canvas.style.zIndex = 1;
            canvas.style.left = canvas.style.top = "0";
            canvas.width = videoSize.width;
            canvas.height = videoSize.height;

            document.body.appendChild(canvas);
    }

    const ctx = canvas.getContext("2d");

    ctx.drawImage(videoDom, 0, 0, videoSize.width, videoSize.height);
}

  注意看我这里做了判断,只在场景上没有指定canvas的时候,才创建它。

  接着通过context的drawImage方法,将视频绘制到场景上,现在我们body虽然没有video标签,但我们也能看到视频了。