Drawing Creative Brushstrokes with JavaScript

Luis Manuel
👁️ 6,177 views
💬 comments

Some time ago I see this fun demo in Codepen, by Akimitsu Hamamuro. Then I think that it would be great to be able to draw things programmatically in the web. Unfortunately, I could not find any library to do it, so I've developed one, using that pen as base.

In this tutorial, we'll see how to use this new library to draw creative brushstrokes directly in the browser with a nice API. We'll be able to draw solid colors, images, or even HTML!

Specifically, we will be learning how to build a creative poster like this:

Table of Contents

    Getting Started

    Getting started with the Brushstroke library is pretty simple. You only need to include the script and start drawing things:

    <!-- Optional dependencies goes here -->
    <script src="dist/brushstroke.min.js"></script>
    <script>
        // Options for customization
        var options = {
            duration: 1,
            queue: true
        };
    
        // Initialization
        var bs = new Brushstroke(options);
    
        // Draw, erase, etc...
        bs.draw();
        bs.erase();
        bs.draw();
    </script>

    This is just a very basic example. You can find a detailed documentation in the github repo.

    Drawing the Background Image

    As you may see, our poster consists of a background image and a text. Let's see how to animate the drawing of the background image.

    First we need to initialize a new Brushstroke instance for the background image, with all the options we want. Please learn more about each option in the github repo.

    // Declaring variables
    
    var width = window.innerWidth || document.body.clientWidth;
    var height = window.innerHeight || document.body.clientHeight;
    var optionsBackground, bsBackground;
    
    // Random curves for background
    
    optionsBackground = {
        animation: 'points',
        points: 10,
        inkAmount: 5,
        size: 300,
        frames: 10,
        frameAnimation: true,
        splashing: false,
        image: 'images/background.jpg',
        centered: true,
        queue: true,
        width: width,
        height: height
    };
    bsBackground = new Brushstroke(optionsBackground);

    Now we can start drawing things, and more! Let's see how to achieve the effect we want for our poster:

    // Function to start the animation
    
    function runAnimation() {
        // Draw a straight line
        bsBackground.draw({
            points: [0, height / 2 - 40, width, height / 3]
        });
    
        // Draw another straight line
        bsBackground.draw({
            points: [width, height / 2, 0, height / 1.5 - 40]
        });
    
        // Draw a curve generated using 20 random points
        bsBackground.draw({
            inkAmount: 3,
            frames: 100,
            size: 200,
            splashing: true,
            points: 20
        });
    }
    
    // Start
    
    runAnimation();

    Drawing the Text

    Our library is not able to draw text as is, but it can draw along any SVG path provided. So we can draw our text as SVG paths, and then we can pass the paths to the library. For this demo, we've used the "Hershey Text" extension available in the Inkscape vector editor (from version 0.91 or newer). You can find more info about it here.

    After render, scale up, and simplify the paths (Ctrl + L) to create smother shapes of our text, our SVG looks like this:

    Now we have our text as single-stroke SVG paths, and we can use them to draw with the Brushstroke library!

    First, we need to include the SVG in the HTML:

    <!-- Each path is a letter in "Scotch.io" string -->
    <svg width="1000" height="300" style="display: none">
        <path d="m157.76 105.88c-7.4053-8.9862-16.122-15.85-27.865-17.818-16.604-3.2803-35.244-2.2435-50.148 6.2719-8.4329 6.4228-15.194 15.018-10.757 25.919 6.9636 23.286 33.81 26.063 53.267 33.499 15.84 4.0064 32.554 13.238 35.503 30.658 1.8468 11.869-0.78168 21.884-11.233 28.659-14.576 8.9259-33.167 9.5932-49.689 6.8414-12.29-2.3318-20.767-8.5079-28.636-18.075"/>
        <path d="m272.9 150.66c-9.8598-11.768-23.423-22.368-39.709-19.191-13.362 0.5304-25.61 11.328-31.775 22.54-6.7138 13.934-7.1853 29.748-0.25697 43.707 5.5237 11.364 17.648 21.717 30.492 23.311 15.813 2.8302 30.126-5.4593 39.688-17.63l1.2482-1.2482 0.31262-0.31262"/>
        <path d="m343.27 131.47c-15.039 5.7827-27.96 17.873-30.612 34.262-4.0296 12.888 0.41254 27.839 7.52 38.602 9.0267 11.179 22.414 19.394 37.213 16.695 13.362-0.53038 25.61-11.328 31.775-22.54 6.7139-13.934 7.1853-29.748 0.25697-43.707-5.5237-11.364-17.648-21.717-30.492-23.311h-15.661z"/>
        <path d="m445.62 86.692c0.37493 36.778-0.74805 73.798 0.55819 110.42 2.5876 11.039 6.138 21.243 18.469 23.832 4.2802 0.18855 8.6454 0.0288 12.958 0.0819m-51.176-89.559h44.779"/>
        <path d="m586.36 150.66c-9.8598-11.768-23.423-22.368-39.709-19.191-13.362 0.5304-25.61 11.328-31.775 22.54-6.7139 13.934-7.1853 29.748-0.25697 43.707 5.5237 11.364 17.648 21.717 30.492 23.311 15.813 2.8302 30.126-5.4593 39.688-17.63l1.2482-1.2482 0.31262-0.31262"/>
        <path d="m631.14 86.692v134.34m0-63.971c12.158-12.114 24.92-29.2 44.341-25.588 10.207-0.53645 22.347 4.9455 22.978 16.444 6.3533 18.657 1.559 39.122 3.0479 58.568v14.546"/>
        <path d="m752.68 189.04c-15.025 6.5899 6.2843 18.544 5.1489 5.1489l-5.1489-5.1489z"/>
        <path d="m797.46 86.692c6.5899 15.025 18.544-6.2843 5.1489-5.1489l-5.1489 5.1489zm6.3971 44.779v89.559"/>
        <path d="m880.62 131.47c-15.039 5.7827-27.96 17.873-30.612 34.262-4.0295 12.888 0.41253 27.839 7.52 38.602 9.0268 11.179 22.414 19.394 37.213 16.695 13.362-0.53038 25.61-11.328 31.775-22.54 6.7139-13.934 7.1853-29.748 0.25697-43.707-5.5237-11.364-17.648-21.717-30.492-23.311h-15.661z"/>
    </svg>

    Then we can use those SVG paths in our library to draw the text:

    // Declaring variables
    
    var optionsPath, bsPath;
    
    // Options for text (SVG paths)
    
    optionsPath = {
        animation: 'path',
        inkAmount: 2,
        frames: 20,
        frameAnimation: true,
        color: 'white',
        width: 1000,
        height: 300
    };
    
    // Initializing
    
    bsPath = new Brushstroke(optionsPath);
    
    // Draw each letter of the text, with a delay among them
    
    var paths = document.querySelectorAll('path');
    var delay = 0;
    
    for (var i = 0; i < paths.length; i++) {
        bsPath.draw({path: paths[i], delay: delay});
        delay += 0.5;
    }

    Adding "Run Again" Functionality

    Finally, we'd like to provide an option to repeat the animation in our poster, so we can see it again and again :)

    The idea is to draw the entire screen with a solid black color (like erasing), and then run our poster animation again. Let's see the implementation:

    // Declaring variables
    
    var button = document.querySelector('button');
    var animating = true;
    var optionsErase, bsErase;
    
    // Erase and run again
    
    optionsErase = {
        queue: true,
        size: 300,
        padding: 0,
        overlap: 100,
        inkAmount: 20,
        frames: 100,
        frameAnimation: true,
        color: '#000',
        width: width,
        height: height,
        end: function () {
            // Clear all canvas and run animation
            bsBackground.clear();
            bsPath.clear();
            bsErase.clear();
            runAnimation();
        }
    };
    bsErase = new Brushstroke(optionsErase);
    
    // Run again button
    
    button.addEventListener('click', function () {
        if (!animating) {
            toggleButton();
            bsErase.draw();
        }
    });
    
    function toggleButton() {
        button.classList.toggle('hidden');
        animating = !animating;
    }

    Conclusion

    And that's all!

    With a bit of styling to get things in the right place, we should have a beautiful and creative poster using brushstroke animations :)

    As always, you can check the final demo here, and also get the full code and documentation on github. There are more exciting features waiting for you there!

    We really hope you liked this tutorial and find it useful!

    Luis Manuel

    13 posts

    Engineer in Computer Science and Freelance Front-End Developer. Available for Hire.