library / backgrounds / parallax-skyline
live - 60fps 1920 x 1080
parallax-skyline.html - self-contained
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Parallax Skyline</title>
    <style>
      /*
        Parallax Skyline
        ----------------
        This page draws a simple skyline made of multiple layers of
        rectangles.  Each layer moves at a different speed to produce a
        parallax effect.  The horizontal speed varies with the user's
        mouse position: moving the cursor to the right makes the buildings
        drift faster.  The design is intentionally minimalist, focusing on
        crisp shapes and smooth animation rather than realism.
      */
      html, body {
        margin: 0;
        padding: 0;
        height: 100%;
        overflow: hidden;
        background: linear-gradient(#1e3c72, #2a5298);
        font-family: sans-serif;
      }
      canvas {
        display: block;
      }
    </style>
  </head>
  <body>
    <canvas id="skyline"></canvas>
    <script>
      (function() {
        const canvas = document.getElementById('skyline');
        const ctx    = canvas.getContext('2d');
        let mouseX   = 0.5; // normalised mouse position [0,1]

        function resize() {
          canvas.width  = window.innerWidth;
          canvas.height = window.innerHeight;
          buildLayers();
        }
        window.addEventListener('resize', resize);
        resize();

        // Capture mouse movement to influence speed.
        document.addEventListener('mousemove', (e) => {
          mouseX = e.clientX / window.innerWidth;
        });

        // Define a building layer.  Each layer has its own speed and
        // building specifications.  We populate an array of buildings
        // spanning the width of the canvas.
        class Layer {
          constructor(opts) {
            Object.assign(this, opts);
            this.buildings = [];
            this.populate();
          }
          populate() {
            this.buildings = [];
            let x = 0;
            while (x < canvas.width + this.maxWidth) {
              const width  = this.minWidth + Math.random() * (this.maxWidth - this.minWidth);
              const height = this.minHeight + Math.random() * (this.maxHeight - this.minHeight);
              this.buildings.push({ x, width, height });
              x += width + this.gap;
            }
          }
          update(speedScale) {
            // Move buildings leftwards.  The mouse controls the overall speed.
            const delta = this.speed * speedScale;
            for (const b of this.buildings) {
              b.x -= delta;
            }
            // Recycle buildings that have moved off the left edge.
            while (this.buildings.length && this.buildings[0].x + this.buildings[0].width < 0) {
              const b = this.buildings.shift();
              // Place it at the end with new dimensions.
              const width  = this.minWidth + Math.random() * (this.maxWidth - this.minWidth);
              const height = this.minHeight + Math.random() * (this.maxHeight - this.minHeight);
              const lastX  = this.buildings.length ? this.buildings[this.buildings.length - 1].x + this.buildings[this.buildings.length - 1].width + this.gap : 0;
              b.x     = lastX;
              b.width = width;
              b.height= height;
              this.buildings.push(b);
            }
          }
          draw(ctx) {
            ctx.fillStyle = this.colour;
            for (const b of this.buildings) {
              ctx.fillRect(b.x, canvas.height - b.height, b.width, b.height);
            }
          }
        }

        // Create layers with different depths.  Closer layers have
        // larger buildings and move faster.
        let layers = [];
        function buildLayers() {
          layers = [
            new Layer({
              speed: 0.5,
              minWidth: 40,
              maxWidth: 80,
              gap: 15,
              minHeight: canvas.height * 0.2,
              maxHeight: canvas.height * 0.4,
              colour: 'rgba(0,0,0,0.6)'
            }),
            new Layer({
              speed: 1.0,
              minWidth: 30,
              maxWidth: 60,
              gap: 10,
              minHeight: canvas.height * 0.3,
              maxHeight: canvas.height * 0.6,
              colour: 'rgba(0,0,0,0.8)'
            }),
            new Layer({
              speed: 2.0,
              minWidth: 20,
              maxWidth: 40,
              gap: 8,
              minHeight: canvas.height * 0.4,
              maxHeight: canvas.height * 0.8,
              colour: 'rgba(0,0,0,1)'
            })
          ];
        }

        // Animation loop
        function render() {
          // Draw sky gradient: dark at top, lighter near horizon.
          const grad = ctx.createLinearGradient(0, 0, 0, canvas.height);
          grad.addColorStop(0, '#152850');
          grad.addColorStop(1, '#113366');
          ctx.fillStyle = grad;
          ctx.fillRect(0, 0, canvas.width, canvas.height);

          // Draw stars.  We use a simple seeded random distribution each frame.
          ctx.fillStyle = 'rgba(255,255,255,0.5)';
          for (let i = 0; i < 80; i++) {
            const x = Math.random() * canvas.width;
            const y = Math.random() * canvas.height * 0.6;
            ctx.fillRect(x, y, 2, 2);
          }
          // Update and draw each layer.  Speed scale increases with mouseX.
          const scale = 0.5 + mouseX * 1.5;
          for (const layer of layers) {
            layer.update(scale);
            layer.draw(ctx);
          }
          requestAnimationFrame(render);
        }
        render();
      })();
    </script>
  </body>
</html>

About this animation

Scrolling cityscape with parallax depth.

Under the hood

Multi-Layer CSS Parallax

Separates foreground, midground, and background visual assets into absolute positioned layers. As the user moves their mouse (mapped to window percentages), the layers translate at different speeds based on their perceived depth, bringing flat images to life.

Source Code
Source copied
Partner hosting options for the copied effect:
Vercel Netlify Hostinger