The perfect document overlay

A ‘document overlay’ is usually used to focus a user’s attention on a modal window and/or to make it impossible for them to use/navigate the document; something which you’d want to do if a form is processing or if you need the user to wait for something.

The easiest way to block user interaction with a page is to make a new element and stretch it over the top of all other elements thus disabling them (from the user’s point of view). It’ll usually be an absolutely positioned element with a very high z-index. Browser inconsistencies can make it tricky but overall it’s a simple and useful method of blocking user interaction.

new: documentOverlay

Since I’ve found myself having to use one of these overlays quite frequently I’ve decided to create a very reusable, entirely independent and cross-browser compatible solution.

I’m currently experimenting with a slightly different design pattern (A more object-orientated approach), which I hope I’ve done correctly… Before looking at the source it’s best to see what type of thing I’m talking about: (Click below)

Click here for a demo

Note: (Double click the overlay to remove it)

The source:

DOWNLOAD FILE (~3k)
The file and the preview below will remain updated with the latest version.

function documentOverlay() {
    // @version 0.12
    // @author James Padolsey
    // @info https://j11y.io/javascript/the-perfect-document-overlay/
 
    // Shortcut to current instance of object:
    var instance = this,
 
    // Cached body height:
    bodyHeight = (function(){
        return getDocDim('Height','min');    
    })();
 
    // CSS helper function:
    function css(el,o) {
        for (var i in o) { el.style[i] = o[i]; }
        return el;
    };
 
    // Document height/width getter:
    function getDocDim(prop,m){
        m = m || 'max';
        return Math[m](
            Math[m](document.body["scroll" + prop], document.documentElement["scroll" + prop]),
            Math[m](document.body["offset" + prop], document.documentElement["offset" + prop]),
            Math[m](document.body["client" + prop], document.documentElement["client" + prop])
	);
    }
 
    // get window height: (viewport):
    function getWinHeight() {
        return window.innerHeight ||
                (document.compatMode == "CSS1Compat" && document.documentElement.clientHeight || document.body.clientHeight);
    }
 
    // Public properties:
 
    // Expose CSS helper, for public usage:
    this.css = function(o){
        css(instance.element, o);
        return instance;
    };
 
    // The default duration is infinity:
    this.duration = null;
 
    // Creates and styles new div element:
    this.element = (function(){
        return css(document.createElement('div'),{
            width: '100%',
            height: getDocDim('Height') + 'px',
            position: 'absolute', zIndex: 999,
            left: 0, top: 0,
            cursor: 'wait'
        });
    })();
 
    // Resize cover when window is resized:
    window.onresize = function(){
 
        // No need to do anything if document['body'] is taller than viewport
        if(bodyHeight>getWinHeight()) { return; }
 
        // We need to hide it before showing
        // it again, due to scrollbar issue.
        instance.css({display: 'none'});
        setTimeout(function(){
            instance.css({
                height: getDocDim('Height') + 'px',
                display: 'block'
            });
        }, 10);
 
    };
 
    // Remove the element:
    this.remove = function(){
        this.element.parentNode && this.element.parentNode.removeChild(instance.element);
    };
 
    // Show element:
    this.show = function(){};
 
    // Event handling helper:
    this.on = function(what,handler){
        what.toLowerCase() === 'show' ? (instance.show = handler)
        : instance.element['on'+what] = handler;
        return instance;
    };
 
    // Begin:
    this.init = function(duration){
 
        // Overwrite duration if parameter is supplied:
        instance.duration = duration || instance.duration;
 
        // Inject overlay element into DOM:
        document.getElementsByTagName('body')[0].appendChild(instance.element);
 
        // Run show() (by default, an empty function):
        instance.show.call(instance.element,instance);
 
        // If a duration is supplied then remove element after
        // the specified amount of time:
        instance.duration && setTimeout(function(){instance.remove();}, instance.duration);
 
        // Return instance, for reference:
        return instance;
 
    };
 
}

Usage/Examples:

Instantiating the documentOverlay can either be a very simple call or a highly customizable one:

#1 Basic, uses defaults:

// (make it last for 3 seconds)
var myOverlay = new documentOverlay();
myOverlay.duration = 3000;
myOverlay.init();

#2 Customize CSS of overlay:

var myOverlay = new documentOverlay();
myOverlay.css({
    background: 'black',
    opacity: 0.5,
    filter: 'alpha(opacity=50)'
}).init();
 
// --------------------------------------
// Yes, it does support basic chaining!!!
// --------------------------------------

#3 Customize CSS and add an event:

var myOverlay =
    (new documentOverlay())
        .css({
            background: 'black',
            opacity: 0.5,
            filter: 'alpha(opacity=50)'
        })
        .on('click',function(){
            myOverlay.remove();
        })
        .init();

#4 Basic setup – remove when Esc key is pressed:

var myOverlay = (new documentOverlay()).init();
document.onkeyup = function(event){
    var keycode = window.event ? window.event.keyCode : event.which;
    // 27 === Esc. keycode
    if (keycode == 27) {
        myOverlay.remove();
    }
}

#5 Fade-in the overlay:

// (Make it last for 10 seconds)
(new documentOverlay())
    .css({
        background: 'black',
        // Set opacity to ZERO!
        opacity: 0,
        filter: 'alpha(opacity=0)'
    })
    .on('show', function(){
        // Let's use jQuery!
        $(this).animate({
            opacity: 0.5
        });
    })
    .init(10000); // < you can set the duration right here!

My main objective was to give maximum control to whoever uses this. As you can see from above it’s very easy to customize and it doesn’t require much polluting of the global (or any) namespace because it’s *mostly* encapsulated within itself. Because it allows chaining there’s no need to create any variables or containers. If you don’t like the idea of chaining then you can use a design pattern to which you’re accustomed; a classical example:

(function(){
 
    var myOverlay = new documentOverlay(),
        overlayElement = myOverlay.element;
 
    overlayElement.style.background = 'red';
    overlayElement.ondblclick = function(){
        alert('This is about to close!');
        myOverlay.remove();
    }
 
    myOverlay.init();
 
})();

Defaults

By default (if you don’t add any custom events/css) the overlay will have no background colour so will be totally invisible to the user. The cursor on the overlay is set to the ‘wait’ symbol – this is easily changeable using the css() method.

There is no default duration so the overlay will remain indefinitely. To remove it after an amount of time you can pass a millisecond parameter to the init() method, or you can access it directly as a public property of the constructor. In order to remove the overlay "manually" you’ll need to keep a reference to the actual DOM node (accessible through (new documentOverlay()).element) or you can use the remove() method as shown in examples #3 and #4 above.

Last words

I realize that this may be a bit too much for most people – I mean, it’s 3k and all it does it show an overlay, boring! But I hope someone finds it useful! :)

Thanks for reading! Please share your thoughts with me on Twitter. Have a great day!