How to Make Better Modal Windows with Lightbox

It started with LightboxJS by Lokesh Dhakar; he created a script you could use to overlay images on your page. Then ParticleTree modified the script to let you create a modal window. Then PJ Hyett posted a stripped down, simplified version of the script.

I liked all of those, and I think the lightbox/modal dialog window is a good UI widget to have. So I created my own version of the script. It’s more abstracted than any of the previous ones, and lets you create a modal window (or more than one) on a page really easily.

Download the source. It including the scripts, the css and the various images you’ll need. (02/13 NOTE: updated sources to fix some IE bugs).


Let’s start with an example

Here’s how you use it:

Include the scripts and the css in your page:




Then, create a hidden div somewhere on your page that holds the contents of your modal window, like this:


Then create a link that triggers the lightbox:


<a href="#" onclick="new Lightbox.base('mybox'); return false">Open the lightbox!</a>

That’s it. The script will automatically add everything else, including the little close icon in the upper left of the lightbox.

Options:

– you can choose to have the lightbox close when the user clicks anywhere else on the dimmed-out screen (I call this area the overlay). This is off by default. To turn it on, do this:

new Lightbox.base('mybox', { closeOnOverlayClick : true })

– you can specify the id of an external control to close the lightbox (in addition to the close-box icon), like this:

new Lightbox.base('mybox', { externalControl : 'cancel'})

- by default the lightbox is assigned the class of “lightbox”. You can change this if you want, as long as you remember to change the css accordingly.

new Lightbox.base('mybox', { lightboxClassName : 'myClassName'})

You can do as many of these as you like on a page. The script closes any that are open before opening a new one, so can only have one open at a time. The function that closes any open lightbox windows is Lightbox.hideAll(). You can call this anytime to close all open lightbox windows.

Feel free to reuse, distribute and modify this code as you wish. If you do something cool with it, I’d love to know. Remember that I whipped this up this morning while listening to A Prairie Home Companion, and also that I’m far from an expert in Javascript, so it is likely to have to kinks that need working out.

UPDATES:
Made some fixes thanks to some feedback in the comments. The script now uses no images, and should work as expected in IE6 (and 7, if you have it).

Get the updated files here.



62 Responses to “How to Make Better Modal Windows with Lightbox”  

  1. 1 Patrick Haney

    Your version has a major issue. When I scroll down the page, then click your demo link, the overlaying opacity background image doesn’t cover the entire browser window. It only covers what you’d see if you hadn’t scrolled the page at all. This is in Firefox 1.5 and Safari for OS X.

    In IE6 for Windows, the opacity background doesn’t display at all.

  2. 2 Bruno

    Hmm…good point. Actually it looks like PJ’s version has that problem too. It’s because the overlay div is set to 100% height and width, but that just takes into account the original viewable area of the window. I’ll see what I can do.

  3. 3 Bruno

    Fixed! Just needed to add some code to stretch the overlay all the way down. I’m still looking into the IE6 windows thing. I’ll post the new source later today.

  4. 4 Gnascher

    Your update says it should work as expected in ie6 & 7. Not sure if you put that up there before your last comment or not.

    Anyway, I get the popup window content, but not the background overlay in IE6 Win.

  5. 5 pmark

    Darn! I can confirm that the updated code does not work in IE 6.

  6. 6 Bruno

    Sorry guys, missed these comments in my moderation queue… I’ll take a look as soon as I can.

  7. 7 Carl Youngblood

    The example link doesn’t seem to be going anywhere.

  8. 8 Bruno

    Yep..some of the files got wiped out when I switched themes this week.

  9. 9 bruno

    Alright, I’ve got it partly figured out…something in K2’s stylesheet (the new Wordpress theme I’m using) is causing the lightbox div to center incorrectly on the page. Having trouble pinpointing exactly what, though…I know if I disable the entire K2 stylesheet the lightbox appears as it should. I’ll keep working on it…but the script should work fine on most other sites.

  10. 10 Brandon Baunach

    Thanks so much! I’m really looking forward to using it. I have one weird problem that I wondered if you knew how to overcome: I’m using a Yahoo! Map and I want to use the lightbox for users to fill out a form. The lightbox shows on top of the yahoo map in Firefox(which is what I want, but the lightbox overlay shows behind the map in IE which means you can’t read the document. You can find an example here: www.bittercyclist.com/maps/accmap.php

    One more thing… I found an article that basically says you can’t use lightbox with DIVs like the Yahoo Map in IE because IE has problems. See http://www.brainjar.com/css/positioning/default5.asp. This article explains that z-index just doesn’t work right with DIVs containing flash, for example. Any work around?

  11. 11 Bruno

    Hi Brandon, I think it has to do with the fact that the z-index css attribute has not effect on embedded Flash objects. So even though the overlay has a higher z-index than the Flash movie (in your case, the Yahoo Map), the Flash gets stacked above everything else.

    One way to get around this (I think), would be to modify lightbox.js to automatically look for any flash objects and hide them when the lightbox is activated (and then show them again when it’s closed).

    This is actually pretty easy to do, thanks to prototpye.js. It would look something like this:
    //first put all your flash in divs with a class name of your choice
    //in this case i'll use
    <div class="flash">

    flashObjects = document.getElementsByClassName('flash')
    flashObjects.each(function(object){
    Element.hide(object);
    })
    </div>

    This would hide them (just put it in the “initialize” function of Lightbox.base).

    To show them again, you’d do:

    flashObjects = document.getElementsByClassName('flash')
    flashObjects.each(function(object){
    Element.show(object);
    })

    And put that in the Lightbox.base.hideBox() function.

    Hope that helps.

  12. 12 Brandon Baunach

    Thanks so much! I’ll try it out at lunchtime! Unfortunately, I have to work now (non-web development).

  13. 13 Brandon Baunach

    It works, but the effect of the light box is totally diminished. I tested the lightbox with the Yahoo AJAX and Google versions, and there is no problems.

  14. 14 Matt

    Thank you very much for the script. I love how you have abstracted things to make it easier to use. But unfortunately I seem to have the same problem in IE6 on Windows XP. I get the pop up content, but the background overlay does not cover the page, it only seems to cover a very small portion of the page.

  15. 15 Corigo

    I’ve been struggling a little bit with Lightbox.js and have even seen it crash Mozilla completely. Your implementation looks interesting, I am testing it now and I see that in Opera 8.52 the background does not completely disappear when I close the lightbox overlay. I have either scroll the window to get back the native page colors, or change focus to another window and back again.

  16. 16 Matt

    I’ve been playing around with Bruno’s script in IE 6 and Firefox 1.5. Everything works fine in Firefox 1.5, but in IE6 the overlay does not cover the entire page and in some cases I don’t see it at all. The problem occurs in IE when you put a ‘lightbox’ div (div that you will popup using lightbox) within another div that does not cover the entire page. If you put the lightbox code right after the ‘’ tag and make sure that it’s not within any other div, then you shouldn’t have problems. Unfortunately I could not do that since I created web pages on the fly from a servlet and moving the divs to the top would require a lot of changes.
    Also in IE, selects aren’t hidden and display above the ‘lightbox’ div, so I fixed that by hiding all selects within the page then showing any selects within the ‘lightbox’ div.
    Another issue I saw in IE is you could scroll up and down the page and since the lightbox doesn’t cover the entire length of the page, I had to prevent the user from being able to do so. So I prevented that from using code I’ve seen in other lightbox implementations.
    So after a little bit of work and testing, I made the lightbox work on my system in IE 6 and Firefox 1.5 no matter if you put the ‘lightbox’ div within another div. I also check for CSS styles top, left, width and height and added code to work with percentage and/or pixel values. This is something I just hacked together, so there are definitely many enhancements, fixes one could make. The code is shown below.

    • Note – remember that this requires the prototype.js script
    • Note – I did test this in Opera 8.52 and the overlay does not completely cover the page and completely disappear.

    Lightbox code:

    
    /*--------------------------------------------------------------------------*/
    /*Lightbox
    *This is a script for creating modal dialog windows
    * (like the ones your operating system uses)
    *
    */
    
    var Lightbox = {
    
    yPos : 0,
    isIE : (navigator.userAgent.toLowerCase().indexOf('msie') != -1) ? true : false,
    
    /* hideAll - closes all open lightbox windows */
    hideAll: function(){
    lboxes = document.getElementsByClassName('lbox')
    lboxes.each(function(box){
    Element.hide(box)
    }
    )
    if ($('overlay')){
    Element.remove('overlay');
    }
    
    // if ie
    if(Lightbox.isIE) {
    Lightbox.setScroll(0,Lightbox.yPos);
    Lightbox.prepareIE("auto", "auto");
    
    // show all the document's selects since we are hiding the lightbox
    Lightbox.toggleSelects('visible');
    }
    },
    
    toggleSelects: function(visibility){
    selects = document.getElementsByTagName('select');
    for(i = 0; i < selects.length; i++) {
    selects[i].style.visibility = visibility;
    }
    },
    
    // Ie requires height to 100% and overflow hidden or else you can scroll down past the lightbox
    prepareIE: function(height, overflow){
    bod = document.getElementsByTagName('body')[0];
    bod.style.height = height;
    bod.style.overflow = overflow;
    
    htm = document.getElementsByTagName('html')[0];
    htm.style.height = height;
    htm.style.overflow = overflow;
    },
    
    // Taken from lightbox implementation found at http://www.huddletogether.com/projects/lightbox/
    getScroll: function(){
    if (self.pageYOffset) {
    this.yPos = self.pageYOffset;
    } else if (document.documentElement && document.documentElement.scrollTop){
    this.yPos = document.documentElement.scrollTop;
    } else if (document.body) {
    this.yPos = document.body.scrollTop;
    }
    },
    
    setScroll: function(x, y){
    window.scrollTo(x, y);
    }
    }
    
    Lightbox.base = Class.create();
    Lightbox.base.prototype = {
    
    initialize: function(element, options){
    //start by getting the current scroll position and hiding all lightboxes
    Lightbox.getScroll();
    Lightbox.hideAll();
    
    this.element = $(element);
    this.options = Object.extend({
    lightboxClassName : 'lightbox',
    closeOnOverlayClick : false,
    externalControl : false
    }, options || {} )
    
    //create the overlay
    new Insertion.Before(this.element, "<div id='overlay' style='display:none;'>");
    
    if(Lightbox.isIE) {
    // find out if the css style is a percentage or not
    var overlayWidthCSS = null;
    var overlayHeightCSS = null;
    var overlayTopCSS = null;
    var overlayLeftCSS = null;
    
    this.widthPercent = null;
    this.heightPercent = null;
    this.leftPercent = null;
    this.topPercent = null;
    
    try {
    overlayWidthCSS = Element.getStyle(overlay, "width").toString();
    
    var percIndex = overlayWidthCSS.indexOf("%");
    if(percIndex != -1 && percIndex  this.widthPercent = parseInt(overlayWidthCSS);
    }
    } catch(err) { }
    
    try {
    overlayHeightCSS = Element.getStyle(overlay, "height").toString();
    
    var percIndex = overlayHeightCSS.indexOf("%");
    if(percIndex != <del>1 && percIndex  overlayHeightCSS.length1) {
    this.heightPercent = parseInt(overlayHeightCSS);
    }
    } catch(err) { }
    
    try {
    overlayTopCSS = Element.getStyle(overlay, “top”).toString();
    
    var percIndex = overlayTopCSS.indexOf(“%”);
    if(percIndex != -1 && percIndex  overlayTopCSS.length - 1) {
    this.topPercent = parseInt(overlayTopCSS);
    } else {
    }
    } catch(err) { }
    
    try {
    overlayLeftCSS = Element.getStyle(overlay, "left").toString();
    
    var percIndex = overlayLeftCSS.indexOf("%");
    if(percIndex != <del>1 && percIndex  overlayLeftCSS.length1) {
    this.leftPercent = parseInt(overlayLeftCSS);
    } else {
    }
    } catch(err) { }
    }
    
    Element.addClassName(this.element, this.options.lightboxClassName)
    
    //also add a default lbox class to the lightbox div so we can find and close all lightboxes if we need to
    Element.addClassName(this.element, ‘lbox’)
    
    //Tip: make sure the path to the close.gif image below is correct for your setup
    closer = ‘Close’
    
    //insert the closer image into the div
    new Insertion.Top(this.element, closer);
    
    Event.observe($(‘close’), ‘click’, this.hideBox.bindAsEventListener(this) );
    
    if (this.options.closeOnOverlayClick){
    Event.observe($(‘overlay’), ‘click’, this.hideBox.bindAsEventListener(this) );
    }
    if (this.options.externalControl){
    Event.observe($(this.options.externalControl), ‘click’, this.hideBox.bindAsEventListener(this) );
    }
    
    this.showBox();
    },
    
    showBox : function(){
    // if ie
    if(Lightbox.isIE) {
    Lightbox.getScroll();
    Lightbox.prepareIE(‘100%’, ‘hidden’);
    Lightbox.setScroll(0,0);
    
    // hide all the document’s selects since you’re showing the lightbox
    Lightbox.toggleSelects(‘hidden’);
    
    // show the local selects
    this.toggleBoxSelects(‘visible’);
    
    var bodyDim = Element.getDimensions(document.getElementsByTagName(‘body’)[0]);
    var overlay = $(‘overlay’);
    var overlayDim = Element.getDimensions(overlay);
    var cumulativeOffset = Position.cumulativeOffset(overlay);
    var realOffset = Position.realOffset(overlay);
    
    if(this.widthPercent != null && !isNaN(this.widthPercent)) {
    overlay.style.width = bodyDim.width*this.widthPercent/100;
    }
    
    if(this.heightPercent != null && !isNaN(this.heightPercent)) {
    overlay.style.height = bodyDim.height*this.heightPercent/100;
    }
    
    if(this.leftPercent != null && !isNaN(this.leftPercent) && !isNaN(bodyDim.width)) {
    overlay.style.left = parseInt(bodyDim.width*this.leftPercent/100);
    } else {
    try {
    var left = parseInt(Element.getStyle(overlay,’left’));
    if(!isNaN(left))
    overlay.style.left = parseInt(-cumulativeOffset[0])+2*left; // value of body left is included in cumulativeOffset
    } catch(err) {}
    }
    
    if(this.topPercent != null && !isNaN(this.topPercent) && !isNaN(bodyDim.height)) {
    //alert(“Calculating new top:nthis.topPercent: “+ this.topPercent +
    //”nbodyDim.height: ” + bodyDim.height +
    //”ncumulativeOffset[1]: ” + cumulativeOffset[1]);
    //alert(“new top = ” + parseInt(bodyDim.height*this.topPercent/100));
    overlay.style.top = parseInt(bodyDim.height*this.topPercent/100);
    } else {
    try {
    var top = parseInt(Element.getStyle(overlay, ‘top’));
    //alert(“top=” + top + “, cumulativeOffset[1]=” + cumulativeOffset[1] + “, realOffset[1]=” + realOffset[1]);
    if(!isNaN(top)) {
    overlay.style.top = top; // value of body top is NOT included in cumulativeOffset
    }
    } catch(err) {}
    }
    }
    
    //show the overlay
    Element.show(‘overlay’);
    
    //center the lightbox
    this.center();
    
    //show the lightbox
    Element.show(this.element);
    return false;
    },
    
    toggleBoxSelects : function(visibility) {
    // Must have try catch because not all elements have the function getElementsByTagName
    try {
    var selects = this.element.getElementsByTagName(‘select’);
    for(i = 0; i < selects.length; i++) {
    //alert(“toggling visibility to ‘” + visibility + “’ of select ‘” + selects[i].name + “’, currently set at ‘” + selects[i].style.visibility + “’”);
    selects[i].style.visibility = visibility;
    }
    } catch (err) {}
    },
    
    hideBox : function(evt){
    // if ie
    if(Lightbox.isIE) {
    Lightbox.setScroll(0,Lightbox.yPos);
    Lightbox.prepareIE(“auto”, “auto”);
    
    // show all the document’s selects since we are hiding the lightbox
    Lightbox.toggleSelects(‘visible’);
    }
    
    Element.removeClassName(this.element, this.options.lightboxClassName)
    Element.hide(this.element);
    //remove the overlay element from the DOM completely
    Element.remove(‘overlay’);
    return false;
    },
    
    center : function(){
    var my_width  = 0;
    var my_height = 0;
    
    if ( typeof( window.innerWidth ) == ‘number’ ){
    my_width  = window.innerWidth;
    my_height = window.innerHeight;
    }else if ( document.documentElement &&
    ( document.documentElement.clientWidth ||
    document.documentElement.clientHeight ) ){
    my_width  = document.documentElement.clientWidth;
    my_height = document.documentElement.clientHeight;
    }
    else if ( document.body &&
    ( document.body.clientWidth || document.body.clientHeight ) ){
    my_width  = document.body.clientWidth;
    my_height = document.body.clientHeight;
    }
    
    this.element.style.position = ‘absolute’;
    this.element.style.zIndex   = 99;
    
    var scrollY = 0;
    
    if ( document.documentElement && document.documentElement.scrollTop ){
    scrollY = document.documentElement.scrollTop;
    }else if ( document.body && document.body.scrollTop){
    scrollY = document.body.scrollTop;
    }else if ( window.pageYOffset ){
    scrollY = window.pageYOffset;
    }else if ( window.scrollY ){
    scrollY = window.scrollY;
    }
    
    var elementDimensions = Element.getDimensions(this.element);
    
    var setX = ( my_width  – elementDimensions.width  )/ 2;
    var setY = ( my_height – elementDimensions.height )/ 2 + scrollY;
    
    setX = ( setX < 0 ) ? 0 : setX;
    setY = ( setY < 0 ) ? 0 : setY;
    
    this.element.style.left = setX + “px”;
    this.element.style.top  = setY + “px”;
    
    }
    }
    
    

Leave a Reply