Responsive iFrames
Deploying code into multiple unknown environments that change constantly is tough. Deploying code into multiple unknown environments that change constantly AND uses responsive web design to render in everything is impossible. Until now.
Using the power of JavaScript we’ve managed to make an iFrame “responsive”. Using an iFrame is currently the best way we can think of to deploy bespoke infographics to our desktop site, responsive site, syndicated content partners’ sites and our Android and iOS apps. Read on to find out how we’re using iframes to make syndication of our infographics and responsive web design play nice together.
Responsive infographics? Phwooooar!
My job over the last year has been to work out how to make infographics on the BBC News website “responsive”. For the last 10 months BBC News’ Visual Journalism team have been producing bespoke, special one off pieces of content on the m.bbc.co.uk/news/ and www.bbc.co.uk/news/ websites.
Using Response-ish Web Design, we’ve created content that has a single starting point but quickly diverges, forking JS and CSS logic to produce slightly different mobile and desktop experiences. It’s not pure #RWD, but it suits our purposes right now while we have two websites.
We recently published a review of infographics from 2013. Take your time to look at a few, use the link in the footer of each page to switch between the mobile and desktop versions.
Supporting the desktop site
These bespoke pieces of work are hand crafted, they are given on average 1 to 2 weeks development, are published and then are left on the live site permanently. Back when we only had a desktop site, this was never a problem as this site is made up of statically published HTML files.
The desktop site is easy to cater for as its made up of static files that are rendered when the page is published:
- A journalist publishes a story, the content is processed by a template file which generates the HTML and stores it on a web server
- A web developer creates an include file with a bespoke infographic, they FTP the file to the same web server.
- When the story page is requested by a user, Apache includes the infographic.
Publishing our bespoke infographics to this website is easy as we know the page will never change, our code will always work like it did the day we FTP’d the inforgraphic to the web server.
Supporting the mobile site
The next generation responsive (mobile and tablet only) website is different:
- A journalist publishes a story, the content is sent to a database
- A web developer creates an include file with a bespoke infographic, they FTP the file to the static web server.
- When the story page is requested by a user, the page is assembled by our PHP application layer, the story data is requested from the database, PHP requests the include file from the static web server and renders everything through a template to create the HTML.
This dynamic site allows the BBC News dev team to make much richer, better services. But (and its a big but), the HTML is always changing, our release cycle averages one release a week, so the HTML template will change once the infographic include has been FTP’d.
Since we started publishing our infographics to the responsive website in the begining of 2013 we’ve had to go back and retro fix all of our bespoke content 3 times. Each time took slightly longer than the last because there was more content to fix. With every bespoke piece of content we make the job of retro fixing our content harder the next time something breaks.
We don’t deliberately break stuff, but its impossible to make our content future proof. Some bit of global CSS might change, a global JavaScript variable that we use could be updated to behave differently, or will react unexpectly against our own code.
This isn’t a great place to be in. We need a solution that decouples our bespoke content from the rest of the page.
An iFrame? *vomits in mouth*
There is a cast iron way of deploying static content to a constantly changing codebase and guaranteeing that your code is sandboxed, but it is very dirty. It is a solution that web advert companies and social media share buttons have used for a long time, but its very frowned upon. It is as bad as using <blink>
and <font>
tags, as bad form as Rick Rolling a fellow web professional or copy and pasting from W3 Schools: it is the <iframe>
.
The thought of putting an iframe onto the BBC News responsive site made me gag a little. I shifted uncomfortably in my chair and looked behind me before typing the tag into Sublime Text.
There are a number of problems with iFrames:
- Historically big accessibility issues, although these are not so much of an issue now with modern browsers.
- Not semantically pure – an iFrame is essentially an additional DOM in your page. This breaks one of the fundamental rules of the web: 1 resource per URI.
- Fixed dimensions – iFrames aren’t fluid, although some browsers allow you to set the width to be 100%. Because the contents of the iFrame is a seperate DOM, the iFrame element never knows what height to set itself to.
- iFrames smell bad. They’re stinky, your friends will make fun of you when you use one.
Before we started experimenting with iframe
s we looked at a few alternatives:
- Not using an iFrame – we REALLY looked at this. Unfortunately you can’t sandbox your CSS, and even if you intentionally wrote the CSS in the host page not to set styles globally, you’d still be open to bugs and mistakes in peoples CSS. Its also impossible to future proof CSS from future new CSS properties that you don’t know will exist. JavaScript is workable as long as everyone and their dependencies stays out of the global scope. This is too delicate to depend on.
- Seamless iFrames – This almost did exactly what we wanted, it allowed us to add our content into the page and the iFrame resized itself according to its own content. Unfortunately, the contents of the seamless iFrame isn’t sandboxed from the host page, so CSS and JS globals leak between the two documents. The browser support is also not great so we would have had to fall back to a standard iFrame anyway.
- HTML Imports, part of the web component spec – Again this almost does exactly what we want, it allows us to load our content into the page as a module with its own specified JS/CSS assets, but these are not sandboxed and are globally available in the page. Its also still just a spec and as of December 2013 hasn’t been implemented into any browser yet.
We bit the bullet and “iframed” a piece of content that we had already made. We relaunched the Stress Test in December 2013.
Findings
If you view the stress test you’ll see that it works *smiley face*. To make the iframe responsive we had to use JavaScript, there are modules in the host page and the iframe page that talk to each other, making sure the iframe height is the same as the content so the user never sees an additional scrollbar in the body of the page.
Communications between the iFrame and the host page was complicated
We quickly came up against the browser security model when sending messages between the two DOMs. Communication is only allowed by default if the two pages are from the same domain. One of our requirements was to syndicate our content to other sites, doing this with an iFrame means hosting our content on a BBC domain. To do this our JavaScript code goes through this logic:
- If
window.postMessage
is supported, use that - Else if the iFrame and the host page are on the same domain, communication can happen between pages via the DOM (
iframe.contentWindow
) - Else we can’t communicate between pages, so set the iFrame to a specific height and force the user to have two scrollbars in the page.
window.postMessage
is a native pubsub-like pattern available to JavaScript. It was designed specifically to allow cross domain JavaScript messaging. It doesn’t break the browser security model because for it to work, you have to set up the listener and the emitter. Browser support for it is almost really good, all browsers except IE7/8/9 support it fully. IE8/9 have limited support, instead of being able to pass objects in the message, they only allow you to pass strings. But this is okay as IE8+ supports the JSON
object, so we can JSON.stringify
our object literals before sending it via postMessage
, and then JSON.parse
it on the other end.
IE7 does not support window.postMessage
, so this means that the JavaScript in the iFrame and the host page can only message each other if they are on the same domain (there is a hack to allow IE7 talk across domains, but its nasty). Otherwise the iFrame has to have a set height. Looking at our browser stats, IE7 is 1% of our traffic so this fallback for syndicated content (when our iFramed content is hosted on other websites) is good enough.
iOS issues
Making the iFrame “responsive” was easy. You set the width to be the same width as the containing element and the height to be the height of the iFrame DOM, then keep checking to see if the dimensions need to be updated. This worked on everything perfectly except iOS Safari. While iOS Safari would render the initial size of the iFrame correctly, any additional iFrame resizes which were required for changes in the iFrame DOM were ignored. This is an issue related to the scrolling ipad issue, Apple decided to stop making the content of iFrames scrollable.
This is fixed by adding a property to the iFrame – scrolling="no"
.
Supporting 100% of browsers
The BBC News responsive website uses the technique Cutting The Mustard to support 100% of browsers. It does this by turning the normal browser support paradigm upside down and supports everything by default with a really simple version of the site. If you are running a modern browser then JavaScript in the page is loaded, enhancing the basic experience.
The Stress Test’s initial state is a link in the DOM, pointing at the iFrame’s content. JavaScript is used to change that link into an iFrame, this runs regardless of whether or not your browser “cuts the mustard”. The contents of the iFrame does use the Cutting The Mustard technique though.
// HTML
<a href="stress-test.html" class="js-responsive-iframe">Take the stress test</a>
// JS
document.querySelectorAll(".js-responsive-iframe").forEach(function () {
var resp_iframe = document.createElement("iframe");
resp_iframe.src = this.href;
resp_iframe.className = "responsive-iframe";
resp_iframe.scrolling = "no";
resp_iframe.frameBorder = "0";
this.parentNode.appendChild(resp_iframe.elm);
this.parentNode.removeChild(link);
});
Legacy IE
Inside an iFrame, our content can’t tell if it is being loading into our responsive mobile News website or the old desktop News website. This means we can’t fork the logic to customise the output into a mobile or desktop version like we currently do (the best example of this is our Cats special, switch between the mobile and desktop site to see how different the output can be).
This means we now have to support all our modern browsers and legacy IE (7 and 8) using the same techniques that we built the new responsive website with. This effects the following things:
- Mobile first CSS uses media queries, IE7/8 doesn’t support this technology
- Cutting The Mustard technique excludes IE7/8
- We use jQuery 2.* on the responsive site which isn’t compatible with IE7/8
To make mobile first, media query driven CSS work with IE7/8, we used the legacy-ie Sass technique. I’m happy to say this is the first time its been put into use on the BBC News website (go me).
To work around the jQuery issue, we used Grunt.js to generate two different versions of our JavaScript bundle, one concatenated with jQuery 1.* for legacy IE, and another concatenated with jQuery 2.* for all other browsers.
We then enhanced “cutting the mustard” like this…
if ('querySelector' in document && 'localStorage' in window && 'addEventListener' in window ) {
browser = 'html5';
}
else if (/MSIE (7|8)/.test(navigator.userAgent)) {
browser = 'legacyie';
}
If the browser doesn’t “Cut The Mustard” then we UA sniff for IE7/8. The variable browser
is then used to decide which of our JavaScript bundles to load. Doing this allows us to support legacy IE without compromising our JavaScript. The alternative JavaScript bundle can be custom made by Grunt.js, first by loading in different dependencies (jQuery 1.* instead of jQuery 2.*) and also by picking and choosing which of our JavaScript modules to add to the bundle.
We can choose to conditionally support IE7/8 per feature, so we might add JavaScript to the “legacy” JS bundle that processes a postcode input by the user, but we can choose not to add the module that changes a static data table into a fancy SVG infographic.
We call this new technique “Spreading The Marmalade”.
Summary
Deploying our bespoke specials to our desktop and mobile products allowed us to treat the iFrame as if it was powered by a element media query. All output via the iFrame is truly responsive, as we will no longer be deploying one codebase with logic that forks depending on whether its on the responsive or desktop site.
Using an iFrame makes us feel uncomfortable. Its definitely the dirtiest thing we’ve done to the responsive news site so far, but right now its an experiment. We’re planning on rolling out a few other bespoke interactives using the “responsive iframe” technique early in 2014. Tell us what you think of it.
By @tmaslen