分析学习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 = ["小", "米", " ", " ", " "];