Frontend Development 22 min read

Recreating Bilibili Home Page Banner with Native JavaScript and Major Frontend Frameworks

This article explains how to analyze Bilibili's homepage banner, extract its image layers and transformation data, and then implement the same effect using pure JavaScript as well as Angular, React, and Vue, providing complete source code and a step‑by‑step guide.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Recreating Bilibili Home Page Banner with Native JavaScript and Major Frontend Frameworks

The author starts by describing how they first implemented Bilibili's banner using Angular and later decided to provide a version that works with plain JavaScript, React, and Vue so that anyone can copy the code directly into their projects.

By inspecting the DOM of Bilibili's banner, each image layer is found inside a div with class layer , and its position, size, rotation, scale and opacity are controlled via CSS transform and opacity properties.

The core idea is to treat one image as a baseline, record its movement values (translateX, translateY, rotate, scale, opacity) and then calculate the corresponding values for all other images based on that baseline.

First, the static resources are collected (image files, dimensions, initial transform values) and stored in a JavaScript array. The following snippet shows the data structure used:

{
  type: 'image',
  file: '[email protected]',
  width: 1728,
  height: 162,
  x: 0,
  y: 0,
  r: 0,
  s: 1,
  o: 1,
  newX: -1.17573,
  newY: 0,
  newRotate: 0,
  bench: -1.17573
}

Next, a simple static HTML page is built to display the layers using the collected data. The HTML skeleton looks like this:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .bili-banner { position: relative; width: 100%; height: 100%; background-color: #f9f9f9; display: flex; justify-content: center; }
    .animated-banner { position: absolute; width: 100%; height: 100%; top: 0; left: 0; overflow: hidden; }
    .layer { position: absolute; width: 100%; height: 100%; left: 0; top: 0; display: flex; align-items: center; justify-content: center; }
  </style>
</head>
<body>
  <div class="banner-container" id="winter-5"></div>
<script>/* JavaScript code will be inserted here */</script>
</body>
</html>

The JavaScript creates the DOM elements for each layer, sets the image source, size and initial transform, and appends them to the container:

const baseSrc = './assets/winter-5/';
const imgData = [...]; // the array shown above
const container = document.getElementById('winter-5');
const biliBanner = document.createElement('div');
biliBanner.className = 'bili-banner';
const animatedBanner = document.createElement('div');
animatedBanner.className = 'animated-banner';
imgData.forEach(item => {
  const layer = document.createElement('div');
  layer.className = 'layer';
  const img = document.createElement('img');
  img.src = baseSrc + item.file;
  img.style.width = item.width + 'px';
  img.style.height = item.height + 'px';
  img.style.transform = `translate(${item.x}px, ${item.y}px) rotate(${item.r}deg) scale(${item.s})`;
  img.style.opacity = item.o;
  layer.appendChild(img);
  animatedBanner.appendChild(layer);
});
biliBanner.appendChild(animatedBanner);
container.appendChild(biliBanner);

To achieve the interactive effect, a class BilibiliBannerBase is defined. It registers mouse events ( mouseenter , mousemove , mouseleave ), tracks the mouse movement, computes the delta, and updates each image's transform based on the previously recorded baseline values. The class also uses ResizeObserver to adapt to container size changes and runs a requestAnimationFrame loop to render at a stable frame rate.

class BilibiliBannerBase {
  constructor({container, imgData, marginLeft = 0, moveRate = 300, maxMove, maxLeftPosition, maxRightPosition}) {
    this.container = container;
    this.imgData = imgData;
    this.marginLeft = marginLeft;
    this.moveRate = moveRate;
    // ...initialisation of observers, event listeners, and rendering loop
  }
  // mouse event handlers
  bilibiliStart(x, y) { this.startPoint = {x, y}; this.transition = 0; }
  bilibiliMove(x, y) { /* calculate moveX, update each layer */ }
  bilibiliEnd() { this.startPoint = {x:0, y:0}; }
  // rendering logic
  render() { this.imgDomList.forEach((img,i)=>{ /* apply new transform and opacity */ }); }
  loop(timestamp) { /* requestAnimationFrame loop */ }
  // other helper methods (getLeftWidth, getRightWidth, reset, destroy)
}

let banner = new BilibiliBannerBase({
  container: document.getElementById('winter-5'),
  imgData: imgData,
  maxMove: {left: 2000, right: 2000}
});

The final result closely matches the original Bilibili banner, with all images moving smoothly according to mouse movement. The full source code for the native JavaScript version, as well as the Angular, React, and Vue implementations, are available on Gitee (https://gitee.com/CrimsonHu/bilibili-banner).

frontendanimationJavaScriptReactVueCSShtmlAngularBilibili Banner
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login 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.