← Backgrounds

Starfield

Stars stream towards you from the middle of a section, as if flying through space at warp speed. Drawn on a canvas behind your content, and paused when off screen.

To infinity

How to use it

  1. Paste the snippet just before </head>.
  2. Add data-starfield to a section with a dark background.

Options

Add any of these attributes to the same element.

data-starsNumber of stars. Default 300.
data-speedSpeed multiplier. Default 1.
data-colorStar colour. Default white.
1 · Snippet
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
  (function () {
    // Starfield: stars stream towards you from the middle of a section, as if
    // flying through space. Drawn on a canvas behind your content.
    // Usage: <section data-starfield style="background:#05050f">…</section>
    // Optional: data-stars (count, default 300), data-speed (default 1),
    // data-color (default white).
    var DEFAULTS = { stars: 300, speed: 1, color: '#ffffff' };

    var css =
      '[data-starfield]{position:relative;isolation:isolate;overflow:hidden}' +
      '.sf-canvas{position:absolute;inset:0;width:100%;height:100%;z-index:-1;pointer-events:none}';
    var style = document.createElement('style');
    style.textContent = css;
    document.head.appendChild(style);

    function build(el) {
      var d = el.dataset;
      var canvas = document.createElement('canvas');
      canvas.className = 'sf-canvas';
      canvas.setAttribute('aria-hidden', 'true');
      el.insertBefore(canvas, el.firstChild);
      var ctx = canvas.getContext('2d');
      var count = +d.stars || DEFAULTS.stars;
      var speed = +d.speed || DEFAULTS.speed;
      var color = d.color || DEFAULTS.color;
      var w = 0, h = 0, dpr = 1;
      var stars = [];

      function star(z) {
        return { x: (Math.random() - 0.5) * 2, y: (Math.random() - 0.5) * 2, z: z === undefined ? Math.random() : z, pz: 0 };
      }
      for (var i = 0; i < count; i++) stars.push(star());

      function resize() {
        dpr = Math.min(2, window.devicePixelRatio || 1);
        w = el.clientWidth;
        h = el.clientHeight;
        canvas.width = w * dpr;
        canvas.height = h * dpr;
      }
      resize();
      if (window.ResizeObserver) new ResizeObserver(resize).observe(el);

      function draw(dt) {
        ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
        ctx.clearRect(0, 0, w, h);
        ctx.strokeStyle = color;
        ctx.lineCap = 'round';
        var cx = w / 2, cy = h / 2, scale = Math.max(w, h) * 0.5;
        stars.forEach(function (s, i) {
          s.pz = s.z;
          s.z -= dt * 0.00025 * speed;
          if (s.z <= 0.02) { stars[i] = star(1); return; }
          // Project to the screen; draw a streak from last position to this one.
          var x = cx + (s.x / s.z) * scale * 0.5, y = cy + (s.y / s.z) * scale * 0.5;
          var px = cx + (s.x / s.pz) * scale * 0.5, py = cy + (s.y / s.pz) * scale * 0.5;
          var depth = 1 - s.z;
          ctx.globalAlpha = Math.min(1, depth * 1.4);
          ctx.lineWidth = 0.4 + depth * 2;
          ctx.beginPath();
          ctx.moveTo(px, py);
          ctx.lineTo(x, y);
          ctx.stroke();
        });
      }

      if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) { draw(0); return; }
      var last = 0;
      var timer = anime.createTimer({
        loop: true,
        duration: 1000,
        onUpdate: function () {
          var now = performance.now();
          var dt = last ? Math.min(50, now - last) : 16;
          last = now;
          draw(dt);
        }
      });
      // Pause while off screen to save battery.
      new IntersectionObserver(function (entries) {
        if (entries[0].isIntersecting) { last = 0; timer.play(); } else timer.pause();
      }).observe(el);
    }

    function init() {
      document.querySelectorAll('[data-starfield]').forEach(function (el) {
        if (el._sf) return;
        el._sf = true;
        build(el);
      });
    }

    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
    else init();
  })();
</script>
2 · Your HTML
<section data-starfield style="background: #05050f; color: #fff">
  <h1>To infinity</h1>
</section>