Upgrade Your Drupal Skills

We trained 1,000+ Drupal Developers over the last decade.

See Advanced Courses NAH, I know Enough

Responsive Iframes — yes it is possible

Parent Feed: 
Global Zero
Responsive Iframes — yes it is possible

The Web has always had a love-hate relationship with 3rd-party content.  Whether that external content is self-contained functionality brought into a website via SaaS, or to add a donation form to your website in a way that reduces your PCI Requirements, or to possibly connect your disparate web properties together.  Back in the prehistoric days before responsive web development (a.k.a. two years ago) a common way to insert 3rd-party content was with an iframe. Something like:
<iframe src="http://example.com/widget" width="300" height="500" /></iframe>

The challenge with responsive design is the hard-coded width and height.  You might be able to choose reasonable settings for a desktop viewport, but a smaller mobile viewport may require something narrower and longer (or sometimes even wider and shorter).  And as the user interacts with the iframe (e.g. submits a form) the content may shrink and grow. Iframes quickly become untenable in a responsive world.  But often there is no alternative (Salsa, Blackbaud NetCommunity, Wufoo and many other CRM/donation/form platforms do not have adequate JavaScript APIs).

However there is a solution. We can use JavaScript to communicate between the iframe and the top-level page. With one slight problem — due to the same-origin policy an iframe at a different domain can't use JavaScript to directly communicate with the parent page.  Instead we have to use the postMessage() method to do cross-window messaging. This is a bit awkward, but there's good news at the end of this blog post.

postMessage()

Let's start with a quick tutorial on how postMessage() works. We'll set up a line of communication from the parent to the iframe and then send CSS and JavaScript links that can be used to style and otherwise manipulate the content.

// Parent
var iframe = document.getElementById('the-iframe');
var tags = '<script type="text/javascript" src="https://example.com/script.js"></script>';
tags += '<link type="text/css" rel="stylesheet" href="https://example.com/styles.css" />';
// Send a message to the iframe only if it contains content from
// https://frame.example.net.
iframe.contentWindow.postMessage(tags, 'https://frame.example.net');

Before we get too far there is one caveat: You must be able to have a small snippet of JavaScript in the iframe (e.g. If your donation form's configuration tool allows you to insert JavaScript). Unfortunately many 3rd-party tools don't allow this. Other JavaScript we can send over to the iframe via postMessage(), but we need to start with something.

// Iframe
function listener(event) {
  // Only listen to events from the top-level page.
  if (event.origin !== "http://example.com" &&
      event.origin !== "https://example.com" &&
      event.origin !== "http://dev.example.com" &&
      event.origin !== "https://dev.example.com"
  ) {
    return;
  }
  jQuery('head').append(event.data);
}
// We attach this differently for older versions of IE.
if (window.addEventListener) {
  addEventListener("message", listener, false);
}
else {
  attachEvent("onmessage", listener);
}

The cross-window messaging security model can be two-sided. The sender ensures that the data is only sent to trusted domains and the receiver checks that the message came from trusted domains.

Resizing the iframe

To resize the iframe we need to communicate in the other direction — the iframe needs to tell the parent how big it is.

// Iframe.
/**
* Send a message to the parent frame to resize this iframe.
*/
var resizeParentFrame = function() {
  var docHeight = jQuery('body').height();
  if (docHeight != parentDocHeight) {
    parentDocHeight = docHeight;
    // There is no need to filter this to specific domains, since the data is
    // not sensitive, so just send it everywhere.
    window.parent.postMessage(parentDocHeight, "*");
  }
};

// Check frequently to see if we need to resize again.
setInterval(resizeParentFrame, 250);

// Parent.
var listener = function (event, iframe) {
  // Ignore messages from other iframes or windows.
  if (event.origin !== 'https://frames.example.net') {
    return;
  }
  // If we get an integer, that is a request to resize the window
  var intRegex = /^\d+$/;
  if (intRegex.test(event.data)) {
    // Math.round() is important to make sure Internet Explorer
    // treats this as an integer rather than a string (especially
    // important if the code below were to add a value to the
    // event.data, although currently we aren't doing that).
    iframe.object.height(Math.round(event.data));
  }
}
// Setup the listener.
if (window.addEventListener) {
  addEventListener("message", listener, false);
}
else {
  attachEvent("onmessage", listener);
}

You can see these concepts in action on the Global Zero donate page. On a soon-to-be-launched project we're also taking it one step further to pass other information to/from the iframe.

If you do get this far, you'll note how this is a big pain in the arse; It will take several hours to get this all setup and QAed.  The good news is that at some point in the future we won't have to do this.  HTML5 has a new attribute for iframes called seamless. A seamless iframe will inherit the parent's CSS and adapt in size to fit the contents.  The bad news is that it has yet to be implemented by any browser, so it's still a long way off.


Photo by Bruce Denis


Thanks to Ben Vinegar for confirming that I'm not crazy to think this is the best way to do this.

Some browsers currently have very superficial support — they only remove the default iframe border. But that's just simple CSS that your browser-reset CSS is likely dealing with anyway.

Original Post: 

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web