目前代码还是有一些bug,主要是调整浏览器大小的时候会出现文字运动停顿等问题,后续有时间在解决,不同文字长度速度不同而产生的文字堆叠问题已经解决,原理就是追击原理,代码如下,看效果可以直接拷贝到vscode打开浏览器运行即可()
效果图
<!DOCTYPE html>
<html lang="en">
<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>Document</title>
</head>
<body>
<style>
.container {
width: 600px;
height: 400px;
border: 1px solid #ccc;
margin: 0 auto;
position: relative;
overflow: hidden;
}
.scroll {
white-space: nowrap;
transition: all 3s;
}
.input {
position: absolute;
top: 450px;
left: 700px;
}
.anima {
animation: fade 2s ease-in-out linear;
}
</style>
<div class="container"></div>
<div class="input">
<button id="stop_run">暂停</button>
<button id="start_run">开始</button>
</div>
<script>
let container = document.querySelector(".container");
let text = document.getElementById("text");
let stop_run = document.getElementById("stop_run");
let start_run = document.getElementById("start_run");
let { width: containerWidth, right: containerRight } =
container.getBoundingClientRect();
let bullet = {
left: 0,
duration: 0,
startTime: 0,
top: 0,
width: 0,
height: 0,
};
let cnStr = "这是一个可以停止的弹幕";
let abc = "abcdefghigklmnpqrst";
let upperABC = abc.toUpperCase();
let channel = [];
let id = 0;
let flag = true;
let enterId;
let zIndex = 9999;
function randomNum(max, min) {
return parseInt(Math.random() * (max - min + 1) + min, 10);
}
function productData() {
let arr = [];
let preWidth = 600;
for (let i = 0; i < 50; i++) {
let str =
i +
cnStr.substr(0, randomNum(-1, cnStr.length)) +
upperABC.substr(0, randomNum(-1, upperABC.length)) +
abc.substr(0, randomNum(-1, abc.length));
arr.push(str);
}
return arr;
}
function enterBullet(e) {
console.log("enter");
if (flag) {
flag = false;
let index = this.channel.findIndex((val) => val._id === e.target._id);
if (index !== -1) {
let targetBullet = this.channel.splice(index, 1)[0];
enterId = targetBullet._id;
targetBullet.style.transform = `translateX(-${targetBullet.currentPosition}px)`;
targetBullet.stopStartTime = new Date().getTime();
targetBullet.distance = targetBullet.width + containerWidth;
}
}
}
function leaveBullet(e) {
let targetBullet = e.target;
if (enterId === targetBullet._id) {
flag = true;
targetBullet.style.transform = `translateX(-${targetBullet.distance}px)`;
targetBullet.stopEndTime = new Date().getTime();
targetBullet.stopTime +=
targetBullet.stopEndTime - targetBullet.stopStartTime;
targetBullet.style.transition = `all ${targetBullet.leftDruation}s linear 0s`;
this.channel.unshift(targetBullet);
if (this.channel.length === 1) {
this.timer = window.requestAnimationFrame(
this.loopWatcherLaunch.bind(this)
);
}
}
}
function initBullet(str, top, type = "") {
if (str) {
let div = document.createElement("div");
div.innerHTML = str;
div.style.position = "absolute";
div.style.left = containerWidth + "px";
div.style.top = top + "px";
div.style.whiteSpace = "nowrap";
if (type === "current") {
div.style.border = "1px solid red";
div.className = "anima";
}
div.onmouseenter = enterBullet.bind(this);
div.onmouseleave = leaveBullet.bind(this);
container.appendChild(div);
div.width = div.clientWidth;
return div;
}
return null;
}
class Barrage {
constructor({ container, data, top = 0, duration = 5 }) {
this.data = data;
this.channel = [];
this.top = top;
this.duration = duration;
this.timer = null;
this.init();
}
init() {
this.loopWatcherLaunch = this.wrapper.call(this);
this.loopWatcherLaunch.call(this);
}
wrapper() {
let div = initBullet.call(this, this.data.shift(), this.top);
return function () {
let firstChnnel = this.channel[0];
if (firstChnnel) {
let timeNow = new Date().getTime();
if (timeNow > firstChnnel.endTime + firstChnnel.stopTime) {
container.removeChild(firstChnnel);
this.channel.shift();
}
}
// 子弹初始化
let check = this.checkIsLauncch(div);
if (check && div) {
this.launchBullet.call(this, div);
div = null;
if (data.length > 0) {
div = initBullet.call(this, this.data.shift(), this.top);
}
}
// 实时更新子弹状态
this.update();
if (this.channel.length > 0) {
this.timer = window.requestAnimationFrame(
this.loopWatcherLaunch.bind(this)
);
}
};
}
launchBullet(div) {
let startTime = +new Date();
let { width } = div;
div.leftDistance = width + containerWidth;
div.distance = width + containerWidth;
div.startTime = startTime;
div.currentTime = startTime;
div.stopTime = 0;
div.stopStartTime = startTime;
div.stopEndTime = startTime;
div.predictEndTime = startTime + this.duration * 1000;
div.endTime = startTime + this.duration * 1000;
div.duration = this.duration;
div.moveV = div.distance / div.duration;
div.leftDruation = div.leftDistance / div.moveV;
div.currentPosition = div.distance - div.leftDistance;
div._id = id++;
if (zIndex < 1) zIndex = 9999;
div.style.zIndex = zIndex--;
div.style.transition = `all ${div.leftDruation}s linear 0s`;
div.style.transform = `translateX(-${div.distance}px)`;
this.channel.push(div);
}
update() {
this.channel.map((val) => {
val.currentTime = new Date().getTime();
val.leftDistance =
val.distance -
((val.currentTime - val.startTime - val.stopTime) / 1000) *
val.moveV;
val.leftDruation = val.leftDistance / val.moveV;
val.currentPosition = val.distance - val.leftDistance;
});
}
checkIsLauncch(dom) {
let lastDom = this.channel[this.channel.length - 1];
if (lastDom && dom) {
let lastRect = lastDom.getBoundingClientRect();
let newsRect = dom.getBoundingClientRect();
if (lastDom.leftDistance > containerWidth - 20) return false;
let lastS = lastDom.leftDistance;
let lastV = lastDom.moveV;
let lastT = lastS / lastV;
let newsS = containerWidth;
let newsV = (containerWidth + newsRect.width) / this.duration;
let newsT = newsS / newsV;
if (newsT < lastT) {
return false;
}
}
return true;
}
getCurrentDataLen() {
return this.data.length;
}
stop() {
window.cancelAnimationFrame(this.timer);
this.timer = null;
this.channel.map((val) => {
val.style.transform = `translateX(-${val.currentPosition}px)`;
val.stopStartTime = new Date().getTime();
val.distance = val.width + containerWidth;
});
}
start() {
this.channel.map((val) => {
val.style.transform = `translateX(-${val.distance}px)`;
val.stopEndTime = new Date().getTime();
val.stopTime += val.stopEndTime - val.stopStartTime;
val.style.transition = `all ${val.leftDruation}s linear 0s`;
});
this.timer = window.requestAnimationFrame(
this.loopWatcherLaunch.bind(this)
);
}
addLaunch(value) {
if (!value) {
return;
}
if (this.data.length > 0) {
this.data.push(value);
} else {
this.data.unshift(value);
this.loopWatcherLaunch.call(
this,
initBullet(this.data[0], this.top)
);
}
}
}
let barrageArr = [];
let data = productData();
// let barrage = new Barrage(container, ["222", "333"]);
let barrage = new Barrage({ container, data, top: 0, duration: 5 });
// barrageArr[i] = {
// instance: barrage,
// length: barrage.getCurrentDataLen(),
// };
stop_run.onclick = function () {
barrage.stop();
};
start_run.onclick = function () {
barrage.start();
};
</script>
</body>
</html>