Create Water Drop Effect in HTML and CSS with AnimeJS
By Prgmaz 12 Jan 2021

alt text

What we need for this tutorial:

  • HTML and CSS
  • AnimeJS
  • and your choice of editor

I downloaded all the assets and placed them in my assets directory so my directory will look like this:

  • animations/assets/css/css-files-here
  • animations/assets/js/js-files-here

Then I created index.html file and styles.css in my assets folder and then linked all css in header tag and linked all js files below the body.

So my index.html is looking like this:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Home - Programmer101N</title>
    <link rel="stylesheet" href="assets/css/styles.css">
</head>
<body>

</body>
<script src="assets/js/anime.js"></script>
</html>

Now let’s create our canvas in HTML. I’m going to create canvas element and set it’s id to canvas.

<body>
  <canvas id="canvas"> </canvas>
</body>

Now let’s create our styles in our style.css. You can copy my styles or create your own.

html, body{
  height: 100vh;
  width: 100vw;
  display: flex;
  margin: 0;
  padding: 0;
}

#canvas{
  background-color: transparent;
}

Now our CSS part is complete, Let’s create our script. First I’m going to get canvas and it’s context with DOM and Set it’s height and width to our window height and width. I set the background color to seablue.

const canvas = document.getElementById("canvas");
const c = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.style.backgroundColor = "#01C5FF";

Now I’m adding a event listener to resize event that will resize canvas evertime window height or width is changed.

window.addEventListener("resize", () => {
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;
});

First I’m going to create a function createDrop which will create water drop and return it.

function createDrop(x, y) {
  const drop = {
    x: x,
    y: y - 150,
    endPos: {
      x,
      y,
      dropForm: 30,
    },
    dropForm: 3,
    opacity: 0,
    draw: () => {
      c.beginPath();
      c.fillStyle = `rgba(255, 255, 255, ${drop.opacity})`;
      c.moveTo(drop.x - drop.dropForm, drop.y);
      c.lineTo(drop.x, drop.y - drop.endPos.dropForm * 1.4);
      c.lineTo(drop.x + drop.dropForm, drop.y);
      c.arc(drop.x, drop.y, drop.dropForm, 0, Math.PI);
      c.closePath();
      c.fill();
    },
  };
  return drop;
}

Now creating a function clickEffect which will create waves when our drop hit and it’s going to call animate function.

function clickEffect(x, y) {
  let strokeWidth = 5;
  const animatables = [];

  for (let j = 0; j < 3; j++) {
    const circle = {
      x: x,
      y: y,
      radius: 0,
      strokeWidth: strokeWidth,
      draw: () => {
        c.beginPath();
        c.arc(
          circle.x,
          circle.y,
          circle.radius,
          0,
          Math.PI * 2,
          true
        );
        c.lineWidth = circle.strokeWidth;
        c.stroke();
      },
    };
    strokeWidth -= 2;
    animatables.push(circle);
  }

  animate(x, y, animatables);
}

Now createing animate function which will animate everything.

function animate(x, y, animatables) {
  const tl = new anime.timeline({
    duration: 7000,
    easing: "easeOutExpo",
  });

  tl.add({
    targets: createDrop(x, y),
    y: (p) => {
      return p.endPos.y;
    },
    dropForm: (p) => {
      return p.endPos.dropForm;
    },
    opacity: [0, 1, 0],
    duration: 1500,
    update: (e) => {
      e.animatables.forEach((a) => {
        a.target.draw();
      });
    },
    easing: "easeInExpo",
  });

  tl.add({
    targets: animatables,
    radius: window.innerWidth * 1.125,
    update: (e) => {
      e.animatables.forEach((a) => {
        a.target.draw();
      });
    },
    delay: anime.stagger(150),
  });
}

Creating a render variable that will clear everything every update.

const render = anime({
  duration: Infinity,
  update: () => {
    c.clearRect(0, 0, canvas.width, canvas.height);
  },
});

Now adding event listener to touch event or click event and calling clickEffect function at coordinates when click is registered and set state of render to play.

document.addEventListener(
  "ontouchstart" in window || navigator.msMaxTouchPoints
    ? "touchstart"
    : "mousedown",
  (e) => {
    clickEffect(e.clientX, e.clientY);
    render.play();
  }
);

Our Effect is now Complete!

Effect Image

You can download this source code from my Github.

Visit this Project on Github

Naman Baranwal
Hi, I’m Prgmaz. I’m a Web, Game and App Developer.

Tags