Wednesday 19 August 2015

Web Tutorial: Wheel of Fortune (Part 2/4)

Now that we've got that wheel, it's time to write some code to spin it. I won't lie to you, there's going to be a teeny bit of math in here, but nothing fancy, I promise.

However, before anything else, let's just add one more thing to your wheel. I mean, what's a Wheel of Fortune without a pointer, right?

Just after the body tag, add a triangle div.
    <body>
        <div id="triangle"></div>
        <div id="wheel_wrapper">


Then style the triangle div like so. The point of this part of the tutorial is about JavaScript and I don't want to digress too much. So I just conveniently lifted the CSS code for the triangle from this site. (https://css-tricks.com/examples/ShapesOfCSS/). I'll get to explaining this code another day, in another tutorial. Maybe.
        <style type="text/css">
            #triangle
             {
                width:0px;
                height:0px;
                border-left:10px solid transparent;
                border-right:10px solid transparent;
                border-top: 20px solid #000000;
                margin-left:auto;
                margin-right:auto;
                margin-top:50px;
            }  
 

            #wheel_wrapper
            {
                width:500px;
                height:500px;
                position:relative;
                margin:0 auto 0 auto;
            }


And this is what you're going to get. A pointer, so it's more obvious, after spinning, what the result is. It's where the pointer is pointing!


Now for some serious business!

We need to add some displays for your reference, so you can test the results of your spinning. Modify your code as follows, with some placeholders for the data you'll be manipulating.
        <div id="info_wrapper">
            <div id="txtStatus">Stopped</div>
            <input type="text" id="hidPos" value="0">
            <input type="text" id="hidMousedownX" value="-1">
            <input type="text" id="hidMousedownY" value="-1">

            <div id="triangle"></div>
        </div>


You;ve just encased your triangle div in another div info_wrapper, and added a txtStatus div and three textboxes within that div.

txtStatus is meant to display the current status of the wheel. Status is initialized at "Stopped".
hidPos is supposed to show the current rotation of the wheel. Initialized at "0".

hidMouseDownX is supposed to show the x position of your mouse pointer after clicking. Initialized at "-1".

hidMouseDownY is supposed to show the y position of your mouse pointer after clicking. Initialized at "-1".

And  a new CSS class for info_wrapper. Basically sets everything within that div to be in the center.
             #info_wrapper
            {
                width:100%;
                text-align:center;
            }


            #triangle
             {
                width:0px;
                height:0px;
                border-left:10px solid transparent;
                border-right:10px solid transparent;
                border-top: 20px solid #000000;
                margin-left:auto;
                margin-right:auto;
                margin-top:50px;
            }


Here's what it should look like now.


Now for some JavaScript!

Add the following into your head tag.
        <script>
        function rotatewheel()
        {
            var currentstatus=document.getElementById("txtStatus");
            currentstatus.innerHTML="Spinning...";

            var currentpos=document.getElementById("hidPos");
            var wheel=document.getElementById("wheel_wrapper");
            wheel.style.transitionTimingFunction="ease-out";
            wheel.style.transitionDuration="10s";
            wheel.style.transform="rotate("+currentpos.value+"deg)";
            wheel.style.WebkitTransitionTimingFunction="ease-out";
            wheel.style.WebkitTransitionDuration="10s";
            wheel.style.WebkitTransform="rotate("+currentpos.value+"deg)";
        }
        </script>


The rotatewheel() function takes the value in hidPos and rotates the wheel by that many degrees.

First, it changes the status to "Spinning...". Then it changes the wheel_wrapper div's transitionTimingFunction and transitionDuration properties to give it a smooth gradual rotation. Note that all rotations, no matter how big, take 10 seconds! I'll explain why later. Lastly, the value of hidPos is taken and the transform property is set to this value. So the wheel will spin that many degrees, using 10 seconds, and do an "ease-out", meaning it will come to a gradual stop.

Test your rotation!

Modify your code as follows:
        <div id="info_wrapper">
            <div id="txtStatus">Stopped</div>
            <input type="text" id="hidPos" value="0" onchange="rotatewheel();">
            <input type="text" id="hidMousedownX" value="-1">
            <input type="text" id="hidMousedownY" value="-1">
            <div id="triangle"></div>
        </div>


Now when you change the value of hidPos, it runs the rotatewheel() function.

Try it. Change the value of hidPos to, say, 90. Does it rotate 90 degrees? Good. Now change it to 120. What happens? Now change it to 420. You'll notice that the wheel spins again. Now, change it to 160. What happens? Why, the wheel rotates backwards!

Why is this? Well, any lower value from the wheel's current rotation entered into hidPos will cause the wheel to rotate the other way, because it's the most direct way. The wheel was trying to go from 420 degrees to 160 degrees, which is an anti-clockwise rotation.

No worries, we're going to fix this next. Modify your code as follows:
        function rotatewheel()
        {
            var currentstatus=document.getElementById("txtStatus");
            currentstatus.innerHTML="Spinning...";

            var currentpos=document.getElementById("hidPos");
            var wheel=document.getElementById("wheel_wrapper");
            wheel.style.transitionTimingFunction="ease-out";
            wheel.style.transitionDuration="10s";
            wheel.style.transform="rotate("+currentpos.value+"deg)";
            wheel.style.WebkitTransitionTimingFunction="ease-out";
            wheel.style.WebkitTransitionDuration="10s";
            wheel.style.WebkitTransform="rotate("+currentpos.value+"deg)";

            currentpos.value=parseInt(currentpos.value)%360;

            setTimeout(function(){rotatewheel_instant()},10000);

        }


The first line sets the value of hidPos to the remainder of the wheel's current rotation after diving by 360. So if you rotate the wheel 420 degrees, the value of hidPos will be changed to 60 (420%360).

The second line calls the rotatewheel_instant() function exactly 10 seconds after the wheel starts spinning. Since the wheel takes exactly 10 seconds to stop, the effect is almost invisible.

Let's write the rotatewheel_instant() function next.
        <script>
        function rotatewheel()
        {
            var currentstatus=document.getElementById("txtStatus");
            currentstatus.innerHTML="Spinning...";

            var currentpos=document.getElementById("hidPos");
            var wheel=document.getElementById("wheel_wrapper");
            wheel.style.transitionTimingFunction="ease-out";
            wheel.style.transitionDuration="10s";
            wheel.style.transform="rotate("+currentpos.value+"deg)";
            wheel.style.WebkitTransitionTimingFunction="ease-out";
            wheel.style.WebkitTransitionDuration="10s";
            wheel.style.WebkitTransform="rotate("+currentpos.value+"deg)";

            currentpos.value=parseInt(currentpos.value)%360;

            setTimeout(function(){rotatewheel_instant()},10000);
        }

        function rotatewheel_instant()
        {
            var currentpos=document.getElementById("hidPos");
            

            var wheel=document.getElementById("wheel_wrapper");
            wheel.style.transitionTimingFunction="";
            wheel.style.transitionDuration="0s";
            wheel.style.transform="rotate("+currentpos.value+"deg)";

            wheel.style.WebkitTransitionTimingFunction="";
            wheel.style.
WebkitTransitionDuration="0s";
            wheel.style.
WebkitTransform="rotate("+currentpos.value+"deg)";

            currentstatus=document.getElementById("txtStatus");
            currentstatus.innerHTML="Stopped";         
        }


The rotatewheel_instant() function does pretty much the same thing as the rotatewheel() function, with three notable differences.

Firstly, it rotates the wheel, but instead of the full value of hidPos, it takes the value of hidPos, Modulus 360. This much is inferred in the final few lines of rotatewheel() function before calling rotatewheel_instant().

Secondly, it sets the wheel_wrapper div's transitionTimingFunction property to nothing and transitionDuration property 0s to make the transition instant.

Thirdly, it sets the status to "Stopped".

Try your code aga.in. Set hidPos to 420. Watch the wheel spin, and watch the value "reset" itself to 60. (420%360), Now set the value of hidPos to 160. Does it go anti-clockwise anymore? It doesn't! Because the rotation of wheel_wrapper is now 60 instead of 420. Only the rotatewheel_instant() function made the change instantaneously as soon as the wheel stopped spinning, so you never saw it. But if you deliberately use a value lower than 60, such as 20, it will rotate anti-clockwise. The trick here is to ensure that the value is never lower than the value in hidPos. This will be covered in the next part of the web tutorial.

Any other problems?

Well, you may have noticed some jarring jerkiness when the wheel rotates. But that really depends on how large your screen is. That's because you're rotating wheel_wrapper. And wheel_wrapper is actually a square, even if it looks like a circle. So when the corner of the square goes beyond the bottom edge of the screen, what happens? A scroll bar appears on the right side of your window, and this causes the jarring effect!

This is illustrated with a visible border for wheel_wrapper. See how the scroll bar appears when the corner of wheel_wrapper goes way past the bottom edge of the screen?



We're going to fix that, by ensuring that there is a scroll bar right from the get-go. Make this adjustment to the wheel_wrapper CSS specification.
            #wheel_wrapper
            {
                width:500px;
                height:500px;
                position:relative;
                margin:0 auto 400px auto;
            }


Here, we set the bottom margin to 400px so the scroll bar always appears, because the screen isn't large enough to accommodate a 500 by 500 pixel div and a bottom margin of 400 pixels. Clever, eh? Run your code again, see if there's still jerking!

Finally...

I said I'd explain why the transition of the rotation is set to 10 seconds no matter how large the rotation. See, the larger the rotation, the faster the wheel should spin, right? After all, if there's a larger rotation, that means more force was applied on the wheel. So what's the formula for speed? Some basic Physics here...

Velocity = Distance / Time


Now assuming Time is a constant of 10 seconds, and Distance is the number of degrees the wheel has to rotate, it makes sense that the wheel will spin faster or slower if we vary the number of degrees rotated!

Next

Thanks very much for your attention. The next part should be slightly less math-y. Right now, all we've done is cause the wheel to rotate based on what values you manually enter into the hidPos text box. But no self-respecting user is going to go through that. We're going to write functions that allow you to accomplish the rotation with a mouse click and drag!


No comments:

Post a Comment