Netbyzz

#A078
Liquid Button

HTML :

<!-- CODE = #A078 -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>NETBYZZ A078</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css">
    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <a href="#" class="button">
      <canvas class="button__canvas"></canvas>
      <span class="button__text">Hover me I am liquid</span>
    </a>
    <script src="https://cdn.jsdelivr.net/npm/ola"></script>
    <script src="script.js"></script>
  </body>
</html>

CSS :

body {
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 100vh;
    background: #eef;
}

.w-100 {
    width: 100%;
    height: 20px;
    position: relative;
}

.button {
    position: relative;
    padding: 1.6em 2.8em;
    text-decoration: none;
}

.button__canvas {
    --offset: 32px;
    position: absolute;
    top: calc(var(--offset) * -1);
    left: calc(var(--offset) * -1);
    width: calc(100% + var(--offset) * 2);
    height: calc(100% + var(--offset) * 2);
    transition: opacity 2s ease;
}

.button__text {
    position: relative;
    color: #ff0;
    font-weight: bold;
    letter-spacing: 0.02em;
    pointer-events: none;
}

.button:hover .button__canvas {
    opacity: 0.85;
    transition: opacity 0.2s ease;
}

.button:active .button__canvas {
    opacity: 1;
    transition: none;
}

JS :

document.querySelectorAll(".button").forEach((elem) => {
  const canvas = elem.querySelector(".button__canvas");
  const ctx = canvas.getContext("2d");

  const offset = 32;
  const background = "#eef";
  const foreground = "#1253c8";

  let points = [];
  let position;

  const distance = new Ola({
    value: offset,
  });

  const size = () => {
    canvas.width = canvas.getBoundingClientRect().width;
    canvas.height = canvas.getBoundingClientRect().height;

    position = new Ola({
      x: canvas.width / 2,
      y: canvas.height / 2,
    });

    const pixelsWidth = canvas.width - offset * 2;
    const pixelsHeight = canvas.height - offset * 2;

    const leftTop = [offset, offset];
    const rightTop = [canvas.width - offset, offset];
    const rightBottom = [canvas.width - offset, canvas.height - offset];
    const leftBottom = [offset, canvas.height - offset];

    points = [];

    Array(pixelsWidth)
      .fill()
      .forEach((_, index) => {
        points.push([leftTop[0] + index, leftTop[1]]);
      });

    Array(pixelsHeight)
      .fill()
      .forEach((_, index) => {
        points.push([rightTop[0], rightTop[1] + index]);
      });

    Array(pixelsWidth)
      .fill()
      .forEach((_, index) => {
        points.push([rightBottom[0] - index, rightBottom[1]]);
      });

    Array(pixelsHeight)
      .fill()
      .forEach((_, index) => {
        points.push([leftBottom[0], leftBottom[1] - index]);
      });
  };

  size();

  const reset = () => {
    ctx.fillStyle = background;
    ctx.fillRect(0, 0, canvas.width, canvas.height);
  };

  const draw = () => {
    ctx.fillStyle = foreground;
    ctx.beginPath();

    points.forEach((point, index) => {
      const [vx, vy] = attract(point);

      if (index === 0) ctx.moveTo(vx, vy);
      else ctx.lineTo(vx, vy);
    });

    ctx.closePath();
    ctx.fill();
  };

  const attract = (point) => {
    const [x, y] = point;

    const dx = x - position.x;
    const dy = y - position.y;

    const dist = Math.sqrt(dx * dx + dy * dy);
    const dist2 = Math.max(1, dist);

    const d = Math.min(dist2, Math.max(12, dist2 / 4 - dist2));
    const D = dist2 * distance.value;

    return [x + (d / D) * (position.x - x), y + (d / D) * (position.y - y)];
  };

  const loop = () => {
    reset();
    draw();
    requestAnimationFrame(loop);
  };

  window.onresize = size;

  canvas.onmousemove = (e) => {
    position.set(
      {
        x: e.clientX - e.target.getBoundingClientRect().left,
        y: e.clientY - e.target.getBoundingClientRect().top,
      },
      200
    );
  };

  canvas.onmouseenter = () => {
    distance.set(
      {
        value: 1,
      },
      200
    );
  };

  canvas.onmouseleave = () => {
    position.set(
      {
        x: canvas.width / 2,
        y: canvas.height / 2,
      },
      2000
    );

    distance.set(
      {
        value: offset,
      },
      12000
    );
  };

  loop();
});
 

Source Code