Skip to main content

Making fun looking animations on login forms using SVG, CSS and JavaScript

· 4 min read
Lajos Szoke
The one man army, who single-handedly built the heart and soul of ConfigCat.

In this post, I will show you how we implemented the animations where the cat follows the cursor with his eyes when entering username and hides them when entering password.


How it works?

We are going to animate SVG using CSS and some JavaScript. You can see it in action here.

Show and hide eyes#

The paws movement animation is some kind of a stop motion animation with opacity changes to make the movements smoother. There are separate groups in the SVG for 5 different frames like so:

<g class="hands">    <g class="hands1" data-name="&lt;hands1&gt;">         <path class="hand1_right" data-name="&lt;hand1_right&gt;" d="..." fill="#c1272d"></path>         <path class="hand1_left" data-name="&lt;hand1_left&gt;" d="..." fill="#c1272d"></path>    </g>    ...    <g class="hands5" data-name="&lt;hands5&gt;">         <path class="hand5_right" data-name="&lt;hand5_right&gt;" d="..." fill="#c1272d"></path>         <path class="hand5_left" data-name="&lt;hand5_left&gt;" d="..." fill="#c1272d"></path>    </g></g>

I have added the .pawsCSS class to all groups and each group/frame has an indexed .pawsN class too.

The animation starts by putting .showEyes or .hideEyes CSS classes on our paws group in the SVG. These classes will start the animation by showing/hiding the frames with different paw states with delay:

Our CSS looks something like this:

.paws.showEyes {    opacity: 1;}
.paws.showEyes .paws5 {    animation: changeFrameShow 0.025s;}
.paws.showEyes .paws4 {    animation: changeFrameShow 0.025s;    animation-delay: 0.02s;}
.paws.showEyes .paws3 {    animation: changeFrameShow 0.025s;    animation-delay: .045s;}
.paws.showEyes .paws2 {    animation: changeFrameShow 0.025s;    animation-delay: .07s;}
.paws.showEyes .paws1 {    animation: lastFrameShow 0.025s;    animation-fill-mode: forwards;    animation-delay: .095s;}

We also have the changeFrameShow and lastFrameShow keyframes with some opacity changes for smoother animation:

@@keyframes changeFrameShow {    0% {        opacity: 1    }    99% {        opacity: 1    }    100% {        opacity: 0    }}
@@keyframes lastFrameShow {    0% {        opacity: 1    }    99% {        opacity: 1    }    100% {        opacity: 0    }}

Following the caret position in the text input with the animation of the eyes.#

How to move the eyes?#

On every input value change, we should position the eyes to look at the caret position of the input. Let’s create a function called moveEyes. Attaching it to the input’s change listeners will set a transform on the .eyes group in the SVG.

moveEyes = function (e) {    var input =;    var coordinates = that.getCursorXY(input);    var newLeft = Math.min(    coordinates.x - input.scrollLeft,        input.offsetLeft + input.offsetWidth    );    newLeft = newLeft * 10 / input.offsetWidth - 5;    $('.eyes').setAttribute('style', 'transform: translate(' + newLeft + 'px, 5px);');}

How to determine the caret position of an input?#

This was the hard part of the animation because you can’t directly access the caret position. So I added some dummy elements to the DOM by creating a <div> with the input’s value’s first part (to the selectionEnd) and a <span> with the remaining value from the input (after selectionEnd). The <span>’s offset will give you the caret position which you just have to correct by the input’s original offset:

getCursorXY = function (input) {    const inputX = input.offsetLeft;    const inputY = input.offsetTop;    // create a dummy element that will be a clone of our input    const div = document.createElement('div');    // we need a character that will replace whitespace when filling our dummy element    const swap = '.';    const inputValue = input.value.replace(/ /g, swap);    // set the div content to that of the input up until selection    const textContent = inputValue.substr(0, input.selectionEnd);    // set the text content of the dummy element div    div.textContent = textContent; = 'auto';    // create a marker element to obtain caret position    const span = document.createElement('span');    // give the span the textContent of remaining content so that the recreated dummy element is as close as possible    span.textContent = inputValue.substr(input.selectionEnd) || '.';    // append the span marker to the div    div.appendChild(span);    // append the dummy element to the body    document.body.appendChild(div);    // get the marker position, this is the caret position top and left relative to the input    const spanX = span.offsetLeft;    const spanY = span.offsetTop;    // lastly, remove that dummy element    document.body.removeChild(div);    // return an object with the x and y of the caret. account for input positioning so that you don't need to wrap the input    return {        x: inputX + spanX,        y: inputY + spanY    };};