Sample code of barrage component realized by native JavaScript

  • 2021-08-28 19:13:49
  • OfStack

Preface

Nowadays, almost all video websites have barrage function, so today we will package a barrage class with native JavaScript. This class wants the following properties and instance methods:

Attribute

Selector of el container node. The container node should be absolutely positioned, and the width and height should be set height Height of each barrage mode barrage mode, half is 1.5 container height, top is 1/3, full is full Time of speed barrage across the screen Distance between the last barrage of gapWidth and the first barrage

Method

pushData Add Barrage Metadata addData continues to join the barrage start starts scheduling barrage stop stop barrage restart restarts barrage clearData Empty Barrage close off open re-display barrage

PS: If there are 1 self-encapsulated tool function, it will not be posted. It is good to know the meaning probably

Initialization

After introducing the JavaScript file, we want to use it as follows, taking the default configuration first.


let barrage = new Barrage({
  el: '#container'
})

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 = []
}

Accept the parameters first and then 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')))
  }
}

The width and height of the box are calculated by getComputedStyleapi, which is used to calculate the width and height of the container here 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
}

Calculate how many lines the barrage should have according to the height, and there will be places to use the number of lines 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 first look at the method of 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 strips 
    this.pushArr(data)
  }
}


this.initDom = function () {
  if (!document.querySelector(`${el} .barrage-list`)) {
    // Registration 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 = `${this.boxSize.height*getDivisor(this.mode)/this.rows}px`
      this.container.appendChild(div)
    }
  }
}

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 least in the barrage list 1 Column, the barrage list is 1 A 2 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
    }
  })
}
// According to the number of lines, 1 The barrage of dimension list Segmentation rows OK 2 Dimensional array 
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
}

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()
}

Launch barrage

Let's take a look at the logic of firing 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 real scheduling starts here, using 1 Instance variables store the subscript of the current schedule. 
  if (row[this.indexs[i]]) {
    this.dispatchItem(row[this.indexs[i]], i, this.indexs[i])
  }
}


this.dispatchItem = function (item, i) {
  // Scheduled 1 Under a barrage of times 1 You don't need it when you schedule it 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 the following 1 When the barrage should appear 
  // Barrage flight 1 Meeting ~
  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
    // Under recursive call 1 Article 
    this.dispatchItem(this.list[i][index + 1], i, index + 1)
  }, pastTime);
}


// Use css Animation, the whole is relatively smooth 
this.run = function (item) {
  item.classList += ' running'
  item.style.left = "left:100%"
  item.style.display = ''
  item.style.animation = `run ${this.speed/1000}s linear`
  // Completed fight 1 Tags 
  setTimeout(() => {
    item.classList+=' done'
  }, this.speed);
}

// According to the width of the barrage and gapWth , work out 1 When the 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
}

The animation css is as follows


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 = []
}
0

Remaining methods

Stop

Stop with the paused property of the animation


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 = []
}
1

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 = []
}
2

Start over

Remove the pause class


this.restart = function () {
  let items = document.querySelectorAll(`${el} .barrage-item`);
  [...items].forEach(item => {
    removeClassName(item, 'pause')
  })
}

Open and close

Do 1 show hidden logic can be


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 = []
}
4

Clear the barrage


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 = []
}
5

Finally, use a timer to regularly clean up the expired barrage:


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 = []
}
6

Finally

I feel that the implementation of this is still flawed. If you designed such a class, how would you design it?


Related articles: