Friday 10 February 2017

Web Tutorial: Valentine Scratch Card

A mushy lovey-dovey upcoming Valentine's Day, dear readers!

Today we'll be making a Valentine's Day scratch card. A card whose contents will be hidden until you run your mouse cursor over it, revealing the contents of the card bit by bit.

The HTML is really simple, and the majority of the work will be borne by the CSS and JavaScript. Here goes...
<!DOCTYPE html>
<html>
    <head>
        <title>Scratch!</title>
    </head>
    <body>
        <div id="scratch_wrapper">

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


I wasn't having you on, was I? The HTML really is that simple. You have a basic HTML layout and a div with an id of scratch_wrapper.

OK, let's style scratch_wrapper. We'll make it a 400 by 400 pixel card, and set the margin property to 10% auto 0 auto so that it sits in the middle of your screen. Then we'll set the border to a nice red so you can see what's happening.
    <head>
        <title>Scratch!</title>
        <style>
            #scratch_wrapper
            {
                width:400px;
                height:400px;
                margin:10% auto 0 auto;
                border: 1px solid #FF0000;
            }
        </style>
    </head>


This is your card so far!


OK, time for a bit of JavaScript. Modify your HTML this way...
    <body onload="unscratch()">


... and add the unscratch() function. This causes the scratch card to revert to its full initial state, where the contents are hidden from view. First, you clear the scratch_wrapper div.
        <style>
            #scratch_wrapper
            {
                width:400px;
                height:400px;
                margin:10% auto 0 auto;
                border: 1px solid #FF0000;
            }
        </style>

        <script>
            function unscratch()
            {
                var wrapper=document.getElementById("scratch_wrapper");
                wrapper.innerHTML="";
            }
        </script>


Next, you specify a nested For loop. 80 times by 80 times. Why 80? We'll get to that in a minute.
            function unscratch()
            {
                var wrapper=document.getElementById("scratch_wrapper");
                wrapper.innerHTML="";

                for (var i=0;i<80;i++)
                {
                    for (var j=0;j<80;j++)
                    {

                    }
                }
            }


In the inner loop of the nested For loop, add this code. It creates a div element and assigns it the CSS class of scratch and full.
            function unscratch()
            {
                var wrapper=document.getElementById("scratch_wrapper");
                wrapper.innerHTML="";

                for (var i=0;i<80;i++)
                {
                    for (var j=0;j<80;j++)
                    {
                        scratch=document.createElement("div");
                        scratch.className="scratch full";
                    }
                }
            }


Then the unique id of this element is created according to its row, i, and its column, j.
            function unscratch()
            {
                var wrapper=document.getElementById("scratch_wrapper");
                wrapper.innerHTML="";

                for (var i=0;i<80;i++)
                {
                    for (var j=0;j<80;j++)
                    {
                        scratch=document.createElement("div");
                        scratch.className="scratch full";
                        scratch.id="scratch_"+i+"_"+j;
                    }
                }
            }


Next, you insert this div element into the scratch_wrapper div using the appendChild() method.
            function unscratch()
            {
                var wrapper=document.getElementById("scratch_wrapper");
                wrapper.innerHTML="";

                for (var i=0;i<80;i++)
                {
                    for (var j=0;j<80;j++)
                    {
                        scratch=document.createElement("div");
                        scratch.className="scratch full";
                        scratch.id="scratch_"+i+"_"+j;
                        wrapper.appendChild(scratch);
                    }
                }
            }


What's scratch and full?

Aha! I see you've been paying attention. Your objective here is to fill the scratch_wrapper div with tiny squares. scratch defines the dimensions of each square, while full defines the color. You'll see that each square is 5 by 5 pixels. This is not a coincidence. The scratch_card div is 400 pixels wide and 400 pixels tall. If you have 80 rows of 80 squares, each square 5 by 5 pixels, you have (5 x 80 = 400)! The float property is set to left so that when the 400 pixel limit is reached, the next row automatically begins on the left. As for the full CSS class, it defines a color red (feel free to change it!) at 100% opacity.
            #scratch_wrapper
            {
                width:400px;
                height:400px;
                margin:10% auto 0 auto;
                border: 1px solid #FF0000;
            }

            .scratch
            {
                width:5px;
                height:5px;
                float:left;
            }

            .full
            {
                background-color:rgba(255,0,0,1);
            }


Your card is now covered with (80 x 80 = 6400) red squares!


Our next objective is to make each square reveal part of the underlying card as your mouse cursor goes over it. To that end, modify your JavaScript. This line adds an event to each div created. On a mouse over, it will fire off the scratchit() function, passing in the id of the current square as an argument.
                for (var i=0;i<80;i++)
                {
                    for (var j=0;j<80;j++)
                    {
                        scratch=document.createElement("div");
                        scratch.className="scratch full";
                        scratch.id="scratch_"+i+"_"+j;
                        scratch.onmouseover=function(){scratchit(this.id)};
                        wrapper.appendChild(scratch);
                    }
                }


And of course, we'll need to create the scratchit() function, which will take in scratchid as a string parameter.
        <script>
            function unscratch()
            {
                var wrapper=document.getElementById("scratch_wrapper");
                wrapper.innerHTML="";

                for (var i=0;i<80;i++)
                {
                    for (var j=0;j<80;j++)
                    {
                        scratch=document.createElement("div");
                        scratch.className="scratch full";
                        scratch.id="scratch_"+i+"_"+j;
                        scratch.onmouseover=function(){scratchit(this.id)};
                        wrapper.appendChild(scratch);
                    }
                }
            }

            function scratchit(scratchid)
            {

            }


Before we go further...

Let's examine what the code is supposed to do.

The layout of the squares is as per follows... (assuming the first number is the row and the second number is the column. Here, I'll show you the top left corner of your scratch_wrapper div as an example.
scratch_0_0 scratch_0_1 scratch_0_2 scratch_0_3 scratch_0_4
scratch_1_0 scratch_1_1 scratch_1_2 scratch_1_3 scratch_1_4
scratch_2_0 scratch_2_1 scratch_2_2 scratch_2_3 scratch_2_4


Let's take x as the row of the current square (the square which the mouse cursor is over) and y as the column. For this example, x = 2 and y = 2. Therefore the id of the square in the example is scratch_2_2. When your mouse cursor is over the square, the surrounding squares are affected. Those squares touching the sides and corners of the current square are affected most severely, lowering their opacity to 20%. Squares one square away from the current square, are slightly affected, lowering their opacity to 50%.
scratch_0_0 scratch_0_1 scratch_0_2 scratch_0_3 scratch_0_4 scratch_0_5
scratch_1_0 scratch_1_1 scratch_1_2 scratch_1_3 scratch_1_4 scratch_1_5
scratch_2_0 scratch_2_1 scratch_2_2 scratch_2_3 scratch_2_4 scratch_2_5
scratch_3_0 scratch_3_1 scratch_3_2 scratch_3_3 scratch_3_4 scratch_3_5
scratch_4_0 scratch_4_1 scratch_4_2 scratch_4_3 scratch_4_4 scratch_4_5

Back to the code!

To do this, first, we take the id of the current square, using the argument scratchid and the split() method. From here, we define x and y.

            function scratchit(scratchid)
            {
                var x=0;
                var y=0;

                var xyvalues=scratchid.split("_");
                x=parseInt(xyvalues[1]);
                y=parseInt(xyvalues[2]);
            }


Then we run the changeopacity() function using the id and the argument "gone".
            function scratchit(scratchid)
            {
                var x=0;
                var y=0;

                var xyvalues=scratchid.split("_");
                x=parseInt(xyvalues[1]);
                y=parseInt(xyvalues[2]);

                var id="";

                changeopacity(scratchid,"gone");
            }


Next, we extrapolate the ids of the surrounding squares, and run the changeopacity() function using these ids and the argument "almostgone". They're more severely affected, remember?
            function scratchit(scratchid)
            {
                var x=0;
                var y=0;

                var xyvalues=scratchid.split("_");
                x=parseInt(xyvalues[1]);
                y=parseInt(xyvalues[2]);

                var id="";

                changeopacity(scratchid,"gone");

                id="scratch_"+(x-1)+"_"+y;
                changeopacity(id,"almostgone");
              
                id="scratch_"+(x+1)+"_"+y;
                changeopacity(id,"almostgone");

                id="scratch_"+x+"_"+(y-1);
                changeopacity(id,"almostgone");
              
                id="scratch_"+x+"_"+(y+1);
                changeopacity(id,"almostgone");

                id="scratch_"+(x+1)+"_"+(y+1);
                changeopacity(id,"almostgone");

                id="scratch_"+(x-1)+"_"+(y+1);
                changeopacity(id,"almostgone");

                id="scratch_"+(x+1)+"_"+(y-1);
                changeopacity(id,"almostgone");

                id="scratch_"+(x-1)+"_"+(y-1);
                changeopacity(id,"almostgone");
            }


Then we do the same for the squares one square away from the current square, but this time using the argument "half".
            function scratchit(scratchid)
            {
                var x=0;
                var y=0;

                var xyvalues=scratchid.split("_");
                x=parseInt(xyvalues[1]);
                y=parseInt(xyvalues[2]);

                var id="";

                changeopacity(scratchid,"gone");

                id="scratch_"+(x-1)+"_"+y;
                changeopacity(id,"almostgone");
              
                id="scratch_"+(x+1)+"_"+y;
                changeopacity(id,"almostgone");

                id="scratch_"+x+"_"+(y-1);
                changeopacity(id,"almostgone");
              
                id="scratch_"+x+"_"+(y+1);
                changeopacity(id,"almostgone");

                id="scratch_"+(x+1)+"_"+(y+1);
                changeopacity(id,"almostgone");

                id="scratch_"+(x-1)+"_"+(y+1);
                changeopacity(id,"almostgone");

                id="scratch_"+(x+1)+"_"+(y-1);
                changeopacity(id,"almostgone");

                id="scratch_"+(x-1)+"_"+(y-1);
                changeopacity(id,"almostgone");

                id="scratch_"+(x-2)+"_"+y;
                changeopacity(id,"half");
              
                id="scratch_"+(x+2)+"_"+y;
                changeopacity(id,"half");

                id="scratch_"+x+"_"+(y-2);
                changeopacity(id,"half");
              
                id="scratch_"+x+"_"+(y+2);
                changeopacity(id,"half");

                id="scratch_"+(x+2)+"_"+(y+2);
                changeopacity(id,"half");

                id="scratch_"+(x-2)+"_"+(y+2);
                changeopacity(id,"half");

                id="scratch_"+(x+2)+"_"+(y-2);
                changeopacity(id,"half");

                id="scratch_"+(x-2)+"_"+(y-2);
                changeopacity(id,"half");

                id="scratch_"+(x+1)+"_"+(y+2);
                changeopacity(id,"half");

                id="scratch_"+(x-1)+"_"+(y+2);
                changeopacity(id,"half");

                id="scratch_"+(x+1)+"_"+(y-2);
                changeopacity(id,"half");

                id="scratch_"+(x-1)+"_"+(y-2);
                changeopacity(id,"half");

                id="scratch_"+(x+2)+"_"+(y+1);
                changeopacity(id,"half");

                id="scratch_"+(x-2)+"_"+(y+1);
                changeopacity(id,"half");

                id="scratch_"+(x+2)+"_"+(y-1);
                changeopacity(id,"half");

                id="scratch_"+(x-2)+"_"+(y-1);
                changeopacity(id,"half");
            }


Next, we write the changeopacity() function.
            function scratchit(scratchid)
            {
                var x=0;
                var y=0;

                var xyvalues=scratchid.split("_");
                x=parseInt(xyvalues[1]);
                y=parseInt(xyvalues[2]);

                var id="";

                changeopacity(scratchid,"gone");

                id="scratch_"+(x-1)+"_"+y;
                changeopacity(id,"almostgone");
              
                id="scratch_"+(x+1)+"_"+y;
                changeopacity(id,"almostgone");

                id="scratch_"+x+"_"+(y-1);
                changeopacity(id,"almostgone");
              
                id="scratch_"+x+"_"+(y+1);
                changeopacity(id,"almostgone");

                id="scratch_"+(x+1)+"_"+(y+1);
                changeopacity(id,"almostgone");

                id="scratch_"+(x-1)+"_"+(y+1);
                changeopacity(id,"almostgone");

                id="scratch_"+(x+1)+"_"+(y-1);
                changeopacity(id,"almostgone");

                id="scratch_"+(x-1)+"_"+(y-1);
                changeopacity(id,"almostgone");

                id="scratch_"+(x-2)+"_"+y;
                changeopacity(id,"half");
              
                id="scratch_"+(x+2)+"_"+y;
                changeopacity(id,"half");

                id="scratch_"+x+"_"+(y-2);
                changeopacity(id,"half");
              
                id="scratch_"+x+"_"+(y+2);
                changeopacity(id,"half");

                id="scratch_"+(x+2)+"_"+(y+2);
                changeopacity(id,"half");

                id="scratch_"+(x-2)+"_"+(y+2);
                changeopacity(id,"half");

                id="scratch_"+(x+2)+"_"+(y-2);
                changeopacity(id,"half");

                id="scratch_"+(x-2)+"_"+(y-2);
                changeopacity(id,"half");

                id="scratch_"+(x+1)+"_"+(y+2);
                changeopacity(id,"half");

                id="scratch_"+(x-1)+"_"+(y+2);
                changeopacity(id,"half");

                id="scratch_"+(x+1)+"_"+(y-2);
                changeopacity(id,"half");

                id="scratch_"+(x-1)+"_"+(y-2);
                changeopacity(id,"half");

                id="scratch_"+(x+2)+"_"+(y+1);
                changeopacity(id,"half");

                id="scratch_"+(x-2)+"_"+(y+1);
                changeopacity(id,"half");

                id="scratch_"+(x+2)+"_"+(y-1);
                changeopacity(id,"half");

                id="scratch_"+(x-2)+"_"+(y-1);
                changeopacity(id,"half");
            }

            function changeopacity(scratchid,opacity)
            {

            }


What we're going to do first, is create three more CSS classes, half, almostgone and gone. They're almost identical to the full CSS class, except that half has 50% opacity while almostgone has 20%. gone is completely transparent.
            .full
            {
                background-color:rgba(255,0,0,1);
            }

            .half
            {
                background-color:rgba(255,0,0,0.5);
            }

            .almostgone
            {
                background-color:rgba(255,0,0,0.2);
            }

            .gone
            {
                background-color:rgba(255,0,0,0);
            }


So here we obtain the actual square via the id passed in, and perform the operation if the object returned is not a null. It will be null if the object does not exist, which can happen if your current square is near the edges of the scratch_wrapper div and you're trying to get its surrounding squares (which, by definition, don't exist).

Also, declare the variable currentopacity. It'll be useful soon,
            function changeopacity(scratchid,opacity)
            {
                var currentopacity="";
                var scratched=document.getElementById(scratchid);

                if (scratched!=null)
                {
                    if (opacity=="gone")
                    {
                        document.getElementById(scratchid).className="scratch gone";  
                    }
                }
            }


The objective is to set the class of the current square to gone, which we've already done, and the surrounding squares to lower opacity, depending on their distance from the current square. But what if the squares affected already have lowered opacity due to an earlier mouse over?

So we find out what their current opacity is, using the getopacity() function. This merely takes the object and obtains its class. It removes the "scratch " (with the space) and returns the resulting string.
            function changeopacity(scratchid,opacity)
            {
                function getopacity(div)
                {
                    var bg=div.className;
                    bg=bg.replace("scratch ","");
                    return bg;
                }

                var currentopacity="";
                var scratched=document.getElementById(scratchid);

                if (scratched!=null)
                {
                    if (opacity=="gone")
                    {
                        document.getElementById(scratchid).className="scratch gone";  
                    }
                }
            }


If you're trying to set the class to "half", first, obtain the current opacity and set the value of currentopacity to this.
            function changeopacity(scratchid,opacity)
            {
                function getopacity(div)
                {
                    var bg=div.className;
                    bg=bg.replace("scratch ","");
                    return bg;
                }

                var currentopacity="";
                var scratched=document.getElementById(scratchid);

                if (scratched!=null)
                {
                    if (opacity=="half")
                    {
                        currentopacity=getopacity(scratched);
                    }

                    if (opacity=="gone")
                    {
                        document.getElementById(scratchid).className="scratch gone";  
                    }
                }
            }


If currentopacity is "full", proceed as planned.
            function changeopacity(scratchid,opacity)
            {
                function getopacity(div)
                {
                    var bg=div.className;
                    bg=bg.replace("scratch ","");
                    return bg;
                }

                var currentopacity="";
                var scratched=document.getElementById(scratchid);

                if (scratched!=null)
                {
                    if (opacity=="half")
                    {
                        currentopacity=getopacity(scratched);

                        if (currentopacity=="full")
                        {
                            scratched.className="scratch half";
                        }
                    }

                    if (opacity=="gone")
                    {
                        document.getElementById(scratchid).className="scratch gone";  
                    }
                }
            }


Now, if currentopacity is already "half", set the class to "almostgone" instead.
            function changeopacity(scratchid,opacity)
            {
                function getopacity(div)
                {
                    var bg=div.className;
                    bg=bg.replace("scratch ","");
                    return bg;
                }

                var currentopacity="";
                var scratched=document.getElementById(scratchid);

                if (scratched!=null)
                {
                    if (opacity=="half")
                    {
                        currentopacity=getopacity(scratched);

                        if (currentopacity=="full")
                        {
                            scratched.className="scratch half";
                        }

                        if (currentopacity=="half")
                        {
                            scratched.className="scratch almostgone";
                        }
                    }

                    if (opacity=="gone")
                    {
                        document.getElementById(scratchid).className="scratch gone";  
                    }
                }
            }


If currentopacity is "almostgone", set it to "gone"!
            function changeopacity(scratchid,opacity)
            {
                function getopacity(div)
                {
                    var bg=div.className;
                    bg=bg.replace("scratch ","");
                    return bg;
                }

                var currentopacity="";
                var scratched=document.getElementById(scratchid);

                if (scratched!=null)
                {
                    if (opacity=="half")
                    {
                        currentopacity=getopacity(scratched);

                        if (currentopacity=="full")
                        {
                            scratched.className="scratch half";
                        }

                        if (currentopacity=="half")
                        {
                            scratched.className="scratch almostgone";
                        }

                        if (currentopacity=="almostgone")
                        {
                            scratched.className="scratch gone";
                        }
                    }

                    if (opacity=="gone")
                    {
                        document.getElementById(scratchid).className="scratch gone";  
                    }
                }
            }


Now do the same for the case of "almostgone"!
            function changeopacity(scratchid,opacity)
            {
                function getopacity(div)
                {
                    var bg=div.className;
                    bg=bg.replace("scratch ","");
                    return bg;
                }

                var currentopacity="";
                var scratched=document.getElementById(scratchid);

                if (scratched!=null)
                {
                    if (opacity=="half")
                    {
                        currentopacity=getopacity(scratched);

                        if (currentopacity=="full")
                        {
                            scratched.className="scratch half";
                        }

                        if (currentopacity=="half")
                        {
                            scratched.className="scratch almostgone";
                        }

                        if (currentopacity=="almostgone")
                        {
                            scratched.className="scratch gone";
                        }
                    }

                    if (opacity=="almostgone")
                    {
                        currentopacity=getopacity(scratched);

                        if (currentopacity=="full")
                        {
                            scratched.className="scratch almostgone";
                        }

                        if (currentopacity=="half")
                        {
                            scratched.className="scratch gone";
                        }

                        if (currentopacity=="almostgone")
                        {
                            scratched.className="scratch gone";
                        }
                    }

                    if (opacity=="gone")
                    {
                        document.getElementById(scratchid).className="scratch gone";  
                    }  
                }
            }


Run your code. Are you getting this effect?


OK, let's make a slight improvement. Alter the scratch CSS class.

Now try this again. The transition will make the effect look way better.
            .scratch
            {
                width:5px;
                height:5px;
                float:left;
                transition:all 1s;
            }


Finishing touches

Modify your scratch_wrapper div's CSS to use this image as background.


            #scratch_wrapper
            {
                width:400px;
                height:400px;
                margin:10% auto 0 auto;
                border: 1px solid #FF0000;
                background-image: url(bg.jpg);
                background-size:cover;
                background-repeat:no-repeat;
                background-position:50% 50%;
            }


Niiiiiice.


Till next Valentine's, remember that beauty is only skin-deep!
T___T

No comments:

Post a Comment