preface
Now almost all video websites have the function of bullet screen, so today we use native JavaScript to encapsulate a bullet screen class. This class wants the following attributes and instance methods:
attribute
el
Container node selector, container node should be absolutely positioned, set width and heightheight
The height of each barragemode
In barrage mode, half is half of the container height, top is one-third, and full is fullspeed
The amount of time a bullet screen movesgapWidth
The distance between the last barrage and the previous barrage
methods
pushData
Add barrage metadataaddData
Keep adding barragestart
Start dispatch of barragestop
Stop the barragerestart
Restart the barrageclearData
Empty the barrageclose
Shut downopen
Redisplay barrage
PS: There are some self-encapsulated tool functions will not be posted, probably know the meaning of good
Initialize the
Once the JavaScript files are introduced, we want to use them as follows, starting with the default configuration.
let barrage = new Barrage({
el: '#container'
})
Copy the code
Parameter initialization:
function Barrage(options) {
let {
el,
height,
mode,
speed,
gapWidth,
} = options
this.container = document.querySelector(el)
this.height = height || 30
this.speed = speed || 15000 //2000ms
this.gapWidth = gapWidth || 20
this.list = []
this.mode = mode || 'half'
this.boxSize = getBoxSize(this.container)
this.perSpeed = Math.round(this.boxSize.width / this.speed)
this.rows = initRows(this.boxSize, this.mode, this.height)
this.timeoutFuncs = []
this.indexs = []
this.idMap = []
}
Copy the code
Take the arguments and initialize them. Let’s look at getBoxSize and initRows
function getBoxSize(box) {
let {
height,
width
} = window.getComputedStyle(box)
return {
height: px2num(height),
width: px2num(width)
}
function px2num(str) {
return Number(str.substring(0, str.indexOf('p')))}}Copy the code
The width and height of the box is calculated using the getComputedStyleapi, which is used to calculate the width and height of the container and will be used later.
function initRows(box, mode, height) {
let divisor = getDivisor(mode)
rows = Math.ceil(box.height * divisor / height)
return rows
}
function getDivisor(mode) {
let divisor = . 5
switch (mode) {
case 'half':
divisor = . 5
break
case 'top':
divisor = 1 / 3
break;
case 'full':
divisor = 1;
break
default:
break;
}
return divisor
}
Copy the code
Figure out how many lines the barrage should have based on its height. The number of lines will be used somewhere below.
Insert data
There are two ways to insert data, one is to add source data, and the other is to continuously add data. Let’s start with adding source data:
this.pushData = function (data) {
this.initDom()
if (getType(data) == '[object Object]') {
// Insert a single bar
this.pushOne(data)
}
if (getType(data) == '[object Array]') {
// Insert multiple inserts
this.pushArr(data)
}
}
this.initDom = function () {
if (!document.querySelector(`${el} .barrage-list`)) {
// Register the DOM node
for (let i = 0; i < this.rows; i++) {
let div = document.createElement('div')
div.classList = `barrage-list barrage-list-${i}`
div.style.height = `The ${this.boxSize.height*getDivisor(this.mode)/this.rows}px`
this.container.appendChild(div)
}
}
}
Copy the code
this.pushOne = function (data) {
for (let i = 0; i < this.rows; i++) {
if (!this.list[i]) this.list[i] = []
}
let leastRow = getLeastRow(this.list) // Get the smallest column in the barrage list, which is a two-dimensional array
this.list[leastRow].push(data)
}
this.pushArr = function (data) {
let list = sliceRowList(this.rows, data)
list.forEach((item, index) = > {
if (this.list[index]) {
this.list[index] = this.list[index].concat(... item) }else {
this.list[index] = item
}
})
}
// Cut the one-dimensional bullet-screen list into a two-dimensional array of rows based on the number of rows
function sliceRowList(rows, list) {
let sliceList = [],
perNum = Math.round(list.length / rows)
for (let i = 0; i < rows; i++) {
let arr = []
if (i == rows - 1) {
arr = list.slice(i * perNum)
} else {
i == 0 ? arr = list.slice(0, perNum) : arr = list.slice(i * perNum, (i + 1) * perNum)
}
sliceList.push(arr)
}
return sliceList
}
Copy the code
The method of continuously adding data simply calls the method of adding source data and starts scheduling
this.addData = function (data) {
this.pushData(data)
this.start()
}
Copy the code
Fired barrage
Let’s look at the logic of the barrage
this.start = function () {
// Start scheduling list
this.dispatchList(this.list)
}
this.dispatchList = function (list) {
for (let i = 0; i < list.length; i++) {
this.dispatchRow(list[i], i)
}
}
this.dispatchRow = function (row, i) {
if (!this.indexs[i] && this.indexs[i] ! = =0) {
this.indexs[i] = 0
}
// The actual scheduling starts here, with an instance variable that stores the current scheduling subscript.
if (row[this.indexs[i]]) {
this.dispatchItem(row[this.indexs[i]], i, this.indexs[i])
}
}
Copy the code
this.dispatchItem = function (item, i) {
// A barrage that has been scheduled once will not be needed the next time
if(! item ||this.idMap[item.id]) {
return
}
let index = this.indexs[i]
this.idMap[item.id] = item.id
let div = document.createElement('div'),
parent = document.querySelector(`${el} .barrage-list-${i}`),
width,
pastTime
div.innerHTML = item.content
div.className = 'barrage-item'
parent.appendChild(div)
width = getBoxSize(div).width
div.style = `width:${width}px; display:none`
pastTime = this.computeTime(width) // Calculate when the next barrage should appear
// The barrage will fly for a while
this.run(div)
if (index > this.list[i].length - 1) {
return
}
let len = this.timeoutFuncs.length
// Record the timer and empty it later
this.timeoutFuncs[len] = setTimeout(() = > {
this.indexs[i] = index + 1
// Call the next item recursively
this.dispatchItem(this.list[i][index + 1], i, index + 1)
}, pastTime);
}
Copy the code
// Use CSS animation, the overall is relatively smooth
this.run = function (item) {
item.classList += ' running'
item.style.left = "left:100%"
item.style.display = ' '
item.style.animation = `run The ${this.speed/1000}s linear`
// Put a mark on the finished item
setTimeout(() = > {
item.classList+=' done'
}, this.speed);
}
Copy the code
// According to the width of the barrage and gapWth, calculate the time when the next barrage should appear
this.computeTime = function (width) {
let length = width + this.gapWidth
let time = Math.round(length / this.boxSize.width * this.speed/2)
return time
}
Copy the code
Animation CSS details are as follows
@keyframes run {
0% {
left: 100%;
}
50% {
left: 0
}
100% {
left: -100%; }}.run {
animation-name: run;
}
Copy the code
The rest of the way
stop
Paused using the animation’s attribute
this.stop = function () {
let items = document.querySelectorAll(`${el} .barrage-item`);
[...items].forEach(item= > {
item.className += ' pause'})}Copy the code
.pause {
animation-play-state: paused ! important;
}
Copy the code
Start all over again
Remove the pause class
this.restart = function () {
let items = document.querySelectorAll(`${el} .barrage-item`);
[...items].forEach(item= > {
removeClassName(item, 'pause')})}Copy the code
Opening and closing
Do a show hide logic
this.close = function () {
this.container.style.display = 'none'
}
this.open = function () {
this.container.style.display = ' '
}
Copy the code
Clean up the barrage
this.clearData = function () {
/ / remove the list
this.list = []
/ / remove the dom
document.querySelector(`${el}`).innerHTML = ' '
/ / remove the timeout
this.timeoutFuncs.forEach(fun= > clearTimeout(fun))
}
Copy the code
Finally, use a timer to clear expired shells:
setInterval(() = > {
let items = document.querySelectorAll(`${el} .done`);
[...items].forEach(item= >{
item.parentNode.removeChild(item)
})
}, this.speed*5);
Copy the code
The last
If you were designing such a class, how would you design it?