Jack MooreTwitterGithub

jQuery Modal Tutorial


Jan 20th, 2012  »  15 comments

A basic modal is actually easy to code, and is sometimes more appropriate than a feature-full modal or lightbox plugin. This guide will walk through writing the necessary CSS and JavaScript.


See The Demo

Drafting the HTML

I like to create a rough draft just using HTML and CSS, leaving the scripting for last. I'll write the HTML first to structure the modal, even though the final HTML will be generated by the script.

<div id='overlay'></div>
<div id='modal'>
    <div id='content'>No JavaScript Yet!</div>
    <a href='#' id='close'>close</a>
</div>

This modal will have two root elements: #overlay and #modal. The overlay is going to be a semi-transparent layer that fills the visitor's viewport. This turns the modal into a focal point, and reduces focus for the rest of the page.

The CSS

#overlay {
    position: fixed; 
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: #000;
    opacity: 0.5;
    filter: alpha(opacity=50);
}

The overlay has a fixed position that is relative to the visitor's viewport. The 100% width and height refer to the viewport size, so we get complete coverage no matter what part of the page the visitor is actually viewing. Unfortunately, fixed position is not supported in iOS 4 or below. IE8 and IE7 do not support opacity, so an alpha filter is used instead.

#modal {
    position:absolute;
    background:url(tint20.png) 0 0 repeat;
    background:rgba(0,0,0,0.2);
    border-radius:14px;
    padding:8px;
}

#content {
    border-radius:8px;
    background:#fff;
    padding:20px;
}

The important thing here is that #modal will be absolutely positioned, the rest of the rules just provide a nice default appearance.

The RGBa background will give a black fill (0,0,0) at 20% opacity (0.2), which will overwrite the previous background property. IE8 and IE7 do not support RGBa, so they will continue to use the previous background property which uses a semi-transparent PNG background image.

I gave #modal a larger border-radius than #content. This made a border that is natural looking and consistent in thickness. IE8 and IE7 do not support border-radius and will instead display the normal square corners.

#close {
    position:absolute;
    background:url(close.png) 0 0 no-repeat;
    width:24px;
    height:27px;
    display:block;
    text-indent:-9999px;
    top:-7px;
    right:-7px;
}

The close button is absolutely positioned relative #modal, since #modal is it's closest ancestor element with positioning. Since I used an anchor element for the close button, it's default display of inline must be changed to block in order to apply a fixed width and height to it. Text-indent moves the anchor's text offscreen so that it will not overlay the background image.

See The Work In Progress

The Script

I would like the modal to handle the following things:

Let's stump out the function and variable names:

var modal = (function(){
    var 
    method = {},
    $overlay,
    $modal,
    $content,
    $close;

    // Center the modal in the viewport
    method.center = function () {};

    // Open the modal
    method.open = function (settings) {};

    // Close the modal
    method.close = function () {};

    return method;
}());

Here we have a function that is immediately executed and returns an object containing the methods needed to control the modal. This object is assigned to the modal variable. The rest of the variables are references to their corresponding elements in the document. Now lets fill in the missing code.

Append The HTML

$overlay = $('<div id="overlay"></div>');
$modal = $('<div id="modal"></div>');
$content = $('<div id="content"></div>');
$close = $('<a id="close" href="#">close</a>');

$modal.hide();
$overlay.hide();
$modal.append($content, $close);

$(document).ready(function(){
    $('body').append($overlay, $modal);
});

This generates the same HTML structure used earlier in the guide, now created and added to the document by our script.

Center Method

method.center = function () {
    var top, left;

    top = Math.max($(window).height() - $modal.outerHeight(), 0) / 2;
    left = Math.max($(window).width() - $modal.outerWidth(), 0) / 2;

    $modal.css({
        top:top + $(window).scrollTop(), 
        left:left + $(window).scrollLeft()
    });
};

Here we balance the distance between each side of the viewport and each side of the modal. jQuery's .outerWidth() and .outerHeight() are used because borders and padding should also be factored in. I used Math.max() so that I could be sure that no value returned would be less than 0. This means that if the modal is larger than the visitor's viewport, it will be positioned in the top left of the viewport rather running beyond that where it could be potentially being inaccessible. $(window).scrollTop() returns the position of the vertical scrollbar, which is the distance from the top of the document to the top of the viewport. This needs to be added to account for any scrolling that has happened.

Open Method

method.open = function (settings) {
    $content.append(settings.content);

    $modal.css({
        width: settings.width || 'auto', 
        height: settings.height || 'auto'
    })

    method.center();

    $(window).bind('resize.modal', method.center);

    $modal.show();
    $overlay.show();
};

The open method accepts an object of our settings. It can contain three properties: content, width, height. Content can be text, HTML, or an element/jQuery object. We append the data from settings.content to #content, and give #modal a width and height if either was specified in the settings object. Method.center() is called to center the modal in the viewport and is bound to the window's resize event. This will cause the modal to re-center itself whenever the viewport changes size. I made use of jQuery's event namespacing so that later, when we unbind the event, we can make sure not to disturb any other resize events that other scripts may have bound to the window. The last thing we do is reveal #modal and #overlay.

Close Method

method.close = function () {
    $modal.hide();
    $overlay.hide();
    $content.empty();
    $(window).unbind('resize.modal');
};

Here we hide the #modal and the #overlay and remove the content. Then we unbind the resize event because there is no need for it when the modal is closed. Outside of the method

Close Button

$close.click(function(e){
    e.preventDefault();
    method.close();
});

The default action of the click event is canceled (the browser from redirecting to the link's href) by calling e.preventDefault(), then the close method is called.

Examples Calling The Modal

modal.open({content: "Howdy"});

modal.open({content: "<p>Howdy</p>"});

modal.open({content: $("<p>Howdy</p>"), width: "500px", height: "200px"});

// Ajax
$.get('ajax.html', function(data){
    modal.open({content: data});
});

See The Demo


Hey You

Follow me on Twitter, Github, or RSS. Why should you? I'm meticulous and have a lot of free time. Sometimes good things come out of that.

Comments

Damien Wilson4 months agoReply
Very nice! I like your style of code authoring - really clean and easy to read :o)
Damien Wilson4 months agoReply
Hi jack, I made a slight addition to method.center. This will account for hidden space above the viewport. Really helpful if the call to Modal is below the fold.
// Center the modal in the viewport
method.center = function () {
    var top, left;
    top = Math.max($(window).height() - $modal.outerHeight(), 0) / 2;
    left = Math.max($(window).width() - $modal.outerWidth(), 0) / 2;
    $modal.css({top:top+$(window).scrollTop(), left:left});
};
Thanks again!
Jack4 months agoReply
Doh, I meant to cover that. I updated the code. Thanks!
Richard4 months agoReply
What a fantastic tutorial, many thanks.
Dave4 months agoReply
Very easy to follow, thank you. Is there a way to delay the modal window, say a few seconds after the page loads? Also, how about using the ESC key to exit the modal window?
Jack4 months agoReply
Of course, what you are asking isn't anything that would be specific to the code given here. Use setTimeout to set a timer, and bind to the keydown event detect when the esc key is pressed.
Khaylaan3 months agoReply
Hi Jack, Very interesting tutorial, thanks ;)
Patrick3 months agoReply
Thanks, this is really great! Quick question, what's the most elegant way of keeping this box centered in the viewport if the user scrolls around?
Jack3 months agoReply
By using fixed positioning instead of absolute positioning. You would replace #modal { position:absolute; } with #modal { positioned:fixed; } and you would no longer add the $(window).scrollTop() and $(window).scrollLeft() values.
Tom Carnevale3 months agoReply
Nice clean approach. Thanks!
Thanh2 months agoReply
Hi Jack, If your html body is more content, the modal and html body's content are heigher than navigator viewport. Is there a way for me to disable scroll bar on the right of navigator, only permit navigator use this scroll bar to scroll modal, and the body content is not move?
Jack2 months agoReply
Set the overflow on your HTML element to hidden when the modal opens, reset it when it closes.
$('html').css({overflow:'hidden'});
cao7113a month agoReply
good tutorial for me, thanks a lot!
Sama month agoReply
I want to make homer shows up after I click. How would I do that Jack? Thank you.
Jung Wana month agoReply
Hello, nice tutorial I want to ask.. how to make a different color of some modal in one css. for example modal1=blue, modal2=green, modal3=red but one css and use code overlay and color in html maybe like this: include jquery.ui.all.css <script> $(function() { $( "#dialog:ui-dialog" ).dialog( "destroy" ); $( "#dialog-modal" ).dialog({ //sample color : red color : blue }); }); </script> thank you

Leave a Comment