background

In order to create a better multimedia experience, many video sites have added social mechanisms that allow users to post comments and view others’ comments at specific points on the media timeline. One of these mechanisms is called Dan Mu, also known as polysmor ト in Japanese (coinage is in use).comment“) or projetiles (danmaku), during playback, a large number of comments and comments may appear and be rendered directly on the video. The barrage was originally introduced by Niconico, a Japanese video site. In China, in addition to being used on bullet screen video sites such as Bilibili and AcFun, video players on other major video sites such as Tencent Video, iQiyi Video, Youku Video and Migu Video also support bullet screen.

In the form of

There are three basic modes of single bullet screen:

  1. Scrolling bullet screen: A bullet screen that scrolls across the screen from right to left and is displayed in priority from top to bottom.
  2. Top bullet screen: top-down static bullet screen in the middle, with top-down priority display.
  3. Bottom bullet screen: bottom-up static bullet screen in the middle, to the bottom-up priority display.

Why are bullet screens needed

From a user experience perspective — before the barrage

Before the bullet screen, we usually interacted with each other through comments or chat rooms:(Above, video on the left, interaction area on the right)

The problem with traditional interaction is that when our eyes are focused on the video, there’s no way to do “one eye and two uses.” Simply put, you can’t get your eyes to look in different directions. The downside is that when the user is focused on the video, the interaction of the interaction area is very poor; And when the user is looking at the comments in the interaction area, and can not pay attention to the main content of the whole thing, ignore one thing and lose another.

(You can’t do both at once.) Meanwhile, for most of the world’s population, the habit of reading from left to right was built into our childhood. Comments like this interactive area, usually from the bottom to the top of the automatic scrolling, the two directions of the combination of the whole text to form a tilt of the direction of motion, making the user’s reading obstacles.(Tilting upward text moves, making it difficult to read the word.)

From the user experience point of view — after the emergence of the bullet screen

After the emergence of the bullet screen, our perspective will focus on the main body of the video. When the bullet screen appears, if it is a rolling bullet screen, it will generally start from right to left, which is very suitable for our reading habit from left to right. In addition, there is only one direction of text movement, which will not cause obstacles to our reading.

Other benefits

Strong interactivity: let you feel not lonely when on demand

When watching the video provided by the video website, viewers will have some ideas or jokes according to the inspiration of the content in the process of watching the video content, and they want to publish it and share it with more people. At this time, it is necessary to meet this demand. Through the bullet screen, the comments of the viewers at the same time can be displayed in the video area by scrolling in a fixed direction, or still displayed at the top or bottom of the video area, which can increase the interaction characteristics between the viewers and the video as well as the interaction between the viewers. Barrage messages sent at the same time have basically the same theme.

Strong interactivity: timely interaction during live broadcast

In the live video scene, the bullet screen can also be a direct way for the anchor to interact with the audience. Compared with traditional real-time comments, anchors can more intuitively understand the audience’s needs and feedback according to the display of bullet screen, and more conveniently adjust the following actions and processing, and can also conduct interactive operations according to the user’s input.

Good atmosphere: “High energy ahead”

When watching some of the more scary and suspenseful content, “High Energy ahead” may prevent you from falling into the shadow of childhood.

The realization of bullet screen

Nowadays, when pressing F12 from B station, iQiyi, Tencent Video and other major media websites, it is easy to find that it is realized through HTML+CSS. In addition, there are a small number of Canvas implementations, such as the previous B site (but I can’t find the toggle button before the deadline).

Let’s say it’s HTML+CSS

Through DOM elements to achieve the barrage, front-end students can easily modify the style of the barrage through CSS. At the same time, thanks to the browser’s native DOM event mechanism, a series of bullet screen interactive functions such as personalization, liking and reporting can be quickly realized with this, so as to meet various interactive needs of the product. It is easy to see that at present, such as Tencent Video, iQiyi and so on, all implement the bullet screen through DOM elements, which is the mainstream way of implementation at present.

Let’s say we do it through Canvas

Canvas is created for animation, but implementing a barrage system based on Canvas is more complicated than implementing it based on DOM. Aside from the fact that most front-end students are far less familiar with Canvas than DOM, what’s more, Canvas does not have a set of native event system, which means that if you want to achieve some interactive functions, you must implement a set of Canvas event mechanism by yourself…

Design of bullet screen

The first is the overall design, mainly three parts: stage, track, barrage pool.

stage

The stage is the main control of the entire barrage, maintaining multiple tracks, a waiting line, and a barrage pool. What the stage should do is to control the rhythm of the whole bullet screen. When each frame is rendered, it will judge whether there is space in the track and take the appropriate bullet screen from the waiting queue and send it to the appropriate track.The ability of a stage can be achieved by implementing a stage base class and corresponding abstract functions, allowing a specific type of stage to implement the corresponding stage logic. To achieve different rendering capabilities (Canvas, HTML+CSS) and different types (scrolling, top fixed, bottom fixed) of the bullet control. Whether you implement a barrage through Canvas or DOM, the methods are similar: add a new barrage to the waiting queue, find the right track, extract a barrage from the waiting queue and put it into the track, render it as a whole, and empty it. Therefore, BaseStage can arrange abstract methods and let concrete subclasses do concrete implementation.

export default abstract class BaseStage<T extends BarrageObject> extends EventEmitter { 
  protected trackWidth: number 
  protected trackHeight: number 
  protected duration: number 
  protected maxTrack: number 
  protected tracks: Track<T>[] = [] 
  waitingQueue: T[] = [] 
 
  // Add a barrage to the queue
  abstract add(barrage: T): boolean 
  // Find the right track
  abstract _findTrack(): number 
  // Draw the barrage from the waiting queue and put it into the track
  abstract _extractBarrage(): void 
  // Render function
  abstract render(): void 
  / / to empty
  abstract reset(): void 
} 
Copy the code

Canvas version

For example, the Canvas stage base class needs to pass in the Canvas element to get the Context. Finally, the abstract method of BaseStage is used to realize the concrete logic.

export default abstract class BaseCanvasStage<T extends BarrageObject> extends BaseStage< 
  T 
> { 
  protected canvas: HTMLCanvasElement 
  protected ctx: CanvasRenderingContext2D 
 
  constructor(canvas: HTMLCanvasElement, config: Config) { 
    super(config) 
    this.canvas = canvas 
    this.ctx = canvas.getContext('2d')! }}Copy the code

HTML + CSS version

For the HTML+CSS implementation, You need to maintain a bullet pool domPool, the mapping between the bullet instances and the DOM (objToElm, elmToObj), and the necessary event handling methods (_mouseMoveEventHandler, _mouseClickEventHandler).

export default abstract class BaseCssStage<T extends BarrageObject> extends BaseStage<T> { 
  el: HTMLDivElement 
  objToElm: WeakMap<T, HTMLElement> = new WeakMap(a)elmToObj: WeakMap<HTMLElement, T> = new WeakMap(a)freezeBarrage: T | null = null 
  domPool: Array<HTMLElement> = [] 
 
  constructor(el: HTMLDivElement, config: Config) { 
    super(config) 
 
    this.el = el 
 
    const wrapper = config.wrapper 
    if (wrapper && config.interactive) { 
      wrapper.addEventListener('mousemove'.this._mouseMoveEventHandler.bind(this)) 
      wrapper.addEventListener('click'.this._mouseClickEventHandler.bind(this))}}createBarrage(text: string, color: string, fontSize: string, left: string) { 
    if (this.domPool.length) { 
      const el = this.domPool.pop() 
      return _createBarrage(text, color, fontSize, left, el) 
    } else { 
      return _createBarrage(text, color, fontSize, left) 
    } 
  } 
 
  removeElement(target: HTMLElement) { 
    if (this.domPool.length < this.poolSize) { 
      this.domPool.push(target) 
      return 
    } 
    this.el.removeChild(target) 
  } 
 
  _mouseMoveEventHandler(e: Event) { 
    const target = e.target 
    if(! target) {return 
    } 
 
    const newFreezeBarrage = this.elmToObj.get(target as HTMLElement) 
    const oldFreezeBarrage = this.freezeBarrage 
 
    if (newFreezeBarrage === oldFreezeBarrage) { 
      return 
    } 
 
    this.freezeBarrage = null 
 
    if (newFreezeBarrage) { 
      this.freezeBarrage = newFreezeBarrage 
      newFreezeBarrage.freeze = true 
      setHoverStyle(target as HTMLElement) 
      this.$emit('hover', newFreezeBarrage, target as HTMLElement) 
    } 
 
    if (oldFreezeBarrage) { 
      oldFreezeBarrage.freeze = false 
      const oldFreezeElm = this.objToElm.get(oldFreezeBarrage) 
      oldFreezeElm && setBlurStyle(oldFreezeElm) 
      this.$emit('blur', oldFreezeBarrage, oldFreezeElm) 
    } 
  } 
 
  _mouseClickEventHandler(e: Event) { 
    const target = e.target 
    const barrageObject = this.elmToObj.get(target as HTMLElement) 
    if (barrageObject) { 
      this.$emit('click', barrageObject, target) 
    } 
  } 
 
  reset() { 
    this.forEach(track= > { 
      track.forEach(barrage= > { 
        const el = this.objToElm.get(barrage) 
        if(! el) {return 
        } 
        this.removeElement(el) 
      }) 
      track.reset() 
    }) 
  } 
} 
Copy the code

Barrage pool

Each bullet screen will correspond to a DOM element. In order to reduce the frequency of creation, the last round of bullets that have rolled off the stage will be stored in the pool on the left side of the screen, and will be reused when there are new bullets.

orbital

It can be seen from the bullet screen we often see that in fact, there are multiple parallel tracks in the middle of the stage, and the relationship between the stage and the tracks is 1 to many. While the bullet screen is running, the bullet screens in the track are rendered in turn. Therefore, there will be an array of bullet screens in the track, representing the bullet screens currently displayed on the track; And a variable called offset, which represents the width of the orbital that is currently occupied.

class BarrageTrack<T extends BarrageObject> { 
  barrages: T[] = [] 
  offset: number = 0 
 
  forEach(handler: TrackForEachHandler<T>) { 
    for (let i = 0; i < this.barrages.length; ++i) { 
      handler(this.barrages[i], i, this.barrages) 
    } 
  } 
 
  / / reset
  reset() { 
    this.barrages = [] 
    this.offset = 0 
  } 
 
  // Add a new bullet screen
  push(. items: T[]) { 
    this.barrages.push(... items) }// Remove the first one (i.e. the one just left)
  removeTop() { 
    this.barrages.shift() 
  } 
 
  remove(index: number) { 
    if (index < 0 || index >= this.barrages.length) { 
      return 
    } 
    this.barrages.splice(index, 1)}// Update Offset by focusing only on the last bullet in the orbit
  updateOffset() { 
    const endBarrage = this.barrages[this.barrages.length - 1] 
    if (endBarrage) { 
      const { speed } = endBarrage 
      this.offset -= speed 
    } 
  } 
} 
Copy the code

The collision

The collision control of the bullet screen and the presentation of the bullet screen, in fact, depends entirely on product demand and personal preferences. Taking most of the bullet screens as an example, in addition to the diversified realization of B station, more of them are realized by means of parallel tracks. If the collision problem of bullet screen needs to be considered, there are generally two methods:

  1. The speed of each bullet screen is the same, so there are no collisions, but the effect is very rigid.
  2. The speed of each bullet screen is different, but collisions need to be addressed.

In order to achieve different speeds, the simplest and most effective way is to find the maximum speed of the bullet screen through the “tracing problem”.Through the “tracing problem”, the maximum velocity VB of bullet screen B can be easily found. However, VB should not be the final velocity of the barrage. Considering that the distance S may be relatively large, the velocity of VB will be very large. At the same time, a bit of randomness should be added to the speed of the barrage. Therefore, a better way to present the speed of bullet screen is:

S = Math.max(VB, Random * DefaultSpeed)Copy the code

DefaultSpeed specifies the DefaultSpeed of the first bullet on the track. It should be set to an appropriate value as required, and the maximum VB value must not exceed this, otherwise the bullet will only “flash” across the track.

Demo

Logcas. Making. IO/a – barrage/e… Logcas. Making. IO/a – barrage/e…

The resources

The w3c. Making. IO/danmaku/use…

Juejin. Cn/post / 686768…