Open Source Plugin Magnific Popup, A Truly Responsive Lightbox (For jQuery And Zepto.js)


A lightbox is one of those tools that work great on the desktop but often fail on small mobile devices. These days, finding a plugin that is responsive and that displays content right away is hard. For this reason, I created Magnific Popup1, an open-source lightbox plugin focused on performance.

In this article, I’ll share the techniques I employed in creating this plugin, techniques that can make your lightbox much faster and easier to use, whatever the device being used.

1. Speed

We might not be able to speed up the loading time of an image with this lightbox plugin, but we can create the perception of a faster loading time.

Progressive Loading of Images

The majority of lightbox plugins will fully preload an image before displaying it. This is done to figure out the original size of the image and to center it with JavaScript. Because Magnific Popup centers the content with CSS, we can avoid preloading and instead display the image right away to take advantage of progressive image loading. It will render and show the image while the data is being received.

You can speed this up even more by progressively rendering the JPEG2. It is rendered not from top to bottom, but from low quality to full quality, so the user can discern the image even faster. The type of rendering to use is strictly a matter of preference.

Progressive image loading3

CSS-Based Resizing

The CSS-only approach makes this lightbox extremely flexible. You can specify sizes in relative units such as ems, resize popups in media queries, and update popup content dynamically without having to worry about how it will be resized and centered. Try to avoid, or at least reduce, the number of resizing properties in a window’s resize event, because it will look much slower than resizing with just pure CSS.

Vertically centering an element with unknown dimensions is probably the most horrifying topic in CSS layout. The main goal for me was to prevent the size of the content area from dynamically updating the contents of the lightbox, and to make it work in IE 8 and above.

Following the principle of progressive enhancement, I decided to drop the vertical centering feature in IE 7 completely, because the only way to implement it was to use slow CSS expressions, which kill performance in old browsers. In contrast to modern browsers, the resolution of monitors on which IE 7 is being run is unusually easy to predict. We’d know that the user would be on an old PC, which typically has a resolution of somewhere between 800 × 600 and 1280 × 1024 pixels; so, we can just set a fixed margin from the top: .lightbox-image { margin-top: 10%; }. Alternatively, instead of opening the lightbox, you can just link directly to the content. In Magnific Popup, you can do this like so:

$('.popup-link').magnificPopup(function() {
  disableOn: function() {
    // Detect IE 7 or lower with your preferred method
    // and return false if you want to trigger the default action
    return isIE7 ? false: true;

Centering an HTML block

Here are the criteria:

  1. The size of the block is unknown and could be changed at any time.
  2. Block should be centered both horizontally and vertically.
  3. If the height of the popup is bigger than the viewport, then the scrollbar should appear, and the popup should automatically align to the top.

The one reliable technique to vertically center an element of unknown height that I’ve found uses a helper inline-block element with a setting of vertical-align: middle and height: 100%. Chris Coyier has written a superb article4 covering this technique. It works perfectly5 and meets all three requirements.

Centering an image with a caption

In addition to the requirements of an HTML block, the image should also meet these requirements:

  • It should fit the area both horizontally and vertically.
  • Its maximum height should equal the height of the original image.
  • The caption should be positioned directly below the image, and the text may flow up to two lines.

Here is the structure of the image’s lightbox:

<div class="container">
  <img src="image.jpg"/>
  <div class="description">Caption</div>

Implementing this just for an image and with support for IE 8 and above is not hard6. The problem comes when you try to position elements near to the image (such as a caption or related icon).

I was not able to find the solution for such a layout without using JavaScript, so I used a technique that applies a max-height to the image. Check the example on CodePen7 to see how it works.

Centering an iframe

Iframes in Magnific Popup are resized using the popular and effective technique introduced by Thierry Koblentz in his article “Creating Intrinsic Ratios for Video8” on A List Apart. All you need to do to force any element’s height to scale according to its width is to put it in a container and apply top padding as a percentage:

.iframe-container {
  width: 100%;
  height: 0;
  overflow: hidden;

  /* element ratio is 4:3 (3/4 * 100) */
  padding-top: 75%;

.iframe-container iframe {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;

Window height on iPhone

Mobile Safari on iPhone and iPod has a nasty bug: the height of the window is always reduced by the height of the navigation bar, whether the bar is present or not, so the popup won’t center correctly. When researching this issue, I found two ways to fix it:

  1. Just add 60 pixels to the height of the window. This solution doesn’t work with iOS 6’s full-screen mode, and it’s not future-friendly.
  2. Use window.innerHeight instead of document.documentElement.clientHeight, which returns the correct value, but only when the window is not zoomed in.

But I’ve figured out a third way to implement this (for more details on this technique, check my answer on Stack Overflow9):

var getIphoneWindowHeight = function() {
  // Get zoom level of mobile Safari
  // Such zoom detection might not work correctly on other platforms
  var zoomLevel = document.documentElement.clientWidth / window.innerWidth;

  // window.innerHeight returns height of the visible area.
  // We multiply it by zoom and get our real height.
  return window.innerHeight * zoomLevel;

If you know of a better way to position elements than what’s suggested here, we’d be hugely grateful to hear!

Preloading images in a gallery is essential and vastly increases browsing speed. The average modern browser accepts about six connections per host name10, whereas IE 7 accepts only two.

After the lightbox gallery is opened, it should start loading not one, but a group of images (the number of items in a group should depend on the size of the images and on how likely your visitor will navigate to the next one — test it!). Don’t even think about waiting to load the next image until after the current one has completely loaded; otherwise, it will reduce browsing speed significantly.

In Magnific Popup, you can set the number of images to preload before and after the current one, and these two values will automatically switch according to the direction in which the user is navigating.

Parallel loading
Inaccurate comparison between parallel and one-by-one loading.

High-DPI Display Support

Magnific Popup’s default controls are made with pure CSS, without any external resources. Thus, the controls are not only light, but also ready for high-DPI (i.e. Retina) displays. But in this section of the article I’d like to talk about serving images for displays with high pixel density.

It’s not hard to change the path, as the image in lightbox is loaded dynamically. The main problem is that the image should be scaled by half (for 2x device pixel ratio). It rises the question: how to get the image size via JavaScript without waiting until it is completely loaded to keep progressive loading?

While researching, I’ve found that the image naturalWidth is defined exactly after browser gets its size. So we just fire an interval that checks if the image object has defined naturalWidth, after it has it we scale the image by applying max-width that equals image.naturalWidth / window.devicePixelRatio. Here is simplified version of how the image loading is implemented:

var interval,
	onHasSize = function() {
		if(hasSize) return;

		// we ignore browsers that don't support naturalWidth
		var naturalWidth = img[0].naturalWidth;

		if(window.devicePixelRatio > 1 && naturalWidth > 0) {
			img.css('max-width', naturalWidth / window.devicePixelRatio);

		hasSize = true;
	onLoaded = function() {
	onError = function() {
	checkSize = function() {
		if(img[0].naturalWidth > 0) {
	img = $('<img />')
		.on('load', onLoaded)
		.on('error', onError)
		// hd-image.jpg is optimized for the current pixel density
		.attr('src', 'hd-image.jpg')

interval = setInterval(checkSize, 100);

There is a very common assumption that the image optimized for Retina display weighs two times or more than the normal one, but it’s not always true. As the image will be scaled down, JPEG quality can be reduced without any notable visual difference. From my experience, saving an image for 2x pixel ratio with 25% JPEG quality produces a good-looking result and the file size is only about 1.4x times larger than the regular one. Daan Jobsis has a great article12 that covers this technique.

What image to serve on what screen size is a topic for another article. I just want to emphasize that for many users mobile is the only way to access the internet. If you serve smaller images for mobile and there is a chance that user will need full-sized ones — provide an alternative way to get it.

Avoid Extra HTTP Requests for Controls

Preload all controls (i.e. the arrows, the closing icon, the preloader) before the popup has opened in order to load the actual content faster. This can be implemented in three ways:

  • Create all controls with CSS only (as Magnific Popup does by default).
  • Include the control graphics in the main CSS sprite of your website, or preload them with CSS before the popup has opened.
  • Use the Data URI scheme13 to embed base64-encoded images directly in the CSS (supported by IE 8 and above).

2. Accessibility

Content that has opened in the lightbox should be easily zoomable and scrollable, no matter what the device. The contents and controls of the popup should be accessible with the tab key for keyboard users.

Conditional Lightbox

This relatively new technique, introduced by Brad Frost14, disables the lightbox entirely on devices with a small screen, substituting an alternative more appropriate to mobile use. Here are some examples:

  • Open maps and videos as a separate page. Many mobile browsers will recognize such links and open a dedicated app that is much easier to use.
  • Simply open images in a new page for easier zooming and panning.
  • Open long text-based popups in a new page. In Magnific Popup, you can do this by adding the source of the popup inside a data attribute and linking to a mobile-friendly page in the href attribute. For example:
    <a href="separate-mobile-friendly-page.html" data-mfp-src="popup-content.html">Open popup</a>

    Here is the popup initialization (with an option that disables the popup and just opens the link when the window is narrower than 500 pixels):

    $('.popup-link').magnificPopup(function() {
      disableOn: function() {
        // Detect here whether you want to show the popup
        // return true if you want
        if($(window).width() < 500) {
          return false;
        return true;

Progressive Enhancement

Build the markup as if there were no JavaScript at all, and make the button that opens the content-oriented lightbox link to the content. Not only is this an enhancement for users without JavaScript, but it allows you to open the content in a new window, and it makes it completely SEO-friendly. Images will be perfectly indexed by search engines, and the anchor text will work as an alt attribute in the img tag.

<!-- Correct: -->
<a href="image.jpg">Description of the image</a>

<!-- Incorrect: -->
<a href="#" data-src="image.jpg">Description of the image</a>

<!-- Correct: -->
<a href="large-image.jpg">
  <img src="thumbail.jpg" alt="Description of the image" />

<!-- Incorrect: -->
<img src="thumbail.jpg" data-src="large-image.jpg" alt="Description of the image" />

Selectable Image

Users should be able to select and copy an image that is opened in a popup. This is one of the few ways to bookmark, save or share it.

Presently, fully overlaying a lightbox image with left and right navigation arrows is very common. The screenshots below show context menus above the image (the one on the right is overlaid with a transparent div, making any kind of interaction with the image virtually impossible).

accessible image15

position: fixed and overflow: scroll

Zooming a fixed-positioned element looks unnatural and confusing in the majority of mobile browsers. I suggest avoiding this property entirely on devices on which content is likely to be zoomed.

Exactly the same problem happens when you apply overflow: scroll to a popup’s wrapper. In this case, the problem is an unnatural scroll:

  • Once the user has reached the end of the scroll, the main window starts scrolling behind the popup.
  • Scrolling momentum is missing. On iOS 5+, this issue can be fixed with -webkit-overflow-scrolling: touch, but what about other devices?

Magnific Popup automatically disables these properties on mobile devices. Instead of using a fixed positon, it adds a huge dark overlay to the whole page that equals the height of the document and that positions the content area using position: absolute and with a top that equals the scrollTop of the window.

Keyboard Focus

Upon loading, the focus should be set on the popup itself or on the first input (if it is a form). When tabbing, focus should be confined to the lightbox and its controls. After the popup has closed, focus should return to its original location. This can be implemented very simply:

// Save current focused element
var lastFocusedElement = document.activeElement;

// Set focus on some element in lightbox

// After lightbox is closed, put it back

Roger Johansson’s great article “Lightboxes and Keyboard Accessibility16” discusses this topic and the overall keyboard accessibility of lightboxes.

Touch Swipe Support

The main problem with the swipe gesture on touch devices is that it requires blocking the default behavior of the touchmove event (e.preventDefault()), which blocks the zooming and panning gesture.

There are just two ways to enable swiping and zooming at once:

  1. Emulate zooming behavior with help of JavaScript touch events by changing the transform property of the content’s container. But this requires recalculating the content’s size, which breaks our CSS-based resizing technique and is not reliable when there are interactive elements such as iframes. Without a doubt, the library that implements such zooming and panning the best is iScroll17 by Matteo Spinelli.
  2. Don’t inhibit the default behavior of browser zooming when two touch pointers are detected. But finding the difference between panning and swiping is very hard because detection of the browser’s zoom level is unreliable.

By design, the main purpose of a lightbox is to show enlarged versions of images. So, we would conclude that natural zooming is much more important than swiping. That is why Magnific Popup does not have swiping support. Navigation between gallery items is implemented simply by tapping on arrows with a large hit area.

But if you really need swiping, there are some ways to implement it:

  • If you don’t need a dragging effect, use something like the TouchSwipe18 plugin.
  • If you need touch navigation with a dragging effect, open in the popup some slideshow plugin with touch support, such as FlexSlider19 or (mine) RoyalSlider20.
  • Use a conditional lightbox technique and create a separate mobile-friendly page that has just a list of stacked images.

If we disable the swiping gesture, we should at least make tap navigation faster. Mobile browsers wait about 300 milliseconds before firing a click event in case they detect a double-tap event. This can be fixed with the popular “fast click” technique, which fires a click on touchend. Ryan Fioravanti of Google has a complete guide21 on it.

3. User Interface

Some devices allow multiple types of input at once (such as touching and mousing). We conclude that we cannot require mouseovers (:hover) for important UI elements, and we cannot neglect mouse events if touch support is present.

Apple’s “Human Interface Guidelines” for iOS state22 that a comfortable size for a tappable UI element is at least 44 × 44 pixels. Assuming that any device with any screen size may have touch support, we’ll apply as large a hit area as possible by default.

Hit area for buttons23
Red rectangles show the hit area for the controls. (Image: JJ Harrison24)

Now let’s talk about actual implementation. First of all, buttons are rendered as <button> elements. Here is why:

  • A button can have a title attribute, which we can use to describe what the button does and its keyboard shortcut.
  • Buttons, unlike <a> elements, do not require hacks such as href="#" and href="javascript:void()". When the cursor hovers over a link, many browsers will show the contents of the href attribute in the bottom-left corner of the browser window; displaying javascript:void() would look quite illogical.
  • Buttons are the most semantically correct approach for such controls.

I recommend reading Nicholas Zakas’ article “You Can’t Create a Button25,” which offers a few more arguments in favor of using <button> elements.

The Close Button

The closing icon is just a math multiplication sign (×) rendered in Arial. I strongly recommend avoiding specifying multiple fonts (like font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif) because the position and size of the multiplication sign is very inconsistent across fonts.

The multiplication sign in different fonts: Arial, Georgia, Lucida Grande, Helvetica, Helvetica Neue, Roboto.

Next and Previous Buttons

The main requirement for default arrow icons is that they should be visible on any background. At first glance, the best option is to use Unicode triangles with shadow27, but it turns out that there are no equally sized left and right triangles.

The only remaining option is to use two nested triangles with different colors. Here is how that’s done:

.double-triangle {
  width: 90px;
  height: 110px;

.double-triangle:after {
  display: block;
  width: 0;
  height: 0;
  position: absolute;
  left: 0;
  top: 0;
  margin-top: 35px;
  margin-left: 35px;
.double-triangle:after {
  content: '';
  border-top: 12px solid transparent;
  border-bottom: 12px solid transparent;
  border-right: 12px solid black;
  top: 8px;
  left: 5px;
.double-triangle:before {
  content: '';
  border-top: 20px solid transparent;
  border-bottom: 20px solid transparent;
  border-right: 20px solid white;

Because of pseudo-elements, such an implementation will not work in IE 7. Navigation buttons are an important part of the UI, so I decided to write a small polyfill to make it work in IE 7, too. We just add two elements inside the button and apply the same styles as the :before and :after elements.

var button = $('<button class="double-triangle"></button>');
if(ie7) {
  button.append('<span class="ie-before"></span><span class="ie-after"></span>');

Here’s the CSS:

.dobule-triangle .ie-before {
  /* styles ... */


I am a big fan of custom cursors; they are a lovely addition to an interface. The zoom-in and zoom-out cursors make very clear that content may be enlarged or reduced, while the progress cursor is an excellent loading indicator of AJAX-based popups. Sadly, zoom cursors are still not supported by IE 10.

.zoom-in-cur {
   cursor: -webkit-zoom-in;
   cursor: -moz-zoom-in;
   cursor: zoom-in;
.zoom-out-cur {
   cursor: -webkit-zoom-out;
   cursor: -moz-zoom-out;
   cursor: zoom-out;
.progress-cur {
  cursor: progress;


The main rule of in and out animation for a lightbox is make sure you need it. If you do, at least keep it short (shorter than 300 milliseconds). Avoid animation if you anticipate a large image or a huge block of HTML in a lightbox.

Magnific Popup does not use JavaScript animation at all. Instead, it uses light and fast CSS transitions. Browsers that don’t support transitions are most likely slow, and thus JavaScript animation would look choppy in them.

One more important point: As I said before, Magnific Popup automatically disables position: fixed on mobile devices and creates a tall dark overlay on the page. Animating such a block might cause mobile devices to lag, so I suggest forcing position: fixed for the background and keeping position: absolute for the content itself.

And here’s a pro tip: To make your sliding animation a little smoother, Paul Irish suggests28 animating the translateY property, instead of top or margin-top.

In Summary

A responsive lightbox is not one that scales down proportionally when the screen’s size changes. It’s the one that provides fully accessible content, whatever the device.

No matter what script you use, it should support these standards:

  • Escape key to close the popup.
  • Left and right arrow keys to navigate the gallery.
  • Tab key to navigate the contents of the popup.
  • If there is a single image, the lightbox should close when any part of the image is clicked.
  • If it is a gallery, clicking on the current image should advance to the next image. Check out the discussion on UX Stack Exchange29 for more information.
  • A gallery should be clearly indicated: “1 of 10” counters, arrows, bullets, thumbnails, or any combination of these.

I hope this article and script will be useful to some. For more techniques, dig into the code of Magnific Popup in the GitHub repository36.

(al) (ea)


  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  30. 30
  31. 31
  32. 32
  33. 33
  34. 34
  35. 35
  36. 36

↑ Back to top Tweet itShare on Facebook

Dmitry Semenov is a professional frontend web developer and user experience designer based in Kiev, Ukraine. Follow Dmitry on Twitter or check his website.

  1. 1

    I want to use your plugin. What should i do if i have an html page that uses iframe and inside of this i want to put that popup? Is that possible?

  2. 52

    Great plugin. Love it. Thanks!

  3. 103

    Hi this plugin is great! I am wanting to be able to have the zoom gallery option but I would lie to be able to insert a pin it button into each image once they are inside the popup. So I would need to add an anchor inside. Is this possible? And can someone help me implement?

  4. 154
  5. 205

    Thanks for the awesome plugin, Dmitry!

    Is there a way I can set the image to the original size/dimensions when users view on desktop without shrinking it to fit in the browser? Thank you! :)

  6. 256

    I have quires using this Magnific Popup effects. .

    Its working fine online but when i paste all those codes to files and run on localhost nothing works. Could you please help me in this one.

  7. 307


    People in this comment chain ask for when the plugin will be ready, and then further down to most recent comments, people are thanking you for the plugin.

    I went to your site and the one at GitHub and it looks as if a plugin for WordPress isn’t available.

    Am I missing something? I saw a site that had your jquery awesomeness — wow. I am trying to teach myself how to install the various bits and pieces from your article – and I’ve done a fair number of backend stuff (though I don’t know js), but I can’t get it. Do you actually plan on releasing it? Also I noticed you’re from Ukraine, so perhaps you’re super extra busy? In any case, I hope you are safe.


  8. 409

    It’s a great plugin I use it often but I can’t figure out how to open it without href attribute. I tried $(‘#popup’).magnificPopup(‘open’) on link click but it seems it doesn’t work that way.

  9. 460

    I just googled “responsive lightbox” and it brought me to your plugin which does exactly what I wanted among many other cool stuff.
    I just love it, just as I love how you took the time to explain the why and how. That’s exactly how one should advertise any product to me :p

    Thank you, this is awesome.

  10. 511

    This is a great, clean popup, good work.

    I’m trying to figure out if there’s a way to make the popup open up to fill the entire browser window and then resize dynamically if the browser is resized. So essentially, a fullscreen popup, but to the browser size, not my whole monitor.

    Any help would be greatly appreciated.


  11. 562

    Will this also handle self hosted videos or only YouTube and Vemo?

  12. 613

    It’s simply fantastic. I’m currently using it on a few of the WordPress Free Templates that I’m working on

  13. 664

    Hello Everyone. Can i use Magnific popup in commercial projects. e.g projects that’ll be sold on theme repository with having to pay? is it freely licensed? Thanks

  14. 715

    Hi guys!

    I have run into an issue, however, and while I’m not sure whether it’s up to WP or the theme, I thought you might have a look at it. Character encoding in image captions, when brought up in lightbox, is broken. I enter them via media gallery interface and everything looks fine there. Caption below the image is also fine. But when clicked and the lightbox (modal) pops up, the encoding is all broken. It’s definitely not a font issue, as the characters are messed up in the source code as well. I Googled it around, but it seems to be related to the lightbox plugin, not WordPress itself.

    Here’s an example:

    What would be the easiest way to fix this?

  15. 766


    I have installed Lightbox plus colorbox plugin in my wordpress, but it doesn’t work for my photo gallery. Why is this so? Please explain. I am a new comer in this field (WordPress) I have never try WordPress before, because all this while I’ve being using and WordPress seems very new to me. Since I got need to use WordPress for my new marketing course, so here I’m.

    So far so good, I have been following Tyler Moore video Tutorial about WordPress, but I got stuck in lightbox plugin.

    Please help and reply ASAP.

    Kind regards,


  16. 817

    I just used it on my Bootstrap Website. Amazingly simple! Thanks a lot!

  17. 868

    I’m using this plugin on a new website i am building. I first time using the plugin. I think i’ve run into a bug with it. Here is the scenario i find myself in. Our website has multiple modules on a page. Its possible that two of these modules types may be on the same page. In the case where two modules are both carousels, its possible that i could end up having 2 carousels on the same that use the same class to trigger the lightbox.

    What i find is that the first carousel seems to work, but the second one does not fire. It just opens the larger image in a new browser window. Question is can i have two lightboxes on the same page that use the same class to trigger? How can i do that?

  18. 919

    To be more clear, i have some carousels on my webpage in modules. If i have 2 modules on the page that have the carousel with the lightbox , the second one doesnt work. When i click the thumbnail of the photo, it simply opens the larger image in a new browser window.

    I found an example in the documentation that I thought might help, but it doesnt appear to work for me.

    jQuery('#carousel-links').each(function() { // the containers for all your galleries
    delegate: 'a', // the selector for gallery item
    type: 'image',
    gallery: {

    Any thoughts?

    • 970

      nearly same problem , I would like to load several gallery without display via a repeater ACF field, and create a link for each of them to launch the sliders. I think it is possible with magnific popup (what is not?). I’m not far from the truth (each(function() is the way) but for now it does not work, I can not create a link for each gallery. did you resolve ?
      Another problem I would like a slider with thumbnails include to lightbox

  19. 1021

    Hi Dmitry, your script is great and was so easy to install. Thanks.
    But I’m having a little problem – when I resize the browser window in IE the overlay image shrinks to fit the window size until it’s tiny, which is good. But in Chrome it only shrinks a bit then scroll bars appear.
    Is this my site’s own css conflicting with magnific in some way?
    If you get a minute could you please take a look and give me some advice what I’m doing wrong? Thanks

  20. 1072

    Does not work properly in iOS. Lot of bugs created for this but no solution :(

  21. 1123

    What about RTL support?

  22. 1174

    Is there a way to make this “touchable”, I mean that on touchscreen (tablet and smartphone) you can swipe through the pictures?

  23. 1225

    You sir, are a genius.

  24. 1276

    It isn’t working on Samsung Android Phone Browser, Also Not Working on Chrome Desktop Browser on Mac after Resizing windows to small width of mobile and clicking link button to show youtube video. I am also using bootstrap3 on my site.


Leave a Comment

Yay! You've decided to leave a comment. That's fantastic! Please keep in mind that comments are moderated and rel="nofollow" is in use. So, please do not use a spammy keyword or a domain as your name, or else it will be deleted. Let's have a personal and meaningful conversation instead. Thanks for dropping by!

↑ Back to top