[小Demo]Canvas手写b站弹幕效果!塔塔开哟!

最近,《进击的巨人》动漫也是迎来了大结局!不知道这样的结局是大家所期望的结局,还是心中的"意难平!"

今天我们就来做一个Demo,来和我们的艾伦塔塔开哟!

大家都看过B站上的"弹幕飞天","弹幕护体"吧

我们今天就来带大家用JS中的canvas画布手搓一个弹幕效果!

其实要这个效果就是在我们的视频上方添加了一层我们看不到的canvas画布!然后通过canvas实现这样一个弹幕满天飞的效果!

什么是canvas?

Canvas API 提供了一个通过JavaScriptHTML``元素来绘制图形的方式。它可以用于动画、游戏画面、数据可视化、图片编辑以及实时视频处理等方面。

Canvas API 主要聚焦于 2D 图形。而同样使用<canvas>元素的 WebGL API 则用于绘制硬件加速的 2D 和 3D 图形。

我们可以通过canvas在页面上绘制我们想要的图形!今天我们先浅浅介绍一下我们实现弹幕需要了解的内容!

首先,我们可以在htmlbody中加入一个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尝试!

上效果

这样我们的效果就实现啦!其实还有很多可以优化的地方!小伙伴们可以尽情去尝试哦!

看到这里了!--点个赞再走呗!🥺🥺🥺

个人gitee库:MycodeSpace: 主要应用的仓库,记录学习coding中的点点滴滴 (gitee.com)

相关推荐
Boilermaker19926 分钟前
【Java EE】SpringIoC
前端·数据库·spring
中微子17 分钟前
JavaScript 防抖与节流:从原理到实践的完整指南
前端·javascript
天天向上102432 分钟前
Vue 配置打包后可编辑的变量
前端·javascript·vue.js
芬兰y1 小时前
VUE 带有搜索功能的穿梭框(简单demo)
前端·javascript·vue.js
好果不榨汁1 小时前
qiankun 路由选择不同模式如何书写不同的配置
前端·vue.js
小蜜蜂dry1 小时前
Fetch 笔记
前端·javascript
拾光拾趣录1 小时前
列表分页中的快速翻页竞态问题
前端·javascript
小old弟1 小时前
vue3,你看setup设计详解,也是个人才
前端
Lefan1 小时前
一文了解什么是Dart
前端·flutter·dart
Patrick_Wilson1 小时前
青苔漫染待客迟
前端·设计模式·架构