FrameWarp - jQuery plugin for displaying pages in a neat overlay
While working on an exciting new web app, I found that I needed a way to show certain pages in an overlay window. This comes handy if you want to reuse something like a sharing or a settings page in different screens of your app. Instead of hacking together something that barely got the job done, I decided to take the time, do it properly and share you with you.
Of course, there is the option of using one of the numerous lightbox plugins to do this, but the plugin we will be creating in this tutorial has a lot of advantages over a generic lightbox script:
- Lightweight - it is created specifically for showing pages, not images;
- No UI, so the page feels like a dialog window;
- The page can close itself, and can also send messages to the parent window;
- Can optionally use a cache for faster subsequent page loads;
- Uses a neat CSS animation with a JavaScript fallback.
Great! Now let's get started.
The Idea
When a link or button is clicked, our plugin, dubbed FrameWarp, will detect the coordinates of that element, and trigger a CSS animation of an expanding polygon moving to the center of the window. The plugin will then load an Iframe pointing to the URL we want to show. If the page is from the same origin as the current site, FrameWarp will also add two useful methods to the iframe - one for hiding it, and another one for sending a message to the parent.
We will be using the jQuery++ collection of tools for jQuery, which converts the library's animate() method to use CSS3 transitions on browsers that support them. This makes constructing complex CSS animations quite easy.
The Animation
As they say, a fiddle is worth 1000 words. So here is the animation in action (hit the Result tab):
The trick here is that we are animating the border properties of the element and the width, while the height remains 0. The left and right borders are set to transparent in the CSS of the plugin. Alternatively, you could do it with 3D CSS transforms, but it wouldn't work in older browsers.
The Plugin
Now to write the plugin. We are going to wrap our code in an anonymous function so that it is isolated from the rest of the page. In effect all the variables and helper functions you can see below are private and accessible only to our plugin.
assets/framewarp/framewarp.js
(function($){ // Private varialble deffinitions var body = $('body'), win = $(window), popup, popupBG; var frameCache = {}; var frameCacheDiv = $('<div class="frameCacheDiv">').appendTo('body'); var currentIframe; $.fn.frameWarp = function(settings){ // The main code of the plugin will go here }; // Helper Functions function hide(){ // Here we will remove the popup and dark background from the page } function setUpAPI(iframe, settings){ // In this function, we will make two API methods available to the frame, // if it the page is from the same domain. } function sameOrigin(url){ // Here we will determine whether the page is from the same domain } function getOrigin(url){ // A helper function for generating an origin string // of the type: https://www.google.com // This includes the protocol and host. } })(jQuery);
The plugin creates a div with a frameCacheDiv class name. It is going to hold the iframes we are adding to the page. Two more divs are added to the page by the plugin - .popup and .popupBG, which we will discuss in a moment. Now let's inspect the helper functions.
function hide(){ if(currentIframe){ currentIframe.hide(); currentIframe = null; } popupBG.remove(); popup.remove(); } function setUpAPI(iframe, settings){ if(sameOrigin(settings.url)){ // Exposing a minimal API to the iframe iframe[0].contentWindow.frameWarp = { hide: hide, sendMessage:function(param){ return settings.onMessage(param); } }; } } function sameOrigin(url){ // Compare whether the url belongs to the // local site or is remote return (getOrigin(url) == getOrigin(location.href)); } function getOrigin(url){ // Using an anchor element to // parse the URL var a = document.createElement('a'); a.href = url; return a.protocol+'//'+a.hostname; }
Browsers implement a security feature called "same origin policy" that limits a web site from accessing the DOM of another. For this reason, we have a helper function that compares the URL of the iframe with the address of the current page. Only when both the domain and the protocol match, will the plugin attempt to access the DOM of the iframe and add the API methods for sending messages and hiding.
Now we are ready to write the actual frameWarp plugin!
$.fn.frameWarp = function(settings){ // Supplying default settings settings = $.extend({ cache: true, url: '', width:600, height:500, closeOnBackgroundClick: true, onMessage:function(){}, onShow:function(){} }, settings); this.on('click',function(e){ e.preventDefault(); var elem = $(this), offset = elem.offset(); // The center of the button var buttonCenter = { x: offset.left - win.scrollLeft() + elem.outerWidth()/2, y: offset.top - win.scrollTop() + elem.outerHeight()/2 }; // The center of the window var windowCenter = { x: win.width()/2, y: win.height()/2 }; // If no URL is specified, use the href attribute. // This is useful for progressively enhancing links. if(!settings.url && elem.attr('href')){ settings.url = elem.attr('href'); } // The dark background popupBG = $('<div>',{'class':'popupBG'}).appendTo(body); popupBG.click(function(){ if(settings.closeOnBackgroundClick){ hide(); } }).animate({ // jQuery++ CSS3 animation 'opacity':1 },400); // The popup popup = $('<div>').addClass('popup').css({ width : 0, height : 0, top : buttonCenter.y, left : buttonCenter.x - 35 }); // Append it to the page, and trigger a CSS3 animation popup.appendTo(body).animate({ 'width' : settings.width, 'top' : windowCenter.y - settings.height/2, 'left' : windowCenter.x - settings.width/2, 'border-top-width' : settings.height, 'border-right-width' : 0, 'border-left-width' : 0 },200,function(){ popup.addClass('loading').css({ 'width': settings.width, 'height': settings.height }); var iframe; // If this iframe already exists in the cache if(settings.cache && settings.url in frameCache){ iframe = frameCache[settings.url].show(); } else{ iframe = $('<iframe>',{ 'src' : settings.url, 'css' : { 'width' : settings.width, 'height' : settings.height, } }); // If the cache is enabled, add the frame to it if(settings.cache){ frameCache[settings.url] = iframe; iframe.data('cached',true); settings.onShow(); } else{ // remove non-cached iframes frameCacheDiv.find('iframe').each(function(){ var f = $(this); if(!f.data('cached')){ f.remove(); } }); } iframe.ready(function(){ frameCacheDiv.append(iframe); setUpAPI(iframe, settings); settings.onShow(); }); } currentIframe = iframe; }); }); return this; };
As I mentioned in the opening section, we are using jQuery++ to enhance jQuery's animate() function to support CSS3 animations. This way we don't have to write tons of CSS, and we also achieve full backwards compatibility, as the new animate() method will fall back to the old if the browser has not support for CSS animations.
Once the first animation is complete, we add the loading class to the .popup div. The new class adds an animated preloader gif to the popup and a soft box-shadow, as you can see by inspecting assets/framewarp/framewarp.css.
Using the plugin
To use the plugin, include assets/framewarp/framewarp.css to the head of your page, and assets/framewarp/framewarp.js after your copy of the jQuery library.
After this, all that is left is to initialize the plugin. As an example, here is the code that drives our demo page:
assets/js/script.s
$(function(){ // If no url property is passed, the // href attribute will be used $('#b1').frameWarp(); $('#b2').frameWarp({ onMessage: function(msg){ $('#messages').append('Message Received: '+ msg+' '); } }); // Cache is enabled by default $('#b3').frameWarp({ url : 'http://www.cnn.com/' }); // Disable caching $('#b4').frameWarp({ url : 'http://www.cnn.com/', cache:false }); });
Done!
With this the plugin is complete! You can use it to enhance your web app and reuse certain parts of it without writing extra code. I would love to hear your suggestions or thoughts in the comment section below.
Bootstrap Studio
The revolutionary web design tool for creating responsive websites and apps.
Learn more
Exactly what i was looking for!
Thank you!
Happy to help!
I am having problems getting the popup box to close when I click the 'Close window' button - even in the files you provided.
Any idea how I can ensure it closes? It closes if I click outside of the pop up but not the button.
Does it throw any errors in the console?
I have the same problem. Chrome js console throws "Uncaught ReferenceError: framewarp is not defined" error.
OMG i have the same problem! the demo on this website is working fine with the close button, but after I download it, the close button stopped working...
I know this sounds tacky, but I need this exactly but on page load and on click to go away.
Where could I add this code? At the top of the plug in or inside the hide function?
You can do it like this:
After you initialize the plugin, say on the #b1 element, trigger a click on it with
$('#b1').click()
. This will show up the window immediately on page load.In the page itself, say frame.html, add this code:
$('body').click(function(){ framewarp.hide() });
which will hide the popup when you click anywhere on the page.Wait as I am not so agile with jQuery (yet :D).
You are saying I should initialize the actual element to load as in $('#b1').click... how can it show immediately on page load? Can't it be $('#b1').frameWarp (on load)?. The snippet you give me is waiting for a click (#b1) for it to load.
Well, the trigger makes the click for you, just need to make this code run when the page is load (ej. put it just before the closing 'body' tag and after the jquery):
// This trigger a click, the same as $("#h1").trigger("click")
$("#h1").click();
// Now listen when you make click in anywhere within the 'body'
// and hides the frameWarp that is show in the lines above.
$("body").click(function(){
framewarp.hide();
});
Simple as pie! =)
Good luck...
$('#b1').click() Will simulate the click. It's not waiting for it.
Thanks mate. I didn't see that the options are set in the plug in regarding width/height and I needed the damn thing responsive :(
Now This is what i was looking for a long time. I think showing a demo to a user is going to be awesome. Once again Thanks Martin!!!
Thank you so much! This tutorial is awesome!
And does it have cross domain when i close an iframe?
Srr bout my english.
Cool effects,Martin! My idea is to not close the popup automatically when the mouse button is clicked outside of the popup, but rather have an image at the top right hand corner of the popup and include the exit function on it's click event handler. I guess this could make a good topic of experimentation on a nice warm Sunday.
One thing that was making me crazy was that I cannot close the iFrame on example1 from the blue button on IE.
After some tries, I could only solve it by using this code:
«
<a href="#" class="button2" id="close" onclick="$('.popupBG',parent.document).trigger('click');">Close Window</a>
»
and putting jQuery on page.
Now it worked for IE and the other browsers.
Great work man!!
But i have a a question.. is it possible to implement your code on "jQuery 1.4.2" ?
i'm getting - " Uncaught TypeError: Object #<Object> has no method 'on' "
in your framewarp.js file when switching to an older version then jQuery 1.7.1.
I know it's something to do with the - onclick function but i'm lost here :(
is there a solution for that? any help will be more then appreciated...
The "on" and "off" methods were introduced in jQuery 1.7. You can replace them with bind/unbind for it to work with jQuery 1.4.
Thanks for this useful plugin. Lightweight and tiny. Best regards.
I love this plugin but I'm having one issue. I have multiple links on the same page that open the frame with the same link but different parameters. After the first link is clicked and then closed, any links opened after that open the first link clicked instead.
Is there any way to clear frame after it is closed?
You can pass the cache:false parameter when initializing the plugin. This will reload the iframe every time.
I'm using b4 which uses cache:false however I still get the same results.
Also set
settings = $.extend({
cache: false,
in framewarp.js. Still no luck.
I found the problem but still not sure how to fix it. In script.js I changed:
$('#b4').frameWarp({
to
$('.b4').frameWarp({
so that I could use multiple links in class="" because IDs need to be unique. This seems to be causing all links to show up as the first one clicked.
Is there a way I can manipulate this to get it to work on multiple links?
Found it!!
When you delete the if-condition in the framewarp.js:
if(!settings.url && elem.attr('href')){
settings.url = elem.attr('href');
}
it will not save the last url.
And i changed in the hide-function down at the document:
currentIframe.hide();
to: currentIframe.remove();
So the frame is really closed. Had the Problem, that after hide you still could here the videostuff i had in the link.
Sorry for my bad english...
Thanks for your tutorial, it's useful.
This isn't working in IE9.
The window won't close using the close button, and messages are never sent.
It works fine in FF.
This plugin works great! However, is there a way to make it close the popup window by clicking on the window itself instead of the background?
Hi Martin, this plugin works great, but long page with scrollbar (e.g. CNN page) within the frame cannot be scrolled down on iOS device such as iPad. Is there any solution?
Wow now that is nice. Thanks Martin, it will come in very handy :)
Awesum Man ... Love it ... Will be using with wp-subscribe plugin of wordpress.
Thanks for the script man, its awesome!!
But I'm get stucked with a thing...
Always work's fine... until I reload the frame.
After frame reload (like a form action="#" on it) the buttons doesnt work anymore for hide the frame or sendmessage to caller.
how can it still works after a frame reload?
another thing:
after frame hide, the back page scrool down a bit.... why??
thanks a lot!!
(sorry about my poor english)
Best regards,
Júnior (from Brazil)
in fact, after frame reload, it works exactly like I was comment the line setUpAPI(iframe, settings); on framewarp.js:
How can I solve this "problem" ???
What's the jquerypp for in the assets/js folder?
Is there a way to stop the background from scrolling when the popup is active?
Hi Martin Angelov, I use FrameWarp to popup a overly page, there is a button in the page which makes ajax get call, like below
/$.get("ModalAddEmail.aspx?email=" + email, AddEmailCallback);
However, when the js code hit this ajax get, the popup window just expand to take the whole screen, any idea?
Hi,
The plugin is great for all browsers but IE.
Well, actually the close button problem I solved with help from Rui Figueiredo comment.
But in IE I can't make the border dissapear, this annoyis me mostly.
Also I have another problem, if I load a second page in the same popup, I can't close it. I tryed onclick="$('.popupBG',parent.parent.document).trigger('click')" but this doesn't seem to cut it. However I am not good at programming as I am a designer, so I am using common sense, not knowlegde when I change the code. :)
Soooo yeah, any help would be greately appreciated!
Great plugin that I am using extensively. There is one problem however - whenever I load another page in the frame, frameWarp.sendMessage() calls are no more available. Any chance to maintain this function?
Hi,
I had the problem, that in IE9 the the sendMessage method did not work. To solve the issue I found a small workaround:
I added a method "takeOver(msg)" in the calling website (the website where the iframe is attached). If the frameWarp.sendMessage fails with an exception, I call this method via window.parent.takeOver(...)
Afterwards I hide the iFrame with the hack Rui Figueiredo already posted...
This is fantastic, Martin!
Just what I was looking for.
Unfortunately, I couldn't firgure out how to get this into the script...
Thank you.
Appreciate your work, but unfortunately can't use it.
>> Close button doesn't work.
>> On smartphone the overlay is just nuts.
Great would have been if it just opens in a new window on mobile. But the way it's now, can't be used. Sad and unfortunately my Java Script skills are not good enough to implement it myself.