Wednesday, 18 April 2018

Spot The Bug: Full Transparency

Welcome back, you bug-ridden programmers.

I spy bugs with my little eye...

Today's Spot The Bug edition will deal with one of the pitfalls of client-side scripting. I used to be very sure of what was going on with the Document Object Model (DOM) until this happened to me.

I was just going for a simple Proof Of Concept. There was supposed to be a square in the middle of the page, orange in color. Upon clicking the square,  once, it was supposed to become 50% transparent. And if I clicked on the semi-transparent square a second time, it was supposed to become fully transparent, i.e. invisible.

Here's the HTML code and JavaScript.
<!DOCTYPE html>
<html>
    <head>
        <title>Opacity Test</title>

        <style>
            .square
            {
                width: 200px;
                height: 200px;
                border: 1px solid #000000;
                cursor: pointer;
                margin: 10% auto 0 auto;
            }
        </style>

        <script>
            function adjustOpacity(obj)
            {
                var bgcolor = obj.style.backgroundColor;
                var opacity = parseFloat(bgcolor.replace("rgba(255,100,0,", "").replace(")", ""));

                if (opacity > 0)
                {
                    opacity -=0.5;
                }

                obj.style.backgroundColor = "rgba(255,100,0," + opacity + ")";
            }
        </script>
    </head>

    <body>
        <div class="square" style="background-color:rgba(255,100,0,1)" onclick="adjustOpacity(this);">
            Click me
        </div>   
    </body>
</html>


So, I had this bright idea to simply grab the transparency values from the DOM, meddle with the Alpha value and alter the DOM. No need to produce a lot of If cases, right? Sounded good in theory...

What went wrong

It simply didn't work. The square remained stubbornly, solidly orange no matter how many times I clicked it.


Why it went wrong

See, what happened was that I had coded my CSS inline, which is usually a bad idea anyway. What the browser (in this case, Chrome) did was to parse the HTML and build the DOM with the appropriate properties, making little simplifying tweaks along the way. I did a little test...
        <script>
            function adjustOpacity(obj)
            {
                var bgcolor = obj.style.backgroundColor; console.log(bgcolor);
                var opacity = parseFloat(bgcolor.replace("rgba(255,100,0,", "").replace(")", ""));

                if (opacity > 0)
                {
                    opacity -=0.5;
                }

                obj.style.backgroundColor = "rgba(255,100,0," + opacity + ")";
            }
        </script>


Which meant the background-color property ended up looking like this...


You see now why my JavaScript didn't work? It was looking for a pattern that didn't exist.

How I fixed it

Man, that took a lot of fixing. First, I defined three classes - full, half and none.
        <style>
            .square
            {
                width: 200px;
                height: 200px;
                border: 1px solid #000000;
                cursor: pointer;
                margin: 10% auto 0 auto;
            }

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

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

            .none
            {
                background-color: rgba(255, 100, 0, 0);
            }
        </style>


Then I fixed the JavaScript to change the class name instead of the transparency values, after commenting out my previous code.
        <script>
            function adjustOpacity(obj)
            {
                /*
                var bgcolor = obj.style.backgroundColor;console.log(bgcolor);
                var opacity = parseFloat(bgcolor.replace("rgba(255,100,0,", "").replace(")", ""));

                if (opacity > 0)
                {
                    opacity -=0.5;
                }

                obj.style.backgroundColor = "rgba(255,100,0," + opacity + ")";
                */

                var bgclass = obj.className.replace("square ", "");

                if (bgclass == "full")
                {
                    bgclass = "half";
                }
                else
                {
                    if (bgclass == "half")
                    {
                        bgclass = "none";
                    }
                }

                obj.className = "square " + bgclass;
            }
        </script>


Plus, some changes to the HTML.
    <body>
        <!--<div class="square" style="background-color:rgba(255,100,0,1)" onclick="adjustOpacity(this);">-->
        <div class="square full" onclick="adjustOpacity(this);">
            Click me
        </div>   
    </body>


And yep! It worked just fine now. This is how it looks after the first click...


And after the second click.


Conclusion

Try not to get cute with the DOM. Remember that all web development is basically writing code for browsers to execute, and as such, you're always at the mercy of the browser.

Also, avoid inline styling. Use CSS class names!

Stayed tuned for the next Spot The Bug. Be there or be square!
T___T

No comments:

Post a Comment