分析学习canvas

121 阅读2分钟

分析学习canvas

今天看了一个帖子忘了大佬叫啥了对不起,然后他分享的将视频转成canvas的动画的源码,我给拿下来大概看懂了,记录一下

仓库地址

源码

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>BadApple</title>
    <style type="text/css">
      html,
      body {
        margin: 0;
        padding: 0;
      }
    </style>
    <script type="text/javascript">
      // 视频播放之前调用的方法,吧视频标签传递进去,后面这12不晓得是啥
      function renderVideoFrame(videoDom, gap = 12) {
        // 声明要渲染进去的文字
        var asciiList = ["小", "米", " ", " ", " "];

        // 准备宽和高 宽度和高度为视频标签的一半
        var videoSize = {
          width: parseFloat(videoDom.videoWidth / 2),
          height: parseFloat(videoDom.videoHeight / 2),
        };
        // 生成一个canvas
        var canvas = document.querySelector("#canvas");
        // 如果没有东西就证明没有生成这个canvs,所以就生成一个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;
          // 给body添加一个子集的canvas
          document.body.appendChild(canvas);
        }
        // 指定画布绘制的类型,目前只能绘制2d类型的画面
        const ctx = canvas.getContext("2d");
        // 向画布上绘制想要绘制的图片 后面传的0 0是x和y的定位,在后面的width和height是宽和高 是style里的宽和高
        ctx.drawImage(videoDom, 0, 0, videoSize.width, videoSize.height);

        // getImageData() 方法返回 ImageData 对象,该对象拷贝了画布指定矩形的像素数据
        // 经过测试每一帧都会返回这个imgData
        var imgData = ctx.getImageData(
          0,
          0,
          videoSize.width,
          videoSize.height
        ).data;
        // 清空指定的矩形
        ctx.clearRect(0, 0, videoSize.width, videoSize.height);
        // 设置字体大小 指定字体为 Verdana
        ctx.font = gap + "px Verdana";

        for (var h = 0; h < videoSize.height; h += gap) {
          for (var w = 0; w < videoSize.width; w += gap) {
            var position = (videoSize.width * h + w) * 4;
            var r = imgData[position],
              g = imgData[position + 1],
              b = imgData[position + 2];

            var gray = (r * 30 + g * 59 + b * 11 + 50) / 100;

            var i = Math.min(
              asciiList.length - 1,
              parseInt(gray / (255 / asciiList.length))
            );

            // 可以支持彩色
            ctx.fillStyle = "rgb(" + r + "," + g + "," + b + ")";

            // 使用fillText在画布上绘制文字,后面是坐标
            ctx.fillText(asciiList[i], w, h);
          }
        }
      }
      // 初始化视频
      function init() {
        // 生成一个视频标签 给视频标签添加上一些属性
        var videoDom = document.createElement("video");
        // var videoDom = document.querySelector("video");
        // videoDom.src = "./video/badapple.mp4";
        videoDom.src = "./video/dy.mp4";
        videoDom.style.width = "720px";
        videoDom.style.height = "540px";
        // videoDom.style.display = "none";
        // document.body.appendChild(videoDom);

        // 生成一个div 给div添加一些属性 显示一个play的样式
        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 = videoDom.style.height;
        btnPlayAndPause.style.lineHeight = videoDom.style.height;
        btnPlayAndPause.style.cursor = "pointer";
        btnPlayAndPause.style.fontSize = "30px";
        btnPlayAndPause.style.zIndex = 2;
        // 给play文字的颜色改成红色
        btnPlayAndPause.style.color = "red";
        btnPlayAndPause.innerText = "play";
        // 让他居中再页面上,给body的子集添加他
        document.body.appendChild(btnPlayAndPause);

        // 监听play的点击事件
        btnPlayAndPause.addEventListener("click", function () {
          // 通过判断中间的文字有没有来确定他有没有再播放
          // 调用视频标签的播放和暂停API
          if (btnPlayAndPause.innerText === "play") {
            videoDom.play();
          } else {
            videoDom.pause();
          }
        });

        // 监听视频的播放之前  在视频准备好播放之前会触发这个钩子
        videoDom.addEventListener("canplay", function () {
          renderVideoFrame(videoDom);
        });

        // 开始播放的时候将中间文字清空
        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);
        }
      }
      window.onload = function () {
        init();
      };
    </script>
  </head>
  <body></body>
</html>

上面是源码,下面开始分析写了什么东西

​ 首先这个代码的入口再最下面

window.onload = function () {
     init();
};
// 在window加载完毕之后调用init()这个方法

那么我们去看init()这个方法干了什么

//这个方法很长,我们分段说

# 1.声明视频标签,设置标签属性
var videoDom = document.createElement("video");
videoDom.src = "./video/badapple.mp4";
videoDom.style.width = "720px";
videoDom.style.height = "540px";

# 2.生成一个div 给div添加一些属性 显示一个play的样式
//这个盒子主要是用来存放中间的play字样
//下面就是设置一堆属性
var btnPlayAndPause = document.createElement("div");
// 给play文字的颜色改成红色
btnPlayAndPause.style.color = "red";
btnPlayAndPause.style.textAlign = "center";
btnPlayAndPause.style.position = "absolute";
btnPlayAndPause.style.top = btnPlayAndPause.style.left = "0px";
//这个div盒子的大小和视频的大小是一样的
btnPlayAndPause.style.width = videoDom.style.width;
btnPlayAndPause.style.height = videoDom.style.height;
btnPlayAndPause.style.lineHeight = videoDom.style.height;
btnPlayAndPause.style.cursor = "pointer";
btnPlayAndPause.style.fontSize = "30px";
btnPlayAndPause.style.zIndex = 2;
btnPlayAndPause.innerText = "play";
// 给body的子集添加他
document.body.appendChild(btnPlayAndPause);

# 3.监听play的点击事件
btnPlayAndPause.addEventListener("click", function () {
// 通过判断中间的文字有没有来确定他有没有再播放
// 调用视频标签的播放和暂停API
if (btnPlayAndPause.innerText === "play") {
		videoDom.play();
	} else {
		videoDom.pause();
	}
});

# 4.监听视频的播放之前  在视频准备好播放之前会触发这个钩子
# 这里调用了renderVideoFrame方法,先暂且不说下面就会看这个方法
# 这里传递进去的是一个视频的dom!
videoDom.addEventListener("canplay", function () {
	renderVideoFrame(videoDom);
});


# 5.剩下这三个不重要,通俗易懂
// 开始播放的时候将中间文字清空
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();
});

# 6.这三个看起来可能会有点迷
# 我的理解就是点开始就调用动画,把他储存起来方便后面的清除和递归调用
var timerId;
function startRender() {
	timerId = requestAnimationFrame(updateRender);
}
function updateRender() {
	renderVideoFrame(videoDom);
	// 递归调用这个动画
	timerId = requestAnimationFrame(updateRender);
}
#这个是清除,跟上面那俩没多大关系,他俩没调用这个
function stopRender() {
	cancelAnimationFrame(timerId);
}

我们看完了init()方法,就继续看在其中被调用的renderVideoFrame()方法

# 1.先看声明
function renderVideoFrame(videoDom, gap = 12)
//这个方法接收了俩参数,第一个参数就是传递过来的视频dom对象
//第二个参数是一个数字,默认值为12,是我们渲染到canvas中文字的默认大小
//然后继续向下看

# 2.声明要渲染进去的文字
var asciiList = ["小", "米", " ", " ", " "];