<!-- tsParticles container -->
<div id="tsparticles"></div>
<canvas id="field"></canvas>
<!-- https://github.com/matteobruni/tsparticles -->
<div class="github">
  <a class="btn btn-link" href="https://github.com/matteobruni/tsparticles" title="Find more info on GitHub">
    <img class="img-fluid" id="gh-mark" src="https://cdn.matteobruni.it/images/particles/GitHub-Mark-120px-plus.png" alt="">
    <span id="gh-project">Made with tsParticles</span>
  </a>
  <div>
    <a class="github-button" href="https://github.com/matteobruni/tsparticles" data-icon="octicon-star" data-show-count="true" aria-label="Star matteobruni/tsparticles on GitHub">Star</a>
    <a class="github-button" href="https://github.com/matteobruni/tsparticles/fork" data-icon="octicon-repo-forked" data-show-count="true" aria-label="Fork matteobruni/tsparticles on GitHub">Fork</a>
    <a class="github-button" href="https://github.com/matteobruni/tsparticles/releases/tag/tsparticles%401.17.9" data-icon="octicon-download" aria-label="Download matteobruni/tsparticles on GitHub">Download</a>
  </div>
</div>
<script async="" defer="" src="https://buttons.github.io/buttons.js"></script>
/* ---- reset ---- */
body {
  margin: 0;
  background: #000;
}
canvas {
  display: block;
  vertical-align: bottom;
}
/* ---- tsparticles container ---- */
#tsparticles {
  position: fixed;
  width: 100%;
  height: 100%;
  left: 0;
  top: 0;
  z-index: 1;
}

#field {
  position: fixed;
  width: 100%;
  height: 100%;
  left: 0;
  top: 0;
  z-index: 0;
}

.github {
  bottom: 10px;
  right: 10px;
  position: fixed;
  border-radius: 10px;
  background: #fff;
  padding: 0 12px 6px 12px;
  border: 1px solid #000;
  z-index: 1000;
}

.github a:hover,
.github a:link,
.github a:visited,
.github a:active {
  color: #000;
  text-decoration: none;
}

.github img {
  height: 30px;
}

.github #gh-project {
  font-size: 20px;
  padding-left: 5px;
  font-weight: bold;
  vertical-align: bottom;
}
let noiseZ;
let size;
let columns;
let rows;
let w;
let h;
let field;
let canvasField = document.getElementById("field");
let ctxField = canvasField.getContext("2d");

function setup(container) {
  size = 20;
  noiseZ = 0;
  reset(container);
  window.addEventListener("resize", reset);
}

function initField() {
  field = new Array(columns);
  for (let x = 0; x < columns; x++) {
    field[x] = new Array(columns);
    for (let y = 0; y < rows; y++) {
      field[x][y] = [0, 0];
    }
  }
}

function calculateField() {
  for (let x = 0; x < columns; x++) {
    for (let y = 0; y < rows; y++) {
      let angle = noise.simplex3(x / 50, y / 50, noiseZ) * Math.PI * 2;
      let length = noise.simplex3(x / 100 + 40000, y / 100 + 40000, noiseZ);
      field[x][y][0] = angle;
      field[x][y][1] = length;
    }
  }
}

function reset(container) {
  const pxRatio = window.devicePixelRatio;
  w = canvasField.offsetWidth * pxRatio;
  h = canvasField.offsetHeight * pxRatio;
  canvasField.width = w;
  canvasField.height = h;
  noise.seed(Math.random());
  columns = Math.floor(w / size) + 1;
  rows = Math.floor(h / size) + 1;
  initField();
}

function drawField(ctx) {
  ctx.clearRect(0, 0, canvasField.width, canvasField.height);
  for (let x = 0; x < columns; x++) {
    for (let y = 0; y < rows; y++) {
      let angle = field[x][y][0];
      let length = field[x][y][1];
      ctx.save();
      ctx.translate(x * size, y * size);
      ctx.rotate(angle);
      ctx.strokeStyle = `hsla(${(angle * 180) / Math.PI}, 100%, 50%, 1)`;
      ctx.lineWidth = 1;
      ctx.beginPath();
      ctx.moveTo(0, 0);
      ctx.lineTo(0, size * length);
      ctx.stroke();
      ctx.restore();
    }
  }
}

tsParticles
  .load("tsparticles", {
    fpsLimit: 120,
    particles: {
      number: {
        value: 200,
        density: {
          enable: true,
          value_area: 800
        }
      },
      color: {
        value: ["#5bc0eb", "#fde74c", "#9bc53d", "#e55934", "#fa7921"]
      },
      shape: {
        type: "circle"
      },
      opacity: {
        value: 0.7,
        random: {
          enable: true,
          minimumValue: 0.2
        },
        animation: {
          enable: true,
          speed: 1,
          minimumValue: 0.2
        }
      },
      size: {
        value: 7,
        random: {
          enable: true,
          minimumValue: 3
        },
        animation: {
          enable: true,
          speed: 5,
          minimumValue: 3
        }
      },
      line_linked: {
        enable: false,
        distance: 150,
        color: "#ffffff",
        opacity: 0.4,
        width: 1
      },
      move: {
        enable: true,
        speed: 5,
        direction: "none",
        random: false,
        straight: false,
        outMode: "out",
        bounce: false,
        warp: true,
        noise: {
          enable: true,
          delay: {
            value: 0.01
          }
        },
        trail: {
          enable: false,
          color: {
            value: "#000"
          },
          length: 30
        }
      }
    },
    interactivity: {
      detect_on: "canvas",
      events: {
        resize: true
      }
    },
    detectRetina: true,
    pauseOnBlur: true
  })
  .then((container) => {
    container.setNoise({
      init: function () {
        setup(container);
      },
      update: function () {
        calculateField();
        noiseZ += 0.005;
        drawField(ctxField);
      },
      generate: function (p) {
        const pos = p.getPosition();

        const px = Math.max(Math.floor(pos.x / size), 0);
        const py = Math.max(Math.floor(pos.y / size), 0);

        if (!field || !field[px] || !field[px][py]) {
          return { angle: 0, length: 0 };
        }

        return {
          angle: field[px][py][0],
          length: field[px][py][1]
        };
      }
    });

    container.refresh();
  });

External CSS

  1. https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.0/css/bootstrap.min.css

External JavaScript

  1. https://cdn.jsdelivr.net/npm/tsparticles@1.18.3/dist/tsparticles.min.js
  2. https://codepen.io/DonKarlssonSan/pen/jBWaad.js