May 25th in JavaScript by .

offscreen.js – Accessibly Hide And Show Elements


A couple of weeks ago I wrote an article where I changed the way jQuery’s hide() and show() function work. After publishing the article there was quite a lot of comments where people expressed their dislike of the idea of changing these two functions. Thinking about it, I see how the title of the article led people to believe that I was suggesting that jQuery should change their hide() and show() functions to use positioning as apposed to display.

In fact that is not the case. These functions do exactly what their names suggest, nothing more, nothing less and there is no need to change these in any way. With that said, there definitely are scenarios where the use of display is not the best and positioning content offscreen is a much better choice. As mentioned in some off my comments on the previous article, one that immediately springs to mind is tabbed interfaces.

To test this I created a simple tabbed interface, in the one I used offscreen.js to handle the hiding and showing and with the other I used jQuery’s show() and hide(). Next I installed JAWS from Freedom Scientific and opened up the second test page.

JAWS here identified four links and one top level heading. If you open the headings dialog (Insert+F6) the dialog list just one heading even though there are four on the page. Opening the first demo page produces a much better result. JAWS identifies the four links but, then also 4 level one headings. What really improves the usability of the page is that the user can open the headings dialog and jump to each of the content section directly from a tool they are experienced in using and there is no need to get around the tabs using tabbing and activating links.

This then provides enough proof that using offscreen positioning as apposed to hiding content with display sometimes, should be the preferred option and enough impetus to write a script that will make this as simple as using show() and hide(). The result off all of this then is offscreen.js.

offscreen.js exposes two functions, the first being offscreen()

jQuery.fn.offscreen = function(callback) {

     var i = 0,
     j = this.length,
     position = "",
     left = "";

    if(useClass) {
        for (i = 0; i < j; i++) {
            this.addClass("accessibly-hidden");
        }
    } else {

        for (i = 0, j = this.length; i < j; i++) {

            if (this[i].style) {
                position = jQuery.css(this[i], "position");
                left = jQuery.css(this[i], "left");

                console.log(position);
                console.log(left);

                if (left !== "-1599984px") {
                    jQuery._data(this[i], {
                        "oldposition" : position,
                        "oldleft" : left
                    });
                }
            }
        }

        // Set the display of the elements in a second loop to avoid the constant reflow
        for (i = 0; i < j; i++) {
            /*
            * Hide elements using positioning and left offset. This makes for an accessible
            * way of hiding content without hiding it from screen readers.
            */
            if (this[i].style) {
                this[i].style.position = "absolute";
                this[i].style.left = "-99999em";
            }
        }
    }
	return this;
};

As you can see from the above, I implemented this as an extension to jQuery and not as a standalone plugin. I also ‘inherited’ some of the code from the jQuery hide() function to make use of the data object to store the initial values of the element before hiding it. You will also see that I first test whether the useClass boolean is true and if it is, I simply add a class.

So, where does useClass fit in and how does it work? Well, while I was writing the script I though, what if one already added the accessible-hidden class to your CSS? Then one simply needs to dynamically add and remove the class instead of having to manipulate the style attribute and writing and reading from the data object.

Because I want the use of the script as frictionless as possible, I decided to check for the existence of the class inside the code and not rely on the user to pass in a variable. The code that does this, is as follows:

var useClass = false,
stylesheets = document.styleSheets,
styleRules = [],
ruleList = [],
initOffscreen = function() {

	var setRuleList = function(styleSheet) {
		/* Because IE uses the non standard rules property we have to do this check */
		return (styleSheet.cssRules) ? styleSheet.cssRules : styleSheet.rules;
	};

	$(stylesheets).each(function(i, styleSheet) {
		styleRules = setRuleList(styleSheet);
	});

	$(styleRules).each(function(i, rule) {
		useClass = rule.selectorText === ".accessibly-hidden" ? true : false;
	});
};

The code basically runs through all of the stylesheets linked to the document and then builds up an array of all of the style rules. Lastly, we loop through the style rules array and test whether any of them matches the accessibly-hidden class and then either sets useClass to true or false.

The other function that is exposed is onscreen:

jQuery.fn.onscreen = function(callback ) {
	var elem,
    i = 0,
    j = this.length,
    left = "";

    if(useClass) {
        for (i = 0; i < j; i++) {
            this.removeClass("accessibly-hidden");
        }
    } else {

        for ( i = 0; i < j; i++ ) {
            elem = this[i];

			if ( elem.style ) {
				left = elem.style.left;

				if (left === "" || left === "-99999em") {
					elem.style.position = jQuery._data(elem, "oldposition") || "static";
					elem.style.left = jQuery._data(elem, "oldleft") || "";
				}
			}
		}
    }
	return this;
};

As with offscreen, we check whether useClass is true, if so, simply remove the class else, read the original properties of the element from the data object and restore them to return the element to the screen. And that is pretty much that. Only two things remain, the usage and the accessible-hidden class, if you wish to go this root and have access to the CSS to add the class.

The accessibly-hidden class:

/* Based on http://webaim.org/techniques/css/invisiblecontent/ */
.accessibly-hidden {
    position:absolute;
    left:-99999em;
    top:auto;
    width:1px;
    height:1px;
    overflow:hidden;
}

Usage:

To use offscreen.js is dead simple:

$().ready(function() {
    initOffscreen();
});

The above is basically called to run through the stylesheets and set the useClass variable. After this, you can simply call either $(“element”).offscreen() or $(“element”).onscreen(). Also note, if you know that the class does not exist, you can ommit the call to initOffscreen().

You can grab the code from Github and I would love to hear everyone’s comments and your suggestions on optimizing and improving the code.

Image courtesy: biblioteekje

  • Jon

    This looks like a GREAT solution but I just can’t seem to get it to work.  I’m a semi-noob with JS (actually more of a designer type who integrates JS) so the logic sometimes escapes me.  I’m calling offscreen.js in my head and I know the accessibility-hidden class is not in the page so I’m skipping the init.  I added the class to the css.  I thought that I could replace the .hide and .show references in my current script with .onscreen and .offscreen but that doesn’t work: all the JS dies.

    Here’s my current script junk I’m trying to fix using offscreen.js.  Can you offer advice?

    //Hide (Collapse) the toggle containers on load $(“.openLoad”).onscreen();  $(“.closedLoad”).offscreen();  //Switch the “Open” and “Close” state per click then slide up/down (depending on open/close state) $(“.trigger”).click(function(e){ $(this).toggleClass(“active”).next().slideToggle(“fast”); e.preventDefault();  }); //Expand/Collapse all – Chris it’s all you! 3.18.11 var flip = 0; $(“.expand”).click(function (f){ $(‘.trigger’).toggleClass(‘closed’, flip % 2 == 0).toggleClass(‘active’, flip % 2 == 0); $(“.toggle_container”).toggle( flip++ % 2 == 0 ); f.preventDefault(); }); //Switch to “Open” and “Close” table cells $(“tr.classDetails”).hide();        $(“td.triggerDetails,td.classTitle”).click(function(){        $(this).parent().next(“tr”).toggle(“fast”);

    • Anonymous

      Hi there Jon,

      Thanks for using offsreenjs. I will have a look at your code and see where I can help. Will be in touch soon.

      • Jon

        No, it never worked.  I eventually just created a new class the pushes everything offscreen instead of invoking jquery .hide.  Unfortunately this doesn’t work with .toggle.

        I’d rather just have something like offscreen.js that I can just add into pages so that they’re accessible.

        Not sure what else of my code you’d need to see.  Can you be more specific?

        • http://expansive-derivation.ossreleasefeed.com/ Schalk

          Hi there,

          Looked at my code, I need to do a little rewrite to support this use case. Keep an eye on the Github page for the update.

          Schalk

    • Anonymous

      I would need to see a little more of our code and maybe a pointer as to where things go wrong. Happy to help

    • Anonymous

      Hi there Jon,

      I have been swamped with other things, have you been able to use offscreen.js or, do you still have some questions? Please let me know.

Performance Optimization WordPress Plugins by W3 EDGE