Ok, I’ve been at this web stuff for a LONG time.  Over the years I’ve probably read over a hundred web design tutorials on centering something horizontally and vertically in a browser window, so I’m wondering how it is I’ve NEVER seen this technique before?!  Thanks to Ilya Streltsyn who posted it over at Stack Overflow  😎

I’ve been working on a major overhaul of the integrated “lightbox” in my Ryuzine webapps.  I’ve never really been happy with it and the obvious solution would be to use one of the many, many lightbox scripts that are already out there right?  Except most of them use jQuery or some other framework and I’ve been trying to keep Ryuzine framework-free (I’ve never been convinced it’s worth loading an entire framework just to do a few things you could do with vanilla javascript if you just put your mind to writing the code.  Also there are potential licensing conflicts since Ryuzine is technically commercial and closed-source).  There are also “Pure CSS” solutions out there, but they tend to require a lot of extra HTML markup to make them work and the current lightbox in Ryuzine is already too markup heavy – I’m trying to simplify it (down to just an <a> tag with an href= direct link to media).  The most likely solution pretty much HAS to be a mix of JS and CSS – which is fine because it’s a webapp that already requires both of them to work anyway.

Horizontally centering stuff is super easy, of course, either set display to “block” and use “margin: 0 auto;” or “text-align: center;” on inline elements.  EASY!  It’s the VERTICAL centering that drives people batty.  Before now there were only two tried and true methods:

1. TABLE Layout  

Yeah, evil I know.  Thankfully you don’t have to use an *actual* table, you can use DIV tags set to display as table elements. The current lightbox is set up something like this:

.lighttable {
position: absolute;
height: 100%;
width: 100%;
display: table;
}
.lightcell {
position: absolute;
height: 100%;
width: 100%;
vertical-align: middle;
display: table-cell;
}
.pic {
position: relative;
display: table;
margin: 0 auto;
}

<DIV class="lightbox">
     <DIV class="lightcell">
          <DIV class="pic">

The problems with this are you have to set a specific height and width for anything inside the “pic” container, because it’s set to display as a table and collapse the border around the content. Also, if the browser height isn’t sufficient to display the content the top and bottom go out of view because a DIV set to display as a table, of course, will not obey “max-height: 100%;” and changing it to display as a “block” or “inline-block” (with “text-align:center;” on the parent “lightcell” container). But being inside a container set to display as a “table-cell” short-circuits using “max-height: 100%;” for “block” an “inline-block” too.  AARGH!

2. Absolute Box + Negative Margins

This one requires giving the “pic” container the specific sizing instead of its contents, setting the position to “absolute” and doing the “top: 50%;” “left” 50%;” trick with negative margins of one-half the set height and width.  This is a tried and true method for centering something both vertically and horizontally, except when you get a really short browser window the top and bottom of the lightbox go off the screen (just like they do with the “display: table;”) and you can’t just switch to “height: 100%” and “margin-top:-50%;” – which you *think* should work, but it doesn’t.  That’s likely to make the lightbox fly out of view up top.  AARGH!

3. Inline Boxes + Negative Margin Psuedo Element

I also tried the solution where you make the elements display as “inline-block” and use a 100% height psuedo element with a negative margin. It works, yes, but for very narrow responsive layouts (like a phone in portrait view) the centered element would disappear off the screen. A solution there is to constantly calculate and apply a pixel-specific negative margin to the pseudo element with javascript. Again, clumsy and I’d like to avoid polling the viewport size, calculating a negative margin, and applying an inline style via scripting.  AARGH!  (I’m sensing a theme here).

4. Flex Box + Align Items: Center

Ok, so this new thing came along called a “flex box” that sounds really promising – especially if you don’t need to support any legacy browsers, which I don’t anymore with the most recent versions of Ryuzine.  But this has its own set of problems.  Here was the CSS that I tried for that:

.lightbox {
position: absolute;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
height: 100%;
width: 100%;
-webkit-flex-align: center;
-ms-flex-align: center;
-webkit-align-items: center;
align-items: center;
}
.lightboxed {
position: relative;
display: block;
max-height: 100%;
max-width: 100%;
margin: 0 auto;
}
.pic {
position: relative;
display: block;
height: 100%;
width: 100%;
overflow: auto;
}
.pic img {
max-width: 100%;
max-height: 100%;
}

That would be fine if I was creating a single lightboxed container and writing the content into it, or if I didn’t intend to display more than one lightboxed item at a time. But I needed to have a lightbox for each item and the ability to animate it in/out, which still wasn’t a deal breaker so long as no more than one was actually displaying at a time. But I also wrote some new code to support named “gallery” sets of items – and that’s where I ran into irresolvable problems.

When you clicked on a gallery thumbnail obviously that one would set to display (off screen) and then animate in. You have to display before you can start the animation. Then if I put all the items in the gallery *after* the displayed item “on deck” and all of the items *before* it in the “out” position (but still set to “display: none;”) as soon as I’d set either the “back” or “next” item to “display: block;” both it and the current item would shrink to half their size! This is because flex box containers are designed to do that. If you have two visible elements inside it they adopt half the dimensions of the parent container. Display three and they’re each 1/3rd the size of the parent. Display four and they’re each a quarter…and so on.

One possible solution was to set “lightbox” to “display:block;” then put the “flex” display on each “lightboxed” container and make “pic” adopt the vertical centering. But this was getting really, really clumsy. There was also the problem in Firefox that, without an explicit height set, you could not get portrait orientation images to adopt the correct height (they’d just scale by width and overflow the “pic” container anyway).

5. “Four Corners” Absolute Box (aka “What Actually Works!”)

Ok, this is the solution I mentioned up top – the one I’m not sure how it is I’ve never heard of this one.  Here’s how it is set up:

.lightboxed {
position: absolute;
top: 0; left: 0;
right: 0; bottom: 0;
max-height: 100%;
max-width: 100%;
margin: auto;
}
.pic {
position: relative;
height: 100%;
width: 100%;
overflow: auto;
}
/* Media Queries for specific sizes */
@media screen and (min-width: 1280px)  {
	.lightboxed {
		width: 1280px;
		height: 720px;
	}
}
/* ...and so on... */

Now what that does is create the “lightboxed” container centered horizontally and vertically at the set sizes in the media queries (all of which are set 16:9 aspect ratios) – UNLESS there isn’t enough height, which then limits the height to 100%. YAY!

I said this is “what actually works” but that, of course, comes with the disclaimer “on modern browsers.”  I’ve tested this and found the following levels of support:

Unsupported – don’t work at all
Internet Explorer 8 and earlier
Firefox 4 and earlier
Safari 3.x, Safari Mobile iOS 5.

Partial Support – the media query resizing works, but the “max-height:100%” does not.
Android 2.3 Browser
Safari 4.x
Safari Mobile iOS 6.1

Full Support – works as intended.
Android 4.x Browser
Chrome for Android
Safari 5.x
Firefox 30+
Internet Explorer 9+
Chrome 30+

So, if you don’t need to support old versions of IE, Android, or iOS that nobody should still be using anyway, you’re probably ok to use this method.

If you need to make it work for “Partial Support” legacy browsers you can add additional media queries targeting heights in landscape orientation like this:

@media screen and (max-height: 480px) and (orientation: landscape) {
	.lightboxed {
		height: 100%;
	}
}

Or you can add javascript to check whether the “lightboxed” container will fit vertically and, if not, assign a specific value to it less than or equal to the viewport height, so there are ways to make it work for partial support legacy browsers too.