Drawing and Animating Jelly Shapes with Canvas

Luis Manuel

In recent times, we have seen that the web is no longer a page in a browser full of squares. Every day there are more designs which incorporate irregular shapes.

In this tutorial we want to teach you how to create and animate shapes with a jelly effect. At the end, you will be able to create the jelly shape you want, and animate it according to your needs without too much effort.

Specifically, in this first part we will see how to achieve the following result:

Introduction

The maths behind an effect like this can be very difficult to achieve. That's why we have tried to group the code needed to create and animate jelly shapes in a library that is easy to use for developers.

At the same time, we have been inspired by this pen by Thom Chiovoloni, inspired as well in the game The Floor is Jelly. And we have started specifically from this implementation of the jelly physics.

So, we've packaged this implementation into a library we've just called jelly.js, to which we've added everything we need to get jelly shapes easily. Then let's see how to use it!

Creating the shapes with SVG paths

We have chosen SVG paths to create the shapes, because we believe it is the easiest and most customizable way we have to do it. In this way, we can create the shapes that we want in a vector editor (like Inkscape or Illustrator), and insert them directly into our HTML document, or even import them from JavaScript.

For example, we can draw a simple shape like this:

Then we can include the relevant SVG code directly in the HTML:

<!-- SVG with a pentagon path -->
<!-- Note the `display: none` style, because we don't want to show the SVG, but just get the path from JavaScript -->
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="400" height="400" style="display: none">
    <path id="pentagon-path" d="m200 97.218 108.07 78.524-41.28 127.04h-133.58l-41.281-127.04z"/>
</svg>

Setup the markup

We also need a canvas to draw the shape, and some other elements with the aim of moving a letter (S, from Scotch) coordinately with the centroid (average point) of the shape.

<div class="jelly-container">
    <!-- Canvas to draw the jelly pentagon -->
    <canvas class="jelly-canvas"></canvas>

    <!-- Text in the centroid of the jelly pentagon -->
    <div class="centroid-container">
        <div class="centroid-text">S</div>
    </div>
</div>

A bit of style

For this example we need very few styles. Let's see the code:

/* General styles */

html, body {
  margin: 0;
}

body {
  background-color: #D98327;
  overflow: hidden;
}

/* Jelly styles */

.jelly-container {
  position: relative;
  display: inline-block;
  left: 50%;
  margin-left: -200px;
}

.jelly-container, .jelly-canvas {
  width: 400px;
  height: 400px;
}

/* It's important to position the `.centroid-container` in the top-left corner
   This way the `.centroid-text` will be positioned in the center (with JavaScript) */
.centroid-container {
  position: absolute;
  left: 0;
  top: 0;
  transform: translate(-50%, -50%);
  pointer-events: none;
}

.centroid-text {
  font-size: 100px;
  color: white;
}

Making it jelly

Finally we have arrived at the most fun part! Let's draw our pentagon on the canvas and see how we can animate it in a jelly way. But don't worry, it will be very easy using our library.

We just need a few lines of code to draw our pentagon, and to get it reacting in a jelly way if we move the mouse to the edges of the shape:

/* Setup options */

var options = {
    paths: '#pentagon-path',     // Shape we want to draw
    pointsNumber: 10,            // Number of points
    maxDistance: 70,             // Max distance among points
    color: '#5C1523',
    centroid: '.centroid-text'   // Element to move accordingly with the centroid of the shape
    // debug: true               // Uncomment this to see the points
};

/* Initializing jelly */

var jelly = new Jelly('.jelly-canvas', options);

Note that the constructor of our library (Jelly) receives a canvas element and a set of options. We can also provide an array of options, a set of options for each shape we want to draw. For a detailed description of the available options, you can check the Github repository.

We could leave it there, but our library has much more to offer. So let's look at some other things we can do.

Implementing a jelly dragging

To illustrate a little more the options we have (and for fun), let's see how we can shake our pentagon as we drag it across the screen.

First, we need to know when the mouse is inside the shape, to allow dragging it only when that happens. Check the following code, doing just that:

/* Check hover item (shape) and update cursor */

var container = document.querySelector('.jelly-container');
var hoverIndex = -1;

function checkHover() {
    // The `getHoverIndex` function will return the index of the shape being hovered, or -1
    hoverIndex = jelly.getHoverIndex();
    container.style.cursor = hoverIndex === -1 ? 'default' : 'pointer';
    window.requestAnimationFrame(checkHover);
}
window.requestAnimationFrame(checkHover);

And let's see how we can implement a basic dragging logic. Please look at the comments to understand what is happening there, and pay special attention to the shake function:

/* Drag and drop */

var startX, startY, dx, dy, endX = 0, endY = 0, x = 0, y = 0, lastX = 0, lastY = 0;
var down = false;
// This will be the max distance for shaking
var shakeLimit = 5;

container.addEventListener('mousedown', function (e) {
    if (hoverIndex >= 0) {
        startX = e.clientX;
        startY = e.clientY;
        down = true;
    }
});

document.addEventListener('mousemove', function (e) {
    if (down) {
        x = e.clientX - startX;
        y = e.clientY - startY;
        container.style.transform = 'translate(' + (endX + x) + 'px, ' + (endY + y) + 'px)';

        dx = x - lastX;
        dy = y - lastY;
        if (dx > shakeLimit || dx < - shakeLimit) dx = dx < 0 ? - shakeLimit : shakeLimit;
        if (dy > shakeLimit || dy < - shakeLimit) dy = dy < 0 ? - shakeLimit : shakeLimit;

        // The `shake` function will "move" the half of the points (alternately) the distance defined
        jelly.shake({x: - dx, y: - dy});

        lastX = x;
        lastY = y;
    }
});

function mouseUp() {
    if (down) {
        down = false;
        endX += x;
        endY += y;
    }
}

document.addEventListener('mouseup', mouseUp);

document.addEventListener('mouseout', function (e) {
    if (e.target.nodeName == 'HTML') {
        mouseUp();
    }
});

Performance and browser support

The browser support is really good, because all modern browsers have support for canvas. But jelly.js uses Promises, so we need a polyfill for any browser that does not support native promises.

On the other hand, the performance is very variable according to the browsers and the operating systems. This is due to the CPU intensive work in each animation frame. So, don't abuse the use of these effects, because they can kill website performance.

Conclusion

And we are done! So far we have created a truly jelly pentagon without too much effort. Our shape also change the cursor on hover event, and respond to dragging in a jelly way as well :)

You can see the final demo here, and you can get the full code on Github too.

These are not the only things we can do. In fact, in the second part of this tutorial we will build an awesome slider, where everything will be jelly. In broad outline, we will learn how to:

  • Draw more jelly shapes, and text!
  • Use images inside the shapes, not only solid colors.
  • Animate the shapes to show or hide them smoothly.
  • Morph from a jelly shape to another.
  • Make the entire slider responsive.

We really hope you enjoyed it and found it useful! See you in the second part ;)

Luis Manuel

7 posts

Engineer in computer science and front-end developer. Passionate about web design, HTML, CSS and JavaScript. Available for hire.