本弹幕组件要实现的效果:
在输入框中按下回车键以后将输入框中的内容生成为弹幕,弹幕从视频区域的右侧进入,不断向左移动,当弹幕完全移动到视频区域的左侧外面时使弹幕从内存中消失,防止内存泄漏。
生成弹幕的条件:
按下的必须是回车键,并且输入框中的内容不能 全为空格或者没有内容。
html代码部分:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>弹幕</title>
<style>
/* 根据视频宽高设置输入框的宽度以及大容器的宽高 */
input {
width: 528px;
height: 40px;
font-size: 30px;
}
.div0 {
width: 536px;
height: 540px;
position: relative;
overflow: hidden;
}
</style>
</head>
<body>
<div class="div0">
<video src="./test.mp4" controls></video>
<input type="text" />
</div>
<script type="module">
import Bullet from "./Bullet.js";
var input = document.querySelector("input");
document.addEventListener("keyup", keyHandler);
function keyHandler(e) {
if (e.keyCode !== 13) return;
if (input.value.trim().length === 0) return;
var elem = new Bullet(input.value);
elem.appendTo(".div0");
input.value = "";
}
</script>
</body>
</html>
js代码部分:
观察者 TimeManager.js:
export default class TimeManager {
ids;
list = new Set();
static _instance;
constructor() {}
static get instance() {
if (!TimeManager._instance)
Object.defineProperty(TimeManager, "_instance", {
// 将TimeManager的_instance属性的值设置为TimeM一个实例化对象anager的,并且该属性是不可修改、不可枚举、不可修改或删除描述对象的
value: new TimeManager(),
});
return TimeManager._instance;
}
add(elem) {
this.list.add(elem);
if (this.list.size > 0 && !this.ids)
this.ids = setInterval(() => this.update(), 16);
}
remove(elem) {
this.list.delete(elem);
if (this.list.size === 0 && this.ids) {
clearInterval(this.ids);
this.ids = undefined;
}
}
update() {
this.list.forEach((item) => {
if (item.update) item.update();
});
}
}
被观察者 Bullet.js:
import TimeManager from "./TimeManager.js";
export default class Bullet {
elem;
rect;
width;
x = 0;
speed = 2;
constructor(txt) {
this.elem = this.createElem(txt);
}
// 根据传入的内容生成弹幕的内容
createElem(txt) {
if (this.elem) return this.elem;
let div = document.createElement("div");
Object.assign(div.style, {
// 防止弹幕刚进入视频显示区域时出现换行的情况
whiteSpace: "nowrap",
position: "absolute",
});
div.textContent = txt;
return div;
}
appendTo(parent) {
if (typeof parent === "string") parent = document.querySelector(parent);
parent.appendChild(this.elem);
// 获取父容器的宽高及位置等属性
this.rect = parent.getBoundingClientRect();
Object.assign(this.elem.style, {
// 限制弹幕到容器顶部的距离为父容器高度的1/4范围内
top: (Math.random() * this.rect.height) / 4 + "px",
left: this.rect.width + "px",
});
this.x = this.rect.width;
this.width = this.elem.offsetWidth;
// 将当前实例化的弹幕对象添加到TimeManager.instance所对应的list集合中
TimeManager.instance.add(this);
}
update() {
if (!this.width) return;
this.x -= this.speed;
this.elem.style.left = this.x + "px";
// 如果当前实例弹幕对象已经完全移出了视频区域,则将其从TimeManager.instance的list集合中删除,并且将其对应的DOM元素从内存中完全删除
if (this.x < -this.width) {
TimeManager.instance.remove(this);
this.elem.remove();
this.elem = null;
}
}
}