Wednesday 13 February 2019

Web Tutorial: The Valentine Heart Animation

It's the Season of Love, folks! Valentine's Day is approaching!

This Valentine's Day, I have a web tutorial that's both simple and exciting. We will be playing with jQuery and jQuery UI.

What, jQuery?

Yes, jQuery. You didn't think I was going to do this the old-fashioned way, did you? Too much like work. Trust me, it'll be fun.

Here we go...

For this, we'll begin with a basic HTML page with a simple black background. Remember to add script links to jQuery and jQuery UI.
<!DOCTYPE html>
<html>
    <head>
        <title>Hearts</title>

        <style>
            body
            {
                background-color: #000000;
            }
        </style>

        <script src="https://code.jquery.com/jquery-1.12.4.js"></script>
        <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>

        <script>

        </script>
    </head>

    <body>

    </body>
</html>


Nothing much to see here... yet.


Let's put a div in the HTML, and give it an id of placeholder.
    <body>
        <div id="placeholder"></div>
    </body>


Now, we style placeholder. margin properties are set for it to be squarely in the middle of the screen. Note that we're not going to define height and width properties yet. Let's also give it a white outline so we can see what we're dealing with.
        <style>
            body
            {
                background-color: #000000;
            }

            #placeholder
            {
                margin: 10% auto 0 auto;
                outline: 1px solid #FFFFFF;
            }
        </style>


And all the divs within it. Yes, there will be divs. All of them will float left. They'll all have orange outlines for visibility.
        <style>
            body
            {
                background-color: #000000;
            }

            #placeholder
            {
                margin: 10% auto 0 auto;
                outline: 1px solid #FFFFFF;
            }

            #placeholder div
            {
                text-align: center;
                float: left;
                outline: 1px solid #FF4400;
            }
        </style>


See that thick white line? That's actually a div with a white outline and a height of 0 pixels (because we didn't specify height) and 100% width (because we didn't specify width).


For the purposes of this web tutorial, we'll do most of our animation from an object, named objAnimation. Now after declaring that object, use the ready() method, but don't place anything in there right now. It's a placeholder for more code to be executed later on. For that to happen, we must first get the objAnimation object in order.
        <script>
            var objAnimation =
            {

            }

            $(document).ready
            (
                function()
                {
                  
                }
            );
        </script>


One of the properties of objAnimation will be a method named generateRandomNo(). You guessed it, it generates a number between min and max.
            var objAnimation =
            {
                generateRandomNo: function(min, max)
                {
                    return Math.floor((Math.random() * (max - min + 1)) + min);
                },
            }


Next, we specify other properties such as color (set to red), a font size and what will be the width and height of each div inside the placeholder div. The property content is set to "x". We'll change it later on; this is just for simplicity.
            var objAnimation =
            {
                generateRandomNo: function(min, max)
                {
                    return Math.floor((Math.random() * (max - min + 1)) + min);
                },
                content: "x",
                color: "rgba(255, 0, 0, 1)",
                fontSize: "30px",
                unitSize: 30,
            }


OK, we're now going to define shape, the next property. It's a 10 x 10 matrix... OK fine, it's an array of arrays. You'll notice that the 1s form a heart! That's not a coincidence.
            var objAnimation =
            {
                generateRandomNo: function(min, max)
                {
                    return Math.floor((Math.random() * (max - min + 1)) + min);
                },
                content: "x",
                color: "rgba(255, 0, 0, 1)",
                fontSize: "30px",
                unitSize: 30,
                shape:
                [
                    [0, 0, 1, 1, 0, 0, 1, 1, 0, 0],
                    [0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
                    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
                    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
                    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
                    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
                    [0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
                    [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
                    [0, 0, 0, 1, 1, 1, 1, 0, 0, 0],
                    [0, 0, 0, 0, 1, 1, 0, 0, 0, 0]
                ],
            }


Now for the really big stuff. Declare the next property as render(), a method.
            var objAnimation =
            {
                generateRandomNo: function(min, max)
                {
                    return Math.floor((Math.random() * (max - min + 1)) + min);
                },
                content: "x",
                color: "rgba(255, 0, 0, 1)",
                fontSize: "30px",
                unitSize: 30,
                shape:
                [
                    [0, 0, 1, 1, 0, 0, 1, 1, 0, 0],
                    [0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
                    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
                    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
                    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
                    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
                    [0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
                    [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
                    [0, 0, 0, 1, 1, 1, 1, 0, 0, 0],
                    [0, 0, 0, 0, 1, 1, 0, 0, 0, 0]
                ],
                render: function()
                {

                },
            }


Remember this block? Well, you can fill it in now. When the page loads, objAnimation's render() method will run.
            $(document).ready
            (
                function()
                {
                    objAnimation.render();  
                }
            );


Now, inside render(), we declare a few variables. The first, ph, is set to the placeholder div. The rest are set to properties within the same object, thus the use of this.
                render: function()
                {
                    var ph = $("#placeholder");
                    var shape = this.shape;
                    var content = this.content;
                    var color = this.color;
                    var fontSize = this.fontSize;
                    var unitSize = this.unitSize;
                },


Now, we set some properties of ph. What we're trying to create here is a square made out of many smaller squares, to form the shape in shape. (Remember the heart?) First, we'll define the height and width of ph here. Since shape has 10 elements, the placeholder div will contain 10 squares by 10 squares. Each square will be unitSize pixels by unitSize pixels. Therefore placeholder's height and width should be (unitSize x shape.length) pixels.
                    var ph = $("#placeholder");
                    var shape = this.shape;
                    var content = this.content;
                    var color = this.color;
                    var fontSize = this.fontSize;
                    var unitSize = this.unitSize;

                    ph.attr
                    (
                        "style", "width:" + (unitSize * shape.length) + "px;"
                        + "height:" + (unitSize * shape.length) + "px;"
                    );


Aren't we clever... we just turned the thick white line into a box! Specifically, a box (unitSize x shape.length = 30 x 10 = 300) pixels in height and width!


Next, let's iterate through each element of shape...
                    ph.attr
                    (
                        "style", "width:" + (unitSize * shape.length) + "px;"
                        + "height:" + (unitSize * shape.length) + "px;"
                    );

                    $(shape).each
                    (
                        function (index)
                        {

                        }
                    );


Declare variable shapeRow and make it the current element pointed to by index. Then iterate through each element of shapeRow. shape is a two-dimensional array, remember?
                    $(shape).each
                    (
                        function (index)
                        {
                            var shapeRow = shape[index];

                            $(shapeRow).each
                            (
                                function (indexRow)
                                {

                                }
                            );
                        }
                    );


And for each element in shapeRow, we declare a variable piece and set it to a newly created div. Add classes according to the row and column. This will be useful later.
                    $(shape).each
                    (
                        function (index)
                        {
                            var shapeRow = shape[index];

                            $(shapeRow).each
                            (
                                function (indexRow)
                                {
                                    var piece = $("<div></div>");
                                    piece.addClass("row" + index);
                                    piece.addClass("col" + indexRow);
                                }
                            );
                        }
                    );


Then set the attributes of piece, which are the attributes of each and every div within ph! And then append piece to ph using the append() method.
                                function (indexRow)
                                {
                                    var piece = $("<div></div>");
                                    piece.addClass("row" + index);
                                    piece.addClass("col" + indexRow);
                                    piece.attr
                                    (
                                        "style", "color:" + color + ";"
                                        + "font-size:" + fontSize + ";"
                                        + "width:" + unitSize + "px;"
                                        + "height:" + unitSize + "px;"
                                    );

                                    ph.append(piece);
                                }


A white box filled with orange boxes!


OK, next let's fill each div with content.
                                function (indexRow)
                                {
                                    var piece = $("<div></div>");
                                    piece.addClass("row" + index);
                                    piece.addClass("col" + indexRow);
                                    piece.attr
                                    (
                                        "style", "color:" + color + ";"
                                        + "font-size:" + fontSize + ";"
                                        + "width:" + unitSize + "px;"
                                        + "height:" + unitSize + "px;"
                                    );

                                    if (shapeRow[indexRow] == 1)
                                    {
                                        piece.html(content);
                                    }

                                    ph.append(piece);
                                }


You can now see the heart being formed.


Let's get rid of the outlines...
            #placeholder
            {
                margin: 10% auto 0 auto;
                outline: 0px solid #FFFFFF;
            }

            #placeholder div
            {
                text-align: center;
                float: left;
                outline: 0px solid #FF4400;
            }


And change the content property to use the ♥ symbol.
            var objAnimation =
            {
                generateRandomNo: function(min, max)
                {
                    return Math.floor((Math.random() * (max - min + 1)) + min);
                },
                content: "&hearts;",
                color: "rgba(255, 0, 0, 1)",
                fontSize: "30px",
                unitSize: 30,
                shape:


Ain't this just adorable?!

Animating the heart (or, er, hearts)

You didn't think we were done, did you? What we just did was to lay the groundwork for some truly neat stuff.

After we've rendered the heart of hearts, declare variable anim and set it to the current object. Then use the setInterval() function with an interval of, say, 2.5 seconds. Then, in the callback, run the animateObject() method with 0 passed in as the argument.
                    $(shape).each
                    (
                        function (index)
                        {
                            var shapeRow = shape[index];

                            $(shapeRow).each
                            (
                                function (indexRow)
                                {
                                    var piece = $("<div></div>");
                                    piece.addClass("row" + index);
                                    piece.addClass("col" + indexRow);
                                    piece.attr
                                    (
                                        "style", "color:" + color + ";"
                                        + "font-size:" + fontSize + ";"
                                        + "width:" + unitSize + "px;"
                                        + "height:" + unitSize + "px;"
                                    );

                                    if (shapeRow[indexRow] == 1)
                                    {
                                        piece.html(content);
                                    }

                                    ph.append(piece);
                                }
                            );
                        }
                    );

                    var anim = this;

                    setInterval
                    (
                        function()
                        {
                            anim.animateObject(0);
                        },
                        2500
                    );


We'll create the animateObject() method next. It will accept a parameter, animateId.
            var objAnimation =
            {
                generateRandomNo: function(min, max)
                {
                    return Math.floor((Math.random() * (max - min + 1)) + min);
                },
                content: "&hearts;",
                color: "rgba(255, 0, 0, 1)",
                fontSize: "30px",
                unitSize: 30,
                shape:
                [
                    [0, 0, 1, 1, 0, 0, 1, 1, 0, 0],
                    [0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
                    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
                    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
                    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
                    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
                    [0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
                    [0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
                    [0, 0, 0, 1, 1, 1, 1, 0, 0, 0],
                    [0, 0, 0, 0, 1, 1, 0, 0, 0, 0]
                ],
                render: function()
                {
                    var ph = $("#placeholder");
                    var shape = this.shape;
                    var content = this.content;
                    var color = this.color;
                    var fontSize = this.fontSize;
                    var unitSize = this.unitSize;

                    ph.attr
                    (
                        "style", "width:" + (unitSize * shape.length) + "px;"
                        + "height:" + (unitSize * shape.length) + "px;"
                    );

                    $(shape).each
                    (
                        function (index)
                        {
                            var shapeRow = shape[index];

                            $(shapeRow).each
                            (
                                function (indexRow)
                                {
                                    var piece = $("<div></div>");
                                    piece.addClass("row" + index);
                                    piece.addClass("col" + indexRow);
                                    piece.attr
                                    (
                                        "style", "color:" + color + ";"
                                        + "font-size:" + fontSize + ";"
                                        + "width:" + unitSize + "px;"
                                        + "height:" + unitSize + "px;"
                                    );

                                    if (shapeRow[indexRow] == 1)
                                    {
                                        piece.html(content);
                                    }

                                    ph.append(piece);
                                }
                            );
                        }
                    );

                    var anim = this;

                    setInterval
                    (
                        function()
                        {
                            anim.animateObject(0);
                        },
                        2500
                    );
                },
                animateObject: function (animateId)
                {

                }


Again, declare the variables and set them to the properties of this object. Then let's have an If block to handle animateId being 0.
                animateObject: function (animateId)
                {
                    var shape = this.shape;
                    var color = this.color;
                    var fontSize = this.fontSize;

                    if (animateId == 0)
                    {

                    }
                }


Next, declare changeSize, changeColor and genRan. genRan is set to the generateRandomNo() method of this object. changeSize and changeColor are set to the animateSize() and animateColor() methods of this object respectively.
                animateObject: function (animateId)
                {
                    var shape = this.shape;
                    var color = this.color;
                    var fontSize = this.fontSize;
                    var changeSize = this.animateSize;
                    var changeColor = this.animateColor;
                    var genRan = this.generateRandomNo;

                    if (animateId == 0)
                    {

                    }
                }


We should, of course, create those methods now. We've already created generateRandomNo() earlier. Each of these methods accepts three parameters - obj, delay and color or size. They then run jQuery UI's animate() method from the object referred to by obj. The duration of the animation is set by delay. The property to be manipulated depends on the method. animateColor() animates the color, changing it to color. And animateSize animates the font size, changing it to size pixels.
                animateObject: function (animateId)
                {
                    var shape = this.shape;
                    var color = this.color;
                    var fontSize = this.fontSize;
                    var changeSize = this.animateSize;
                    var changeColor = this.animateColor;
                    var genRan = this.generateRandomNo;

                    if (animateId == 0)
                    {

                    }
                },
                animateColor: function(obj, delay, color)
                {
                    $(obj).animate
                    (
                        {
                            color: color
                        },
                        delay
                    );
                },
                animateSize: function(obj, delay, size)
                {
                    $(obj).animate
                    (
                        {
                            fontSize: size
                        },
                        delay
                    );
                }


Now, back to the animateObject() method. Declare variable colorToChange and set it to a new rgba string by running genRan() for each red, green or blue value. You can use any range of values between 0 to 255. I've chosen these values to keep the heart mostly red or warm colors.
                    if (animateId == 0)
                    {
                        var colorToChange =
                        "rgba(" +
                        genRan(255, 255)
                        + "," +
                        genRan(50, 200)
                        + "," +
                        genRan(50, 200)
                        + ", 1)";
                    }


Now, iterate through each instance of shape.
                    if (animateId == 0)
                    {
                        var colorToChange =
                        "rgba(" +
                        genRan(255, 255)
                        + "," +
                        genRan(50, 200)
                        + "," +
                        genRan(50, 200)
                        + ", 1)";

                        $(shape).each
                        (
                            function (index)
                            {

                            }
                        );
                    }


During each iteration, use the setTimeout() function with an interval of (index x 100). This gives variable delays, which will result in a beautiful pattern. Just trust me on this...

And once the interval is reached, run the changeColor() method using the class name, a delay of 200 milliseconds, and colorToChange!
                    if (animateId == 0)
                    {
                        var colorToChange =
                        "rgba(" +
                        genRan(255, 255)
                        + "," +
                        genRan(50, 200)
                        + "," +
                        genRan(50, 200)
                        + ", 1)";

                        $(shape).each
                        (
                            function (index)
                            {
                                setTimeout
                                (
                                    function()
                                    {
                                        changeColor(".row" + index, 200, colorToChange);
                                    },
                                    index * 100
                                )
                            }
                        );
                    }


Ah, it's changed! Watch! Every 2.5 seconds, the object gradually changes a different color in a vertical cascade!


Now what if we changed it back a slight delay (index x 200) milliseconds later?
                    if (animateId == 0)
                    {
                        var colorToChange =
                        "rgba(" +
                        genRan(255, 255)
                        + "," +
                        genRan(50, 200)
                        + "," +
                        genRan(50, 200)
                        + ", 1)";

                        $(shape).each
                        (
                            function (index)
                            {
                                setTimeout
                                (
                                    function()
                                    {
                                        changeColor(".row" + index, 200, colorToChange);
                                    },
                                    index * 100
                                )
                            }
                        );

                        $(shape).each
                        (
                            function (index)
                            {
                                setTimeout
                                (
                                    function()
                                    {
                                        changeColor(".row" + index, 1000, color);
                                    },
                                    index * 200
                                )
                            }
                        );
                    }


Admit it, this is gorgeous. It changes from red, to a random color, then back to red.


Now, if you got the general idea, we can accelerate past the next part. In the render() method, change this.
                    setInterval
                    (
                        function()
                        {
                            anim.animateObject(1);
                        },
                        2500
                    );


In the animateObject() method, add an If block for if animateId is 1. In fact, go ahead and add If blocks for 2 and 3 as well.
                animateObject: function (animateId)
                {
                    var shape = this.shape;
                    var color = this.color;
                    var fontSize = this.fontSize;
                    var changeSize = this.animateSize;
                    var changeColor = this.animateColor;
                    var genRan = this.generateRandomNo;

                    if (animateId == 0)
                    {
                        var colorToChange =
                        "rgba(" +
                        genRan(250, 255)
                        + "," +
                        genRan(50, 200)
                        + "," +
                        genRan(50, 200)
                        + ", 1)";

                        $(shape).each
                        (
                            function (index)
                            {
                                setTimeout
                                (
                                    function()
                                    {
                                        changeColor(".row" + index, 200, colorToChange);
                                    },
                                    index * 100
                                )
                            }
                        );

                        $(shape).each
                        (
                            function (index)
                            {
                                setTimeout
                                (
                                    function()
                                    {
                                        changeColor(".row" + index, 1000, color);
                                    },
                                    index * 200
                                )
                            }
                        );
                    }

                    if (animateId == 1)
                    {

                    }

                    if (animateId == 2)
                    {

                    }

                    if (animateId == 3)
                    {

                    }


This next one repeats what we did earlier, accept the transition is from left to right! Copy and paste the code from the previous If block, but instead on operating on shape, operate on the first element of shape. Fittingly, we'll change index to indexCol to reflect what we're doing here - operating on columns. To that end, also make sure the class names are changed in the calls to changeColor().
                    if (animateId == 1)
                    {
                        var colorToChange =
                        "rgba(" +
                        genRan(250, 255)
                        + "," +
                        genRan(50, 200)
                        + "," +
                        genRan(50, 200)
                        + ", 1)";

                        $(shape[0]).each
                        (
                            function (indexCol)
                            {
                                setTimeout
                                (
                                    function()
                                    {
                                        changeColor(".col" + indexCol, 200, colorToChange);
                                    },
                                    indexCol * 100
                                )
                            }
                        );

                        $(shape[0]).each
                        (
                            function (indexCol)
                            {
                                setTimeout
                                (
                                    function()
                                    {
                                        changeColor(".col" + indexCol, 1000, color);
                                    },
                                    indexCol * 200
                                )
                            }
                        );
                    }


Great, right?!


Change this again...
                    setInterval
                    (
                        function()
                        {
                            anim.animateObject(2);
                        },
                        2500
                    );


This next one is also a transition from left to right, but this time, it uses the changeSize() method, thus changing the size! Kind of like a Kallang Wave. Copy the code from the previous If block, and instead of using the changeColor() method, use the changeSize() method. Of course, instead of colorToChange, declare and use sizeToChange. sizeToChange here is a size between 10 to 25 pixels, but feel free to implement your own.
                    if (animateId == 2)
                    {
                        var sizeToChange = genRan(10, 25) + "px";

                        $(shape[0]).each
                        (
                            function (indexCol)
                            {
                                setTimeout
                                (
                                    function()
                                    {
                                        changeSize(".col" + indexCol, 500, sizeToChange);
                                    },
                                    indexCol * 100
                                )
                            }
                        );

                        $(shape[0]).each
                        (
                            function (indexCol)
                            {
                                setTimeout
                                (
                                    function()
                                    {
                                        changeSize(".col" + indexCol, 500, fontSize);
                                    },
                                    indexCol * 200
                                )
                            }
                        );
                    }


Sweet!


Change this again!
                    setInterval
                    (
                        function()
                        {
                            anim.animateObject(3);
                        },
                        2500
                    );


This one uses both the changeSize() and changeColor() methods, passing in random colors and sizes respectively! I'll let you figure out on your own how this is done...
                    if (animateId == 3)
                    {
                        $(shape).each
                        (
                            function (index)
                            {
                                $(shape[index]).each
                                (
                                    function (indexCol)
                                    {
                                        setTimeout
                                        (
                                            function()
                                            {
                                                var colorToChange =
                                                "rgba(" +
                                                genRan(250, 255)
                                                + "," +
                                                genRan(50, 200)
                                                + "," +
                                                genRan(50, 200)
                                                + ", 1)";

                                                var sizeToChange = genRan(10, 65) + "px";

                                                changeSize(".row" + index + ".col" + indexCol, 500, sizeToChange);
                                                changeColor(".row" + index + ".col" + indexCol, 300, colorToChange);
                                            },
                                            0
                                        )
                                    }
                                );
                            }
                        );

                        $(shape).each
                        (
                            function (index)
                            {
                                $(shape[index]).each
                                (
                                    function (indexCol)
                                    {
                                        setTimeout
                                        (
                                            function()
                                            {
                                                changeSize(".row" + index + ".col" + indexCol, 500, fontSize);
                                                changeColor(".row" + index + ".col" + indexCol, 300, color);
                                            },
                                            100
                                        )
                                    }
                                );
                            }
                        );
                    }


Groovy! There are so many different ways you could play with this!


One last change. Instead of passing in an arbitrary number as an argument, pass in a random number between 0 and 3. This way, you get a random animation every 2.5 seconds.
                    setInterval
                    (
                        function()
                        {
                            anim.animateObject(anim.generateRandomNo(0, 3));
                        },
                        2500
                    );


We're done!

I went a bit overboard with the animation, but damn that felt good. We could have done it without jQuery, but this web tutorial might have taken twice as long. So yeah, I think it was worth it.

In my heart of hearts, I salute you!
T___T

No comments:

Post a Comment