目录
- 一、介绍
- 二、准备
- 三、⽬标
- 四、代码
- 五、完成
一、介绍
弹幕指直接显现在视频上的评论,可以以滚动、停留甚⾄更多动作特效⽅式出现在视频上,是观看视频的⼈发送的简短评论。通过发送弹幕可以给观众⼀种“实时互动”的错觉,弹幕的出现让观看过程充满乐趣。本题需要在已提供的基础项⽬中,完成视频弹幕的功能。
二、准备
开始答题前,需要先打开本题的项⽬代码⽂件夹,⽬录结构如下:
├── effect.gif
├── css
│ └── style.css
├── video
│ └── video1.webm
├── index.html
└── js
└── index.js
其中:
- index.html 是主⻚⾯。
- js/index.js 是需要补充代码的 js ⽂件。
- css/style.css 是样式⽂件。
- effect.gif 是完成的效果图。
- video 是存放视频的⽂件夹。
在浏览器中预览 index.html ⻚⾯,显示如下所示:
三、⽬标
请在 js/index.js ⽂件中补全代码。具体需求如下:
- 补全 renderBullet 函数中的代码,控制弹幕的显示颜⾊和移动。功能说明如下:
- 每个弹幕内容包裹在 span 标签中,作为⼦节点插⼊到 #video 元素节点内。
- ⽣成的 span 元素节点相对于 #video 元素绝对定位 ,初始位置的 left 是 #video 元素的宽, top 是 #video 元素的⾼内的随机数。
注意:需求中所需样式可直接通过已提供的 getEleStyle ⽅法获取。
- 弹幕每隔 bulletConfig.time (弹幕配置对象) 时间,向左移动距离为bulletConfig.speed (弹幕配置对象)。
- 当弹幕最右端完全移出 #video 元素时,移除 span 元素。
- 补全 #sendBulletBtn 元素的绑定事件,点击发送按钮,输⼊框中的⽂字出现在弹幕中,样式不同于普通弹幕(样式红⾊字体红⾊框已设置,类名为 create-bullet )。通过调⽤renderBullet ⽅法和正确的传参实现功能。
最终效果可参考⽂件夹下⾯的 gif 图,图⽚名称为 effect.gif(提示:可以通过 VS Code 或者浏览器预览gif 图⽚)。
四、代码
index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>视频弹幕</title>
<link rel="stylesheet" href="./css/style.css" />
</head>
<body>
<div class="container">
<div class="video-header">
<h1 class="title">workerman基础</h1>
<div class="video-info">
<span>13.2亿播放</span>
<span>656.5万弹幕</span>
<span>2022-12-11 15:29:24</span>
</div>
</div>
<div id="video">
<video src="./video/video1.webm" poster="" id="vd" controls>
您的浏览器不支持 video 标签。
</video>
</div>
<div class="player-sending-area">
<p class="player-video-info">231 人正在看,4396 条弹幕</p>
<div class="switch-box">
<input
type="checkbox"
name=""
id="switchButton"
class="switch"
checked
/>
<label for="switchButton"></label>
</div>
<input
type="text"
name=""
id="bulletContent"
placeholder="请输入您的弹幕"
/>
<button id="sendBulletBtn">发送</button>
</div>
</div>
<script src="./js/index.js"></script>
</body>
</html>
css/style.css
* {
margin: 0;
padding: 0;
}
.container {
width: 850px;
margin: 0px auto;
}
.container .video-header {
box-sizing: border-box;
padding: 24px 0;
}
.container .video-header .title {
font-size: 20px;
font-family: PingFang SC, HarmonyOS_Regular, Helvetica Neue, Microsoft YaHei,
sans-serif;
font-weight: 500;
margin-bottom: 6px;
color: #18191c;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.container .video-header .video-info {
display: flex;
align-items: center;
}
.container .video-header .video-info span {
margin-right: 12px;
font-size: 13px;
color: #9499a0;
}
#video {
width: 850px;
height: 560px;
background-color: #000;
position: relative;
overflow: hidden;
}
#video > video {
width: 100%;
height: 100%;
}
#video > span {
position: absolute;
white-space: nowrap;
color: #fff;
font-size: 16px;
}
#video > .create-bullet {
border: 1px solid red;
box-shadow: 0 0 5px 5px rgb(255, 84, 84);
padding: 5px 10px;
border-radius: 10px;
color: red !important;
background-color: rgba(255, 255, 255, 0.5);
}
.container .player-sending-area {
display: flex;
align-items: center;
width: 830px;
height: 50px;
background-color: #fff;
box-shadow: 0 1px 5px rgb(207, 207, 207);
padding: 0 10px;
}
.container .player-sending-area .player-video-info {
font-size: 14px;
color: rgb(95, 95, 95);
margin: 0 15px;
}
.container .player-sending-area .switch-box {
width: 48px;
}
.container .player-sending-area .switch-box .switch {
display: none;
}
.container .player-sending-area .switch-box label {
position: relative;
display: block;
margin: 1px;
height: 28px;
cursor: pointer;
}
.container .player-sending-area .switch-box label::before {
content: "";
position: absolute;
top: 50%;
left: 50%;
margin-top: -13px;
margin-left: -14px;
width: 26px;
height: 26px;
border-radius: 100%;
background-color: #fff;
box-shadow: 1px 1px 1px 1px rgba(0, 0, 0, 0.06);
transform: translateX(-9px);
transition: all 0.3s ease;
}
.container .player-sending-area .switch-box .switch:checked ~ label::before {
transform: translateX(10px);
}
.container .player-sending-area .switch-box label::after {
content: "";
display: block;
border-radius: 30px;
height: 28px;
background-color: #dcdfe6;
transition: all 0.3s ease;
}
.container .player-sending-area .switch-box .switch:checked ~ label::after {
background-color: #00a1d6;
}
.container .player-sending-area > input {
width: 250px;
height: 30px;
margin-left: 20px;
border: 1px solid #e7e7e7;
outline: none;
text-indent: 10px;
background-color: rgb(245, 244, 244);
font-size: 12px;
color: rgb(36, 35, 35);
}
.container .player-sending-area > input::placeholder {
font-size: 12px;
}
.container .player-sending-area > button {
width: 60px;
height: 32px;
color: #fff;
background-color: #00a1d6;
border: none;
}
js/index.js
const bullets = [
"前方高能",
"原来如此",
"这么简单",
"学到了",
"学费了",
"666666",
"111111",
"workerman",
"学习了",
"别走,奋斗到天明",
];
/**
* @description 根据 bulletConfig 配置在 videoEle 元素最右边生成弹幕,并移动到最左边,弹幕最后消失
* @param {Object} bulletConfig 弹幕配置
* @param {Element} videoEle 视频元素
* @param {boolean} isCreate 是否为新增发送的弹幕,为 true 表示为新增的弹幕
*
*/
function renderBullet(bulletConfig, videoEle, isCreate = false) {
const spanEle = document.createElement("SPAN");
spanEle.classList.add(`bullet${index}`);
if (isCreate) {
spanEle.classList.add("create-bullet");
}
// TODO:控制弹幕的显示颜色和移动,每隔 bulletConfig.time 时间,弹幕移动的距离 bulletConfig.speed
}
document.querySelector("#sendBulletBtn").addEventListener("click", () => {
// TODO:点击发送按钮,输入框中的文字出现在弹幕中
});
function getEleStyle(ele) {
// 获得元素的width,height,left,right,top,bottom
return ele.getBoundingClientRect();
}
function getRandomNum(end, start = 0) {
// 获得随机数,范围是 从start到 end
return Math.floor(start + Math.random() * (end - start + 1));
}
// 设置 index 是为了弹幕数组循环滚动
let index = 0;
const videoEle = document.querySelector("#video");
// 弹幕配置
const bulletConfig = {
isHide: false, // 是否隐藏
speed: 5, // 弹幕的移动距离
time: 50, // 弹幕每隔多少ms移动一次
value: "", // 弹幕的内容
};
let isPlay = false;
let timer; // 保存定时器
document.querySelector("#vd").addEventListener("play", () => {
// 监听视频播放事件,当视频播放时,每隔 1000s 加载一条弹幕
isPlay = true;
bulletConfig.value = bullets[index++];
renderBullet(bulletConfig, videoEle);
timer = setInterval(() => {
bulletConfig.value = bullets[index++];
renderBullet(bulletConfig, videoEle);
if (index >= bullets.length) {
index = 0;
}
}, 1000);
});
document.querySelector("#vd").addEventListener("pause", () => {
isPlay = false;
clearInterval(timer);
});
document.querySelector("#switchButton").addEventListener("change", (e) => {
if (e.target.checked) {
bulletConfig.isHide = false;
} else {
bulletConfig.isHide = true;
}
});
五、完成
/**
* @description 根据 bulletConfig 配置在 videoEle 元素最右边生成弹幕,并移动到最左边,弹幕最后消失
* @param {Object} bulletConfig 弹幕配置
* @param {Element} videoEle 视频元素
* @param {boolean} isCreate 是否为新增发送的弹幕,为 true 表示为新增的弹幕
*
*/
function renderBullet(bulletConfig, videoEle, isCreate = false) {
const spanEle = document.createElement('SPAN')
spanEle.classList.add(`bullet${index}`)
if (isCreate) {
spanEle.classList.add('create-bullet')
}
// TODO:控制弹幕的显示颜色和移动,每隔 bulletConfig.time 时间,弹幕移动的距离 bulletConfig.speed
if (bulletConfig.isHide) {
return
}
//设置弹幕随机颜色
spanEle.style.color = `rgb(${getRandomNum(255)},${getRandomNum(255)},${getRandomNum(255)})`
//设置弹幕内容
spanEle.innerHTML = bulletConfig.value
// ⽣成的 span 元素节点相对于 #video 元素绝对定位 ,
spanEle.style.position = 'absolute'
//初始位置的 left 是 #video 元素的宽,
let leftSpan = getEleStyle(videoEle).width
spanEle.style.left = leftSpan + 'px'
// top 是 #video 元素的⾼内的随机数。
spanEle.style.top = getRandomNum(getEleStyle(videoEle).height) + 'px'
let _timer = setInterval(() => {
//弹幕每隔 bulletConfig.time (弹幕配置对象) 时间,向左移动距离为
leftSpan -= bulletConfig.speed
spanEle.style.left = leftSpan + 'px'
//每个弹幕内容包裹在 span 标签中,作为⼦节点插⼊到 #video 元素节点内
videoEle.appendChild(spanEle)
if (leftSpan < 0) {
clearInterval(_timer)
videoEle.removeChild(spanEle)
}
}, bulletConfig.time)
}
document.querySelector('#sendBulletBtn').addEventListener('click', () => {
// TODO:点击发送按钮,输入框中的文字出现在弹幕中
bulletConfig.value = document.querySelector('#bulletContent').value
renderBullet(bulletConfig, videoEle, true)
})