闲来无事,对于bili上面的弹幕感兴趣,于是自己打算尝试制作一个简单的弹幕系统
页面布局
首先先创建一个容器用来存放video播放器和弹幕发射器,注意要有一个遮罩层设置透明度为0,用来显示我们输入的弹幕,位于视频的上方,为了使视频的显示不那么突兀,我们可以把存放视频的容器的背景颜色设置成深色或者黑色
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| <!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> .chat-bullet-container { width: 400px; height: 300px; position: absolute; top: 40px; left: 50%; transform: translateX(-50%); } .chat-bullet-video-container { width: 400px; height: 250px; background-color: rgb(0, 0, 0); } .chat-bullet-video-container video { max-width: 100%; max-height: 100%; position: absolute; top: 50%; left: 50%; transform: translateX(-50%) translateY(-62%); } .chat-bullet-video-container #chat-bullet-video-text { width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0); } .chat-text { position: absolute; top: 70px; left: 70px; color: #000000; } </style> </head> <body> <div class="chat-bullet-container"> <div id="chat-bullet-video-container" class="chat-bullet-video-container"> <video id="video" src="./demo.mp4" controls="true"></video> <div id="chat-bullet-video-text"></div> </div> <div class="chat-bullet-video-method"> <input id="chat-input" type="text"> <button type="text" onclick="handleSend()">发送</button> </div> </div> </body> </html>
|
功能实现
接下来给弹幕发射器添加点击事件,点击的时候让我们发送的文字显示在视频上面
获取遮罩层元素及其宽高,作为弹幕显示和滚动的限制条件
1 2 3
| const chatContainer = document.getElementById('chat-bullet-video-text') const height = chatContainer.offsetHeight const width = chatContainer.offsetWidth
|
为发送弹幕按钮添加点击事件,生成随机颜色和位置的弹幕,然后通过appendChild方法将生成的弹幕放到
1 2 3 4 5 6 7 8 9 10 11 12
| function handleSend() { const input = document.getElementById('chat-input') const msg = input.value const text = document.createElement('span') text.innerHTML = msg let style = `position: absolute; top: ${Math.random() * height}px; right: 30px; color: rgb(${Math.random() * 255},${Math.random() * 255},${Math.random() * 255});` this.element.setAttribute('style', style) this.chatContainer.appendChild(this.element) }
|
这个时候当我们在输入框中输入内容的时候,点击发送的时候就会发现我们写的内容随机显示在视频上面
接下来做的就是让弹幕能够动起来,本次实现的是从右向左移动
但是弹幕不可能只发送一个,弹幕通常来说是很多个的,这个时候如何用上述的方法来添加弹幕和控制弹幕的话显然是不合理。
这个时候我们应该做的是让每个添加的弹幕之间是独立的,互不影响的,能够独立运行,这时候我借鉴了之前学习canvas时使用的方法:创建一个class类,在这个类中实现弹幕的属性初始化,渲染,运行及删除事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| class Chat { constructor(element, chatContainer, data) { this.element = element this.chatContainer = chatContainer this.init() } init() { this.ccx = this.chatContainer.offsetWidth this.ccy = this.chatContainer.offsetHeight this.s = Math.random() + 4 } paint() { let style = `position: absolute; top: ${Math.random() * this.ccy}px; right: ${this.mx}; color: rgb(${Math.random() * 255},${Math.random() * 255},${Math.random() * 255});` this.element.setAttribute('style', style) this.chatContainer.appendChild(this.element) this.mx = -this.element.offsetWidth this.element.style.right = this.mx + 'px' this.element.style.display = 'none' } move() { this.mx = this.mx + this.s this.element.style.display = 'inline-block' this.element.style.right = this.mx + 'px' } delete(index) { if(this.mx >= this.ccx) { this.element.style.display = 'none' } } }
|
点击事件也应该根据实际情况进行修改,同时定义一个数组用来存放我们添加的弹幕的类
1 2 3 4 5 6 7 8 9 10
| const chartList = [] function handleSend() { const input = document.getElementById('chat-input') const msg = input.value const text = document.createElement('span') text.innerHTML = msg const chat = new Chat(text, chatContainer, {color: 'red'}) chat.paint() chartList.push(chat) }
|
但是想要让弹幕动起来可不是那么简单的事情,因为我们上述操作只是使弹幕渲染到了视频上面,并没有使弹幕动起来,这时我们可以定义一个定时器,时间间隔要短,在定时器中我们循环存放弹幕的数组,调用弹幕自身的移动事件及删除事件
1 2 3 4 5 6
| setInterval(() => { for(var i = 0; i < chartList.length; i++) { chartList[i].move() chartList[i].delete(i) } }, 100)
|
将弹幕与视频关联起来
想要做成根据视频播放来显示历史弹幕就要将弹幕和视频当前播放时间进行关联
1 2 3 4 5 6 7 8 9 10 11 12 13
|
video.addEventListener('ended', function() { console.log(video_Status) })
video.addEventListener('canplay', function() { console.log(this.duration) })
video.addEventListener('timeupdate', function() { currentTime = this.currentTime })
|
获取到视频播放时间之后,接下来就是将时间和弹幕关联起来存储,如果没有后台的支持的话可以使用localStorage和indexedDB来存储数据,这里我推介使用indexedDB,因为可以练习一下indexedDB的知识,同时indexedDB的存储空间也比localStorage的存储空间要大很多
首先检测indexedDB是否启用,同时创建所需要使用的数据库和表,设置主键
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function checkIndexedDB(database, table) { const request = window.indexedDB.open(database, 3) request.onupgradeneeded = function(e) { const db = e.target.result if(!db.objectStoreNames.contains(table)) { const tables = db.createObjectStore(table, { keyPath: 'id', autoIncreament: true }) } console.log(123123) db.close() } }
|
然后连接数据库,向表中插入数据
1 2 3 4 5 6 7 8 9
| function linkIndexedDB(database, table) { const request = window.indexedDB.open(database) return new Promise((resolve, reject) => { request.onsuccess = function(e) { const db = e.target.result resolve(db) } }) }
|
同时将弹幕的print()进行修改,每创建一条弹幕就存储一次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| paint() { console.log(currentTime, 'chat') let style = `position: absolute; top: ${Math.random() * this.ccy}px; right: ${this.mx}; color: rgb(${Math.random() * 255},${Math.random() * 255},${Math.random() * 255});` this.element.setAttribute('style', style) this.chatContainer.appendChild(this.element) this.mx = -this.element.offsetWidth this.element.style.right = this.mx + 'px' this.element.style.display = 'none' linkIndexedDB('test', 'chat').then(db => { console.log(db) const request = db.transaction(['chat'], 'readwrite').objectStore('chat') .add({id: Date.now(), element: this.element.innerHTML, style: style, currentTime: currentTime, createUser: 'lxx', speed: this.s}) request.onsuccess = function(e) { console.log('添加成功') db.close() } }) }
|
未完待续。。。。。