DEV Community

netsi1964 πŸ™πŸ»
netsi1964 πŸ™πŸ»

Posted on

Breaking down the task of producing a checkbox which is using SVG + GSAP

The SVG from Figma

Working on a project where a custom checkbox was designed, the design contained in Figma two states of the checkbox: checked and unchecked.

The two states were exported as SVG into two files. They looked quite similar, only some parts of it were located in different position, changed colour.

That is fine - we have the states of the custom checkbox!

The HTML and CSS to show a custom checkbox

When you want to produce a custom checkbox you should include a native HTML input[type="checkbox"](this is the CSS selector for such an element). It will hold the state of the checkbox, and when used inside a HTML form if given a [name] attribute it will be send together with the rest of the form data.

You also get the benefit of keeping all native features from the solid HTML framework. No need to reinvent the wheel! :-) Like if you you change the checked state of the input element you can use css selectors to make the DOM react to it, without you having to do a lot of coding!

For instance here are some powerful CSS allows for state texts follow the state of the input element.

[type="checkbox"]:not(:checked) ~ .false,
[type="checkbox"]:checked ~ .true {
    display: none;
}
[type="checkbox"]:not(:checked) ~ .true,
[type="checkbox"]:checked ~ .false {
    display: block;
}
Enter fullscreen mode Exit fullscreen mode

To break them down into words:

match elements of type checkbox, which has a given :checked state (say: :not(:checked)) then match a true or false class using general sibling combinator "~"

It can be a bit hard to understand at first, but is very powerful an all using vanilla CSS. These powerful CSS rules adds the option to have a sibling element containing say a text which can show on the checked (true) and unchecked (false) state! Here I use it to show true|false besides the beautiful custom checkbox.

The complete CSS based solution now looks like this:

Adding animation with GSAP

I was asked if I could perhaps animate the checkbox. After doing some thinking I came to a conclusion that it would be only a matter of animating some attributes on parts of the SVG, since the two SVG states did not add or remove elements, but only had some small changes to elements which are in the designs.

GSAP is pretty good for animating attributes on a timeline using its .to(..) method. It simply takes a css selector or a DOM element and some parameters which specifies the attribute to animate to which new value. Other than that it can be useful here to set a duration for the animation, and as all changes should be run simultaneously, a label for all the changes to align their starting point to.

The differences to animate I simply found by comparing the two SVG sources. In my code I then create a simple model to contain the changes I wanted to apply on a state change. Ended up with this list of animation request:

let animations = [
    { selector: ".circle", checkedParams: { x: -20} },
    { selector: ".bg", checkedParams: { fill: "#DCDDE6" } },
    { selector: ".disk", checkedParams: { fill: "white" } },
    { selector: ".checkmark", checkedParams: { stroke: "white" } }
];
Enter fullscreen mode Exit fullscreen mode

An array which I can loop through when doing the animated state transition using GASP can be understand like this:

selector: Which CSS selector will match the element I want to animate changes to? For this to be realistic I have manually added a class (say circle) as a "handle" to match in a CSS selector.

checkedParams: An object containing key-values for attributes to animate. Like animate x to -20.

Being the lazy programmer I am...

With many years of development on my back I have the joy of being also a bit lazy :-) My brain tries to optimize the manual work to do by automating what can be automated. In this case it showed up when I had to figure out how have values from both states in my animations object. I have specified checkedParams, but I also need to know the uncheckedParams which should contain hold values for the unchecked (false) state.

At the time of coding I came up with an initialization function which will expand the animations object with the value for the attribute which will be animated. It looks like this:

function updateAnimation(animations) {
    return animations.map((a) => {
        let uncheckedParams = a.uncheckedParams;
        let { selector, checkedParams } = a;
        let keys = Object.keys(checkedParams);
        let ele = document.querySelector(selector);
        uncheckedParams = {};
        keys.forEach((key) => {
            const val = ele.getAttributeNode(key)?.nodeValue;
            uncheckedParams[key] = val !== undefined ? val : params[key];
        });
        return { ...a, uncheckedParams };
    });
}
Enter fullscreen mode Exit fullscreen mode

See if you can figure my code out! :-)

In hindsight I would probably simply enter them manually :-) But breaking the idea into updateAnimation function was a fun task!

Finishing up the task

Only some small pieces of coding was added from here. Like adding a test where clicking somewhere else on the page would work (it did). Also the code to ensure that both a checked and unchecked initial state would work (also did, after some tries!).

Going further with the project

I will be porting this into an Angular component, which is what we use in the project. However it would be nice to do more work on it producing say:

  • A svelte implementation
  • A NPM package
  • A custom Element (web component)
  • An online tool allowing to toggle between your own SVG designs and getting the code generated for you

Such small projects are good "coding fitness", and sharing them online - on CodePen, Github, NPM and other places is what makes our line of business so cool! Open Source rules! And do remember:

If you take from the web, you should give to the web!

Happy coding!

/Sten Hougaard
netsi1964 at CodePen - netsi1964 at Twitter

The final version - enjoy!

Top comments (0)