Wednesday, 11 February 2015

Web Tutorial: Trail of Hearts

Happy Valentine's Day!

If you're reading this, how come you're not on some hot date? Yes, I don't have a life either, but I'm a geek. What's your excuse?

Anyway, in the absence of a hot date, we can have some geeky fun instead. We're going to turn your mouse cursor into a trail of hearts.

How is this useful?

It isn't. It literally is just a bunch of little mini-hearts replacing your mouse cursor. But it will highlight certain CSS and JavaScript tricks which you may want to use for something else.

There's not a whole lot of HTML markup in this tutorial. The bulk of it is CSS and JavaScript. So here you go, get the HTML up!
<!DOCTYPE html>
<html>
    <head>
        <title>VDay test</title>
    </head>

    <body>
        <div id="container">

        </div>
    </body>
</html>


That's it for the HTML! Seriously, that's all there is. The container div is just an overlay on the body, to detect your mouse movement. With that in mind, let's style the container div.
<!DOCTYPE html>
<html>
    <head>
        <title>VDay test</title>
        <style type="text/css">
            #container
            {
                background-color:rgba(254,200,150, 0.7);
                width:100%;
                height:100%;
                position:absolute;
                z-index:500;
                left:0px;
                top:0px;
                cursor:none;
            }
        </style>
    </head>

    <body>
        <div id="container">

        </div>
    </body>
</html>


Here's what we did.

background-color:rgba(254,200,150, 0.7) gives the container div a nice pastel pink.
width: 100% and height: 100% makes it fit the entire screen.
position:absolute,z-index:500, left:0px and top:0px makes the container div fit snugly into the top left corner.
cursor:none hides your mouse cursor.

So far so good? I'm going to show you how to draw a heart using CSS.

The Heart's basic shapes

When you break it apart, the heart's basically one square and two circles, artfully arranged. So the wireframe looks like this.


Wireframe for Heart shape


So we'll start by drawing the square.
        <style type="text/css">
            #container
            {
                background-color:rgba(254,200,150, 0.7);
                width:100%;
                height:100%;
                position:absolute;
                z-index:500;
                left:0px;
                top:0px;
                cursor:none;
            }

            .heart
            {
                background-color:#FF0000;
                position:absolute;
                z-index:1000;
                width:10px;
                height:10px;
            }
        </style>


Here, we basically have a red square of 10x10 pixels. The z-index property has been set to higher than that of the container div, so it'll be displayed over the container div.

Now for the circles. We're going to use the before and after selectors.

For more on CSS selectors: (http://www.w3schools.com/cssref/css_selectors.asp)

        <style type="text/css">
            #container
            {
                background-color:rgba(254,200,150, 0.7);
                width:100%;
                height:100%;
                position:absolute;
                z-index:500;
                left:0px;
                top:0px;
                cursor:none;
            }

            .heart
            {
                background-color:#FF0000;
                position:absolute;
                z-index:1000;
                width:10px;
                height:10px;
            }

            .heart:before,.heart:after
            {
                content:"";
                border-radius:50%;
                width:10px;
                height:10px;
                display: block;
                position:absolute;
                background-color:#FF0000;
            }

            .heart:before
            {
                margin-left:-5px;
            }

            .heart:after
            {
                margin-top:-5px;
            }
        </style>

This adds two circles, both in red, within the square. Both circles are the same size as the square. Since they have so many things in common, before and after are styled together for the most part. And since the position property is set to absolute, they're nicely overlapping one another within the square. But the before circle needs to move left, and the after circle needs to move right. Hence the adjustments to the margin-top and margin-left properties.

If you do a <div class="heart"></div> now, this is what you should see.


Now it's lying on its side. All you need to do is rotate the thing 45 degrees, you're all set! But let's hold off on that. Now that you've learned how to draw a heart, we're gonna start making a trail of them. For that, you'll need to learn about mouse events.

JavaScript mousemove event (http://www.quirksmode.org/js/events_mouse.html#mousemove)

The idea here is to detect whenever there's a mouse movement, then draw hearts. Insert the following JavaScript into your HTML head.
        <script>
            document.onmousemove=makeheart;

            function makeheart(e)
            {
                        if (!e)
                        {
                                var e=window.event;
                        }
            }
        </script>

The makeheart() function will fire off whenever the page detects a mouse movement. e here stands for "event". A mousemove is an event, and you'll need to capture the details of that event.
        <script>
            document.onmousemove=makeheart;

            function makeheart(e)
            {
                        if (!e)
                        {
                                var e=window.event;
                        }
                       
                        var x=parseInt(e.screenX);
                        var y=parseInt(e.screenY);

                        var heartsize=Math.floor((Math.random() * 3));
            }
        </script>

e.screenX gets you the x coordinate of the mouse pointers current location, and e.screenY gives you the y coordinate. For fun, we're going to randomize the size of the heart. Math.floor((Math.random() * 3)) gives you a number from 0 to 3, which means your hearts will either stay the same size or go up to 3 times in size.

Next, we do this.
        <script>
            document.onmousemove=makeheart;

            function makeheart(e)
            {
                        if (!e)
                        {
                                var e=window.event;
                        }
                       
                        var x=parseInt(e.screenX);
                        var y=parseInt(e.screenY);

                        var heartsize=Math.floor((Math.random() * 3));

                        var heart = document.createElement("div");                
                        document.getElementById("container").appendChild(heart);
                        heart.className="heart";
                        heart.style.left=x+"px";
                        heart.style.top=y+"px";
                        heart.style.transform="rotate(45deg) scale("+heartsize+","+heartsize+")";
            }
        </script>

Here, we create a div element and assign it to the heart variable. Then we add heart to the container div as a nested div with the appendChild() method. Now that the heart is created, assign the value "heart" to its className property, so that the styling you did earlier now kicks in! And since it's supposed to be where your mouse pointer is, set its left and top property to the mouse pointer's x and y coordinates, respectively. And while you're at it, rotate the heart 45 degrees and scale it to the random size!

Now open your page in a browser and run your mouse pointer over it. This is what you should see - a lovely trail of hearts!


Beautiful, eh? What next?

It would work much better as a trail if the previous hearts disappeared. Also, appending too many hearts to the page may eventually cause your system to slow down. So we're gonna make the earlier hearts disappear.

Create a few function deleteheart().
        <script>
            document.onmousemove=makeheart;

            function makeheart(e)
            {
                        if (!e)
                        {
                                var e=window.event;
                        }
                       
                        var x=parseInt(e.screenX);
                        var y=parseInt(e.screenY);

                        var heartsize=Math.floor((Math.random() * 3));

                        var heart = document.createElement("div");                
                        document.getElementById("container").appendChild(heart);
                        heart.className="heart";
                        heart.style.left=x+"px";
                        heart.style.top=y+"px";
                        heart.style.transform="rotate(45deg) scale("+heartsize+","+heartsize+")";
            }

            function deleteheart()
            {
                var hearts=document.getElementsByClassName("heart");           
            }
        </script>

With document.getElementsByClassName("heart"), you're referencing all elements with the className of "heart", which is essentially all the lovely red hearts floating over your screen, and then assigning it to the array hearts. Next, we have a few conditions and iterations to implement. Add the following code.
        <script>
            document.onmousemove=makeheart;

            function makeheart(e)
            {
                        if (!e)
                        {
                                var e=window.event;
                        }
                       
                        var x=parseInt(e.screenX);
                        var y=parseInt(e.screenY);

                        var heartsize=Math.floor((Math.random() * 3));

                        var heart = document.createElement("div");               
                        document.getElementById("container").appendChild(heart);
                        heart.className="heart";
                        heart.style.left=x+"px";
                        heart.style.top=y+"px";
                        heart.style.transform="rotate(45deg) scale("+heartsize+","+heartsize+")";            }

            function deleteheart()
            {
                       var hearts=document.getElementsByClassName("heart");

                       if (hearts.length>10)
                       {
                           for (i = 0; i < hearts.length; i++)
                           {
                                if (i<=5)
                                {
                                    document.getElementById("container").removeChild(hearts[i]);
                                }
                           }
                       }           
            }
        </script>


What the code here means is - if there are more than 10 hearts on the screen, start trimming the trail of hearts. But only trim off 6 at a time! Examine the code at your own leisure. You may want to do some additional reading.

JavaScript appendChild (http://www.w3schools.com/jsref/met_node_appendchild.asp)
JavaScript removeChild (http://www.w3schools.com/jsref/met_node_removechild.asp)

We're not done yet, though. You've only written the function deleteheart(), not called it. So call it like this, just under the line that calls makeheart().

            document.onmousemove=makeheart;
            setInterval(function(){deleteheart();},100);

Here we assign the deleteheart() function to an interval of 100 milliseconds. So the browser constantly checks for hearts and trims the trail accordingly. Refresh the browser, try it!

Just in case you feel the need to read even more extra material, here's a link for the JavaScript timing functions. (http://www.w3schools.com/js/js_timing.asp)

Final touches

Always room for improvement. We're going to add a little life to the trail of hearts.

First, we set the hearts to 50% opacity. So we can see each heart individually. Next, we're going to define an animation!

For more on CSS3 animations: (http://www.w3schools.com/css/css3_animations.asp)
        <style type="text/css">
            #container
            {
                background-color:rgba(254,200,150, 0.7);
                width:100%;
                height:100%;
                position:absolute;
                z-index:500;
                left:0px;
                top:0px;
                cursor:none;
            }

            .heart
            {
                background-color:#FF0000;
                position:absolute;
                z-index:1000;
                opacity: 0.5;
                filter: alpha(opacity=50); /* For IE8 and earlier */
                width:10px;
                height:10px;
                -webkit-animation: heartpop 0.5s infinite alternate; /* Chrome, Safari, Opera */
                animation: heartpop 0.5s infinite alternate;
            }

            /* Chrome, Safari, Opera */
            @-webkit-keyframes heartpop 
            {
                from {margin-top:5px;}
                to {margin-top:-5px;}
            }

            /* Standard syntax */
            @keyframes heartpop 
            {
                from {margin-top:5px;}
                to {margin-top:-5px;}
            }

            .heart:before,.heart:after
            {
                content:"";
                border-radius:50%;
                width:10px;
                height:10px;
                display: block;
                position:absolute;
                background-color:#FF0000;
            }

            .heart:before
            {
                margin-left:-5px;
            }

            .heart:after
            {
                margin-top:-5px;
            }
        </style>

The heartpop animation is set to repeat infinitely and reverse after each animation. Here, I'm just making the hearts bounce up and down, so I choose to alter the margin-top property.

This is what you should be seeing now!


Cool, eh? Just think, we used to have to write Java applets to do this!

Sincerely (with all my heart),
T___T

No comments:

Post a Comment