How to Build a Danmaku (Bullet Comment) System with HTML, CSS, and Canvas

This article explains the concept of danmaku (bullet comments), why they improve user experience, and provides a detailed guide on implementing a danmaku system using HTML + CSS or Canvas, including stage design, track management, collision handling, and reusable code examples.

ELab Team
ELab Team
ELab Team
How to Build a Danmaku (Bullet Comment) System with HTML, CSS, and Canvas

Background

To create a richer multimedia experience, many video platforms have added a social feature that lets users post comments at specific points on the video timeline, known as danmaku (bullet comments). Originating from Japan's Niconico, danmaku are now supported on Chinese sites such as Bilibili, AcFun, Tencent Video, iQiyi, Youku, and Migu.

Form

A single danmaku can appear in three basic modes:

Scrolling: moves from right to left across the screen.

Top: static and centered at the top.

Bottom: static and centered at the bottom.

Why Danmaku?

Before Danmaku

Traditional interaction uses separate comment sections or chat windows, forcing users to split their visual attention between the video and the comment area, which degrades both video focus and comment readability.

After Danmaku

Danmaku keep the viewer’s focus on the video while displaying comments in a single, left‑to‑right scrolling direction that matches reading habits, eliminating visual obstacles.

Additional Benefits

Strong Interaction (On‑Demand) : Viewers can instantly share thoughts that appear as scrolling comments, creating real‑time interaction.

Live Interaction : Streamers can gauge audience feedback directly from on‑screen comments.

Atmosphere Enhancement : Highlighted “high‑energy” comments add excitement to suspenseful or horror content.

Implementation Methods

Most major sites implement danmaku with HTML + CSS; a small portion uses Canvas.

HTML + CSS Approach

Using DOM elements makes styling easy via CSS and leverages the browser’s native event system for interactions such as likes, reports, hover, and click.

Canvas Approach

Canvas offers smoother animation but requires custom event handling and is more complex for developers less familiar with it.

Design Overview

The system consists of three main parts: the stage, tracks, and the barrage pool.

Stage

The stage controls multiple tracks, a waiting queue, and the pool. Each frame it checks for empty tracks, moves barrages from the queue to appropriate tracks, and renders them.

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 barrage to waiting queue
  abstract add(barrage: T): boolean
  // Find suitable track
  abstract _findTrack(): number
  // Extract barrage from queue to track
  abstract _extractBarrage(): void
  // Render function
  abstract render(): void
  // Reset
  abstract reset(): void
}

Canvas Stage

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')!
  }
}

HTML + CSS Stage

export default abstract class BaseCssStage<T extends BarrageObject> extends BaseStage<T> {
  el: HTMLDivElement
  objToElm: WeakMap<T, HTMLElement> = new WeakMap()
  elmToObj: WeakMap<HTMLElement, T> = new WeakMap()
  freezeBarrage: T | null = null
  domPool: 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))
    }
  }

  // ...methods for creating, removing, and handling mouse events...
}

Track

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() { this.barrages = []; this.offset = 0 }
  push(...items: T[]) { this.barrages.push(...items) }
  removeTop() { this.barrages.shift() }
  remove(index: number) { if (index >= 0 && index < this.barrages.length) this.barrages.splice(index, 1) }
  updateOffset() {
    const endBarrage = this.barrages[this.barrages.length - 1]
    if (endBarrage) {
      const { speed } = endBarrage
      this.offset -= speed
    }
  }
}

Collision Handling

Two common strategies:

Uniform speed for all barrages (no collision).

Variable speeds with collision detection, often solved by a pursuit‑problem formula to compute a maximum speed, then adding randomness.

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

Where DefaultSpeed is the baseline speed for the first barrage on a track.

Demo Links

CSS implementation: https://logcas.github.io/a-barrage/example/css3.html

Canvas implementation: https://logcas.github.io/a-barrage/example/canvas.html

References

https://w3c.github.io/danmaku/usecase.zh.html

https://juejin.cn/post/6867689680670818317

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaScriptCSSHTMLdanmakubullet comments
ELab Team
Written by

ELab Team

Sharing fresh technical insights

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.