最近,《进击的巨人》动漫也是迎来了大结局!不知道这样的结局是大家所期望的结局,还是心中的"意难平!"
今天我们就来做一个Demo,来和我们的艾伦塔塔开哟!
大家都看过B站上的"弹幕飞天","弹幕护体"吧
我们今天就来带大家用JS中的canvas
画布手搓一个弹幕效果!
其实要这个效果就是在我们的视频上方添加了一层我们看不到的canvas
画布!然后通过canvas
实现这样一个弹幕满天飞的效果!
什么是canvas?
Canvas API 提供了一个通过JavaScript 和 HTML的``元素来绘制图形的方式。它可以用于动画、游戏画面、数据可视化、图片编辑以及实时视频处理等方面。
Canvas API 主要聚焦于 2D 图形。而同样使用<canvas>
元素的 WebGL API 则用于绘制硬件加速的 2D 和 3D 图形。
我们可以通过canvas
在页面上绘制我们想要的图形!今天我们先浅浅介绍一下我们实现弹幕需要了解的内容!
首先,我们可以在html
的body
中加入一个canvas
闭标签
html
<canvas id="canvas" width="300" height="300">
canvas
是一个闭标签,因为它也是媒体标签 ,所以可以直接在标签里面设置宽和高 ,并且不需要加单位!
我们可以实现Dom结构->Html结构
来实现我们的弹幕效果!
并且canvas
自身带了一些方法!
DOM结构: DOM是在浏览器中生成的文档对象模型,它通过JavaScript等脚本语言提供了一种可以动态访问和修改HTML文档结构的方式。DOM以树形结构表示文档,每个HTML元素都是DOM树中的一个节点。通过DOM,你可以通过JavaScript来操纵HTML文档的内容、结构和样式。
HTML结构: HTML(Hypertext Markup Language)是一种标记语言,用于创建网页的结构。HTML文档的基本结构通常包含DOCTYPE声明、html、head和body标签等。
注意!画布没有文档流的规则,得依靠坐标!
js
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");/*创建一个2d的画布*/
我们要获取到canvas
的上下文才能对它进行操作,同时用getContext
规定使用2D
画布
2D绘图
1、绘制文本
js
ctx.font = '30px sans-serif';//字体大小 和 字体类型
ctx.fillStyle ='red'//颜色
//实心文本
ctx.fillText('今天,你学习了嘛?',10,100)//填充文本 内容 点的位置(x,y)
//空心文本 注意 ctx.stroke描绘没有填充色的内容
ctx.strokeText('我学习了',10,200)
2、绘制矩形
js
// 绘制矩形
ctx.fillStyle = "red";
// 绘制实心矩形
ctx.fillRect(50, 50, 100, 80);
// 绘制空心矩形
ctx.strokeRect(200, 200, 100, 80);
3、绘制圆
js
// 绘制圆形
ctx.beginPath();
ctx.arc(200, 50, 30, 0, 2 * Math.PI);
ctx.fillStyle = "blue";
ctx.fill();
4、连线
js
// 路径的开始
ctx.beginPath();
// moveTO 50,50是起始位置
ctx.moveTo(50,50)
//lineTo 目标位置
ctx.lineTo(200,50)
ctx.lineTo(200,200)
ctx.closePath();//闭合路径,闭合三个点
ctx.stroke()//绘制,描绘一下,之前是临摹
ctx.fill()//为封闭的图形填充颜色
好了,关于canvas
的用法就介绍到这里!接下来就开始,我们今天的正题!
实现弹幕效果
整个效果的实现只需要一个html
文件和一个js
文件
1、HTML文件
html
文件还是很简单的!
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#canvas{
position: absolute;
}
</style>
</head>
<body>
<div class="wrap">
<h1>进击的巨人</h1>
<div class="main">
<canvas id="canvas"></canvas>
<video src="./进击的巨人.mp4" id="video" controls="true" width="720" height="480"></video>
</div>
<div class="content">
<input type="text" id="text">
<input type="button" value="发弹幕" id="btn">
<input type="color" id="color">
<input type="range" id="range" max="40" min="20">
</div>
</div>
<!-- js的执行会阻塞html的执行 -->
<script src="./index.js">
</script>
</body>
</html>
这里,我们唯一要注意的就是:我们要在最后再导入js
,因为js
的存在会阻塞html
的执行!
我们body
当中的主要成分:
- 包含一个标题
<h1>
和三个div
元素。 - 第一个
div
元素有类名为 "wrap",包含页面的主要内容。 - 第二个
div
元素有类名为 "main",包含一个<canvas>
元素和一个<video>
元素,用于显示视频和弹幕。 <canvas>
元素用于绘制弹幕。<video>
元素用于显示视频,设置了视频源、控件(播放器控制条)以及宽度和高度。- 第三个
div
元素有类名为 "content",包含一组用户输入和控制元素。 - 包含一个文本输入框
<input type="text" id="text">
用于输入弹幕内容。 - 包含一个按钮
<input type="button" value="发弹幕" id="btn">
用于发送弹幕。 - 包含一个颜色选择器
<input type="color" id="color">
用于选择弹幕颜色。 - 包含一个滑动条
<input type="range" id="range" max="40" min="20">
用于调整弹幕大小。
2、JS文件
js
// window.onload保证页面加载完再执行js
// window.onload = function() {
// }
//用new每一次运行一次都会new一下,工厂模式!
//历史弹幕
let data = [
{value:'口里哇,自由大',color:'red',fontSize:30,time:5},
{value:'男神',color:'blue',fontSize:26,time:5},
{value:'塔塔开哟',color:'red',fontSize:22,time:5},
{value:'一抖哟',color:'green',fontSize:22,time:10},
{value:'男神',color:'yellow',fontSize:26,time:15},
{value:'艾伦~!!!',color:'yellow',fontSize:26,time:20},
{value:'自由嗒',color:'red',fontSize:25,time:16},
{},
]
//整理弹幕数据
CanvasBarrage.prototype.render = function() {
//清除画布
this.clear()
this.renderBarrage()//弹幕绘制再画布上
if(!this.isPaused) {//播放状态
requestAnimationFrame(this.render.bind(this))//这个也是定时器 递归,无限循环
}
}
CanvasBarrage.prototype.clear = function() {
this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height)//清除画布
}
CanvasBarrage.prototype.renderBarrage = function()
{
//拿到视频播放的时间,当前,根据播放的时间判断要不要绘制上
let time=this.video.currentTime
//遍历所有的弹幕
this.barrages.forEach(function(barrage){
if(time>=barrage.time){
//执行一遍后,数组里面所有的值都会重新被初始化,下面让被初始化的弹幕对象就不再操作
if(!barrage.isInit&&!barrage.flag){
barrage.init()
barrage.isInit=true
}
//控制弹幕左移
barrage.x-=barrage.speed
barrage.renderEach()
if(barrage.x<-barrage.width){//从左边出屏幕
barrage.flag = true
}
}
})
}
CanvasBarrage.prototype.add = function(obj){
//添加新的弹幕
this.barrages.push(new Barrage(obj,this))//this.barrages新增16.7ms之后会重新渲染一波
}
function CanvasBarrage(canvas,video,opts = {}) {
if(!canvas||!video) return
// this是实例对象
this.video = video
this.canvas = canvas
// 设置Canvas宽高和video保持一致
this.canvas.width = video.width
this.canvas.height = video.height
//获取画布
this.ctx = canvas.getContext('2d')
//初始化弹幕
let defOpts = {
color:'#e91e63',
fontSize:20,
speed:1.5,
opacity:0.5,
data:[]
}
Object.assign(this,defOpts,opts)//直接往this身上挂key值
//视频播放,弹幕才出现
this.isPaused = true
//获取到所有的弹幕
//map会返回一个新的数组
this.barrages = this.data.map((item)=>new Barrage(item,this))//将每一条弹幕都修饰一下
//移动弹幕
this.render()
}
// let canvas = document.getElementById('canvas')
// new CanvasBarrage(canvas,video,{value:''})
let canvas = document.getElementById('canvas')
// video是因为知道此时视频多少秒
let video = document.getElementById('video')
// $没有意义,区分罢了
let $text = document.getElementById('text')
let $btn = document.getElementById('btn')
let $color = document.getElementById('color')
let $range = document.getElementById('range')
$btn.addEventListener('click', send)
$text.addEventListener('keyup', function(e){
console.log(e)
if(e.keyCode===13)
{
send()
}
})
//添加弹幕数据
//创建整理弹幕的实例对象
let canvasBarrage = new CanvasBarrage(canvas,video,{data})
video.addEventListener('play', function(){
canvasBarrage.isPaused = false
canvasBarrage.render()//处理每一条弹幕!
})
function send(){
//读取文本内容
let value = $text.value
//当前视频播放时间
let time = video.currentTime
let color = $color.value
let fontSize= $range.value
let obj = {value:value,color:color,fontSize:fontSize,time:time}
//add吧obj放进去,接收新的弹幕,再执行一遍
canvasBarrage.add(obj)
// console.log(obj)
}
//修饰一条弹幕,为箭头函数服务
//第一个参数是一条弹幕,第二个是上一个函数的this
//init修饰的意思
Barrage.prototype.init = function(){
this.color = this.obj.color||this.context.color//如果前者为undefined则会读取第二个
this.speed = this.obj.speed||this.context.speed
this.opacity = this.obj.opacity||this.context.opacity
this.fontSize = this.obj.fontSize||this.context.fontSize
//每条弹幕的宽度
let p = document.createElement('p')
p.style.fontSize = this.fontSize + 'px'
p.innerHTML = this.value
document.body.appendChild(p)
this.width = p.offsetWidth
document.body.removeChild(p)
//设置弹幕的位置
this.x = this.context.canvas.width
this.y = this.context.canvas.height*Math.random()
//上下方,超出边界
if(this.y<this.fontSize){
this.y = this.fontSize
}else if(this.y>this.context.canvas.height-this.fontSize)
{
this.y = this.context.canvas.height - this.fontSize
}
}
Barrage.prototype.renderEach = function(){
//这条弹幕绘制在画布上
//设置画布的文字字体和字号
//设置画布的文字颜色
//绘制文字
this.context.ctx.font = `${this.fontSize}px Arial`
this.context.ctx.fillStyle = this.color
this.context.ctx.fillText(this.value,this.x,this.y)
}
function Barrage(obj,context){//context == this
this.value = obj.value//新弹幕的内容
this.time = obj.time
this.obj = obj
this.context = context
}
js
文件还是比较长的!但是每一处我都标注了作用哦!大家可以直接c + v
尝试!
上效果
这样我们的效果就实现啦!其实还有很多可以优化的地方!小伙伴们可以尽情去尝试哦!
看到这里了!--点个赞再走呗!🥺🥺🥺