← Backgrounds

Animated Pattern Tiles

A repeating pattern of stripes, dots, zigzags, checks, waves or little crosses slides endlessly behind a section, like a moving wallpaper. Your own background colour shows through the gaps, so it works with any brand colour.

Stripes
Dots
Zigzags

How to use it

  1. Paste the snippet just before </head>.
  2. Add data-animated-pattern-tiles to a section with a background colour.
  3. Pick a pattern with data-pattern.

Options

Add any of these attributes to the same element.

data-patternstripes, dots, zigzag, checks, waves or plus. Default stripes.
data-colorPattern colour. Default translucent white rgba(255,255,255,.16).
data-sizeTile size in px. Default 32.
data-speedSpeed in px per second. Default 20.
data-angleDirection of travel in degrees: 0 is right, 90 is down. Default 30.
data-fadeFade the pattern out towards the edges.

The pattern is a tiny SVG tile repeated by the browser, so it stays sharp at any size and costs almost nothing. It pauses when off screen. With reduced motion it holds still.

1 · Snippet
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
  (function () {
    // Animated Pattern Tiles: a repeating pattern of stripes, dots, zigzags,
    // checks, waves or crosses slides endlessly behind a section, like a
    // moving wallpaper. Your own background colour shows through the gaps.
    // Usage: <section data-animated-pattern-tiles style="background:#6a0dad">…</section>
    // Optional: data-pattern (stripes, dots, zigzag, checks, waves or plus,
    // default stripes), data-color (pattern colour, default translucent white
    // rgba(255,255,255,.16)), data-size (tile size in px, default 32),
    // data-speed (px per second, default 20), data-angle (direction of travel
    // in degrees, 0 is to the right, 90 is down, default 30), data-fade (fade
    // the pattern out towards the edges).
    var DEFAULTS = { pattern: 'stripes', color: 'rgba(255,255,255,.16)', size: 32, speed: 20, angle: 30 };

    var css =
      '[data-animated-pattern-tiles]{position:relative;isolation:isolate;overflow:hidden}' +
      '.apt-layer{position:absolute;inset:0;z-index:-1;pointer-events:none;background-repeat:repeat}' +
      '.apt-fade{-webkit-mask-image:radial-gradient(ellipse 70% 70% at 50% 50%,#000 30%,transparent 100%);mask-image:radial-gradient(ellipse 70% 70% at 50% 50%,#000 30%,transparent 100%)}';
    var style = document.createElement('style');
    style.textContent = css;
    document.head.appendChild(style);

    // Each pattern is one square SVG tile of side s that repeats seamlessly.
    var PATTERNS = {
      stripes: function (s, c) {
        var h = s / 2;
        return '<path fill="' + c + '" d="M0 0H' + h + 'L0 ' + h + 'Z M' + s + ' 0V' + h + 'L' + h + ' ' + s + 'H0Z"/>';
      },
      dots: function (s, c) {
        var r = s * 0.13;
        return '<circle fill="' + c + '" cx="' + s / 4 + '" cy="' + s / 4 + '" r="' + r + '"/>' +
          '<circle fill="' + c + '" cx="' + (s * 3) / 4 + '" cy="' + (s * 3) / 4 + '" r="' + r + '"/>';
      },
      zigzag: function (s, c) {
        return '<path fill="none" stroke="' + c + '" stroke-width="' + s * 0.13 + '" stroke-linejoin="miter" d="M' + -s / 2 + ' ' + s * 0.35 +
          'L0 ' + s * 0.65 + 'L' + s / 2 + ' ' + s * 0.35 + 'L' + s + ' ' + s * 0.65 + 'L' + s * 1.5 + ' ' + s * 0.35 + '"/>';
      },
      checks: function (s, c) {
        var h = s / 2;
        return '<path fill="' + c + '" d="M0 0H' + h + 'V' + h + 'H0Z M' + h + ' ' + h + 'H' + s + 'V' + s + 'H' + h + 'Z"/>';
      },
      waves: function (s, c) {
        return '<path fill="none" stroke="' + c + '" stroke-width="' + s * 0.1 + '" d="M0 ' + s / 2 + 'C' + s / 4 + ' ' + s * 0.15 + ' ' + s / 4 + ' ' + s * 0.15 + ' ' + s / 2 + ' ' + s / 2 +
          'S' + (s * 3) / 4 + ' ' + s * 0.85 + ' ' + s + ' ' + s / 2 + '"/>';
      },
      plus: function (s, c) {
        var a = s * 0.07, b = s * 0.2, m = s / 2;
        return '<path fill="' + c + '" d="M' + (m - a) + ' ' + (m - b) + 'H' + (m + a) + 'V' + (m - a) + 'H' + (m + b) + 'V' + (m + a) + 'H' + (m + a) +
          'V' + (m + b) + 'H' + (m - a) + 'V' + (m + a) + 'H' + (m - b) + 'V' + (m - a) + 'H' + (m - a) + 'Z"/>';
      }
    };

    function tile(name, s, color) {
      var draw = PATTERNS[name] || PATTERNS[DEFAULTS.pattern];
      var svg = '<svg xmlns="http://www.w3.org/2000/svg" width="' + s + '" height="' + s + '" viewBox="0 0 ' + s + ' ' + s + '">' + draw(s, color) + '</svg>';
      return 'url("data:image/svg+xml,' + encodeURIComponent(svg) + '")';
    }

    function build(el) {
      var d = el.dataset;
      var size = +d.size || DEFAULTS.size;
      var speed = d.speed !== undefined ? +d.speed : DEFAULTS.speed;
      var angle = ((d.angle !== undefined ? +d.angle : DEFAULTS.angle) * Math.PI) / 180;
      var layer = document.createElement('div');
      layer.className = 'apt-layer' + (d.fade !== undefined && d.fade !== 'false' ? ' apt-fade' : '');
      layer.setAttribute('aria-hidden', 'true');
      layer.style.backgroundImage = tile(d.pattern || DEFAULTS.pattern, size, d.color || DEFAULTS.color);
      layer.style.backgroundSize = size + 'px ' + size + 'px';
      el.insertBefore(layer, el.firstChild);
      if (window.matchMedia('(prefers-reduced-motion: reduce)').matches || !speed) return;

      var vx = Math.cos(angle) * speed, vy = Math.sin(angle) * speed, t = 0, last = 0;
      var timer = anime.createTimer({
        loop: true,
        duration: 1000,
        autoplay: false,
        onUpdate: function () {
          var now = performance.now();
          t += (last ? Math.min(50, now - last) : 16) / 1000;
          last = now;
          // The tile repeats every `size` px, so wrap to keep numbers small.
          var x = ((vx * t) % size + size) % size, y = ((vy * t) % size + size) % size;
          layer.style.backgroundPosition = x.toFixed(2) + 'px ' + y.toFixed(2) + 'px';
        }
      });
      new IntersectionObserver(function (entries) {
        if (entries[0].isIntersecting) { last = 0; timer.play(); } else timer.pause();
      }).observe(el);
    }

    function init() {
      document.querySelectorAll('[data-animated-pattern-tiles]').forEach(function (el) {
        if (el._apt) return;
        el._apt = true;
        build(el);
      });
    }

    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
    else init();
  })();
</script>
2 · Your HTML
<section data-animated-pattern-tiles data-pattern="dots" style="background: #6a0dad; color: #fff">
  <h1>On repeat</h1>
</section>