I was testing JetBrainsโ€™ latest Full Line Code Completion plugin in WebStorm by writing a quick JavaScript demo, and by the end, I was pretty happy with what I had built. The JavaScript uses a queue that pushes events to change classes on buttons in 250ms intervals to create a smooth color transition from one style to another.

Is it practical? I donโ€™t know, but it sure is fun. Iโ€™ve included all the demoโ€™s contents in a single file so you can test it locally. By the end of this post, youโ€™ll have seen an event queue and how to use it in your JavaScript.

Write a JavaScript Event Queue

The premise of my event queue is straightforward. Push events into a queue and have them execute on a set universal interval. I also wanted the ability to clear the queue of any remaining events.

class EventQueue {
    constructor(interval) {
        this.queue = [];
        this.interval = interval;
        this.startProcessing();
    }

    enqueue(eventFunction) {
        this.queue.push(eventFunction);
    }

    clear() {
        this.queue = [];
    }

    startProcessing() {
        setInterval(() => {
            if (this.queue.length > 0) {
                const eventFunction = this.queue.shift();
                eventFunction();
            }
        }, this.interval);
    }
}

We use the setInterval function to check the queue at our designated interval to process any remaining events. Letโ€™s start using it.

Transitioning Button Colors on Mouse Move

Before we write some more JavaScript, letโ€™s look at the HTML elements weโ€™ll change. Note that Iโ€™m using the Bulma CSS library and its button styling classes.

<div class="columns">
    <div class="column">
        <button class="m-1 button is-info">๐Ÿ Move</button>
        <button class="m-1 button is-success">๐Ÿ Move</button>
        <button class="m-1 button is-warning">๐Ÿ Move</button>
        <button class="m-1 button is-danger">๐Ÿ Move</button>
        <button class="m-1 button is-primary">๐Ÿ Move</button>
        <button class="m-1 button is-link">๐Ÿ Move</button>
        <button class="m-1 button is-info">๐Ÿ Move</button>
        <button class="m-1 button is-success">๐Ÿ Move</button>
        <button class="m-1 button is-warning">๐Ÿ Move</button>
        <button class="m-1 button is-danger">๐Ÿ Move</button>
        <button class="m-1 button is-primary">๐Ÿ Move</button>
        <button class="m-1 button is-link">๐Ÿ Move</button>
    </div>
</div>

Also, to smooth the transition between styles, I wrote an additional CSS style to ease the background-color to limit the strobing effect that might happen if colors change too quickly.

<style>
    .button {
        transition: background-color 0.2s ease-in-out;
    }
</style>

Letโ€™s write some JavaScript. We want to change the CSS class on each .button as the user moves the mouse. If the user stops moving the mouse or leaves the bounds of the page, we want to clear all remaining events from our queue.

window.addEventListener('DOMContentLoaded', () => {

    const colors = [
        "is-info", "is-success",
        "is-warning", "is-danger",
        "is-primary", "is-link"
    ];

    const queue = new EventQueue(250 /* ms */);
    const changeButtonColors = () => {
        const buttons = document.querySelectorAll(".button");
        buttons.forEach(button => {
            button.classList.forEach((c) => {
                if (c.startsWith("is-")) {
                    const currentIndex = colors.indexOf(c);
                    const newIndex = currentIndex < colors.length - 1
                        ? currentIndex + 1 : 0;
                    button.classList.remove(c);
                    button.classList.add(colors[newIndex]);
                }
            });
        });
    };

    document.addEventListener('mousemove', (e) => {
        if (e.movementX === 0 && e.movementY === 0) {
            queue.clear();
        } else {
            queue.enqueue(changeButtonColors);
        }
    });

    document.addEventListener('mouseleave', () => {
        queue.clear();
    })
});

Thatโ€™s pretty straightforward. As a personal side note, JavaScript and the web stack have come a long way. This was genuinely a joy to write.

Letโ€™s see what itโ€™s like in action.

Pretty cool!

Hereโ€™s the full HTML/JavaScript/CSS combination in one file.

<!DOCTYPE html>
<meta name="viewport" content="width=device-width, initial-scale=1">
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!-- bulma.io -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.9.4/css/bulma.min.css">
    <style>
        .button {
            transition: background-color 0.2s ease-in-out;
        }
    </style>
</head>
<body>
<section class="section">
    <div class="container">
        <h1 class="title">
            Hello World
        </h1>
        <p class="subtitle">
            My first website with <strong>Bulma</strong>!
        </p>
        <div class="columns">
            <div class="column">
                <button class="m-1 button is-info">๐Ÿ Move</button>
                <button class="m-1 button is-success">๐Ÿ Move</button>
                <button class="m-1 button is-warning">๐Ÿ Move</button>
                <button class="m-1 button is-danger">๐Ÿ Move</button>
                <button class="m-1 button is-primary">๐Ÿ Move</button>
                <button class="m-1 button is-link">๐Ÿ Move</button>
                <button class="m-1 button is-info">๐Ÿ Move</button>
                <button class="m-1 button is-success">๐Ÿ Move</button>
                <button class="m-1 button is-warning">๐Ÿ Move</button>
                <button class="m-1 button is-danger">๐Ÿ Move</button>
                <button class="m-1 button is-primary">๐Ÿ Move</button>
                <button class="m-1 button is-link">๐Ÿ Move</button>
            </div>
        </div>
    </div>
</section>
<script type="application/javascript">
window.addEventListener('DOMContentLoaded', () => {

    const colors = [
        "is-info", "is-success",
        "is-warning", "is-danger",
        "is-primary", "is-link"
    ];

    const queue = new EventQueue(250 /* ms */);
    const changeButtonColors = () => {
        const buttons = document.querySelectorAll(".button");
        buttons.forEach(button => {
            button.classList.forEach((c) => {
                if (c.startsWith("is-")) {
                    const currentIndex = colors.indexOf(c);
                    const newIndex = currentIndex < colors.length - 1
                        ? currentIndex + 1 : 0;
                    button.classList.remove(c);
                    button.classList.add(colors[newIndex]);
                }
            });
        });
    };

    document.addEventListener('mousemove', (e) => {
        if (e.movementX === 0 && e.movementY === 0) {
            queue.clear();
        } else {
            queue.enqueue(changeButtonColors);
        }
    });

    document.addEventListener('mouseleave', () => {
        queue.clear();
    })
});

    class EventQueue {
        constructor(interval) {
            this.queue = [];
            this.interval = interval;
            this.startProcessing();
        }

        enqueue(eventFunction) {
            this.queue.push(eventFunction);
        }

        clear() {
            this.queue = [];
        }

        startProcessing() {
            setInterval(() => {
                if (this.queue.length > 0) {
                    const eventFunction = this.queue.shift();
                    eventFunction();
                }
            }, this.interval);
        }
    }

</script>
</body>
</html>

Conclusion

You can create interactive experiences with some JavaScript and some CSS. The EventQueue I wrote is surprisingly simple yet powerful for my use case, but you can adapt it to your needs. The definition of transition is also an excellent tool for keeping visual transitions from being jarring or obnoxious.

Thanks for reading, and I hope you have a great day. Oh, if you thought this post was cool or helpful, please remember to share it with friends and colleagues. Cheers :)