Strategies for the iFrame on the iPad Problem
Magnolia in just 12 minutes
Why not learn more about what Magnolia can do while you have a coffee?
What’s the deal?
IFrames originally behaved on iOS mobile safari sort of how you would expect, the iframe had the size that you set, and the contents inside could be scrolled – with two fingers. Apple changed all that with an update in 2011. The current behaviour is that an iframe width is as you specify it in your code, but the iframe expands vertically to display the full document which it contains. Whoah.
I can imagine why they thought that was a good solution, now the iframe document is fully viewable within the host document, and users are not confused by a scrolling region within a scrolling page. But an expanding iframe is not so great for a number of situations, for example if you are building a web application that behaves like a typical desktop application. A desktop application has scrolling regions – but the main window itself usually does not scroll.
This is all related to another iOS mobile safari problem, wanting to put a large amount of content in a div on the page which has a fixed size. Previously, the “overflow:scroll;” css property did not work, and it still does not have “momentum scrolling” as expected and so several plugins have been created to address this such as iScroll. But iScroll does not work for an iFrame because the touch events fire in the iFrame document, but the parent document must do the scrolling.
Apple/webkit recently alleviated some of the problem with a css property called -webkit-overflow-scrolling: touch;
With this property a div with a lot of content will behave how you would like it to, and it even has the nice mobile safari “momentum scroll” when you flick it with your finger. Cool yeah? I bumped into tons of enthusiastic blog posts on this when the feature was announced. Problem is, its buggy. Under certain situations the contents of a container with this property will not be properly displayed, portions of it will be blank.
Unfortunately I have not discovered a solution that will make an iframe behave the way you want it to in all cases. I did find a solution that works in our specific case – and along the way a number of strategies for addressing it which I’ll list here. Consider if you really need an iframe, if you don’t then an overflow div may be enough, you can use the standard “overflow:scroll;”, or “-webkit-overflow-scrolling:touch;”, or use a library like iScroll. You could even load another document in with AJAX. In our case we require an iframe so that we have a separate context of CSS and Javascript. You’ll want to view the test cases on your iPad or android tablet – Ive found Site To Phone to be a handy tool if you dont have link sharer yet.
Strategies
iFrame in an Overflow Div #1: -webkit-overflow-scrolling: touch
Test Case:
iFrame wants to expand vertically? Fine just let it. Put it in a div with a fixed height. But how to scroll it then, applying “overflow:scroll;” on the enclosing div does not work. But the new “-webkit-overflow-scrolling: touch;” on the enclosing div does work! But there can be rendering problems.
Some people have solved these problems by ensuring that the div contents have only static positioning. One person reported they were able to solve it by forcing hardware acceleration. (SO: images dissapear) (SO: webkit-overflow-overflow error)
In our case we have no control over the iframe contents, so these were not an option.
In my own experimentation with Iframes, the render problem consistently appeared when the contents of the iframe had a greater width or height, then the containing page. (Maybe iOS allocates graphics memory based on parent document size?)
A strategy for this is simply to increase the size of the parent body to match that of the iframe contents (as suggested here: SO iframe content not rendering).
This is the solution that works for us. The rest of our app elements are absolutely positioned and our iframe has 100% width, so a user does not notice that we have created a tall page when we load the iframe contents. The additional problem that this caused is that once the user has scrolled to the bottom of the iframe, then on the *next* down scroll gesture, the whole app scrolls up. Whoops. Until Apple fixes their bug, our solution is to capture scroll events and animate the documents scrollTop back to zero when we detect a document scroll.
iFrame in an Overflow Div #2: Javascript Scrolling
Test Case:
Another concept, explored here (SO: iframe js scrolling). is to put the iframe in a div as above, and to add touch event handlers in the iframe document to capture the drag events. This code then calls to the parent document to move the iframe within the div. I experimented with this, but in my short tests the iframe content jerked around instead of smooth scrolling – understandable given the strange situation. Perhaps this could be made smooth – but I have not seen an example of that yet. This solution could only work if you control the contents of the iframe as you must have your touch detection javascript in there.
If someone gets this approach to work nicely, please post a comment.
Fixed Controls & Scrolling iFrame Page
Test Case:
Another strategy is to just let the iframe expand and fill the page, but surround it with position:fixed elements such as a header, side bars, and even a footer. Fixed position elements work great in iOS 5, and you could fall back to javascript to reposition the fixed elements after a scroll in iOS 4 and below. This almost worked for us.
Popup Window
For completeness sake, if all else fails you could simply launch that iframe content in a popup browser window. I know, i know.
Object instead of IFrame
Test Case: Covered in first Strategy.
I saw a few references to using an object tag instead of an iframe to solve the problem.
An object tag does indeed retain its size as it should – but the content was still not scrollable in my tests. I tried expanding the size of the object element and putting it in an overflow div just like the iframe – but it had exactly the same rendering problems as the iframe, so I don’t see any advantage to the object tag.
Example of Object tag to embed another web page:
<object type="text/html" data="content-to-scroll.html"></object>
Notes
It’s interesting to see that a div with -webkit-overflow-scrolling: touch renders differently, it appears a bit blurry. I guess this is the hardware acceleration – as it reminds me of a how a hardware accelerated css transitioning div looks, you can see it pop back to crispness when the transition is over.
Conclusion
What a mess, huh? There are some strategies but all of them have some pitfalls. I welcome your feedback and suggestions. How have you coped with the situation? Anyone from Apple care to comment?
Comments
Christopher Zimmermann on 18 May, 2012: Addendum: The context of this problem is that we are working on a page editor tool for our product Magnolia CMS. We need to have the context of the CMS editor with its tools and navigation – and also to load in the website which is being edited. An IFrame is a great match because it keeps the CSS/Javascript context separated, and ideally allows us to keep our application as a full screen application – yet scroll through the website page being edited.
David Gee on 27 July, 2012: Thanks for the very informative article. I spent some time struggling with this problem, none of the solutions you listed above worked quite perfectly for me. I eventually took a completely different approach, which so far seems to be very robust – attaching a load handler to the iframe which wraps the iframe’s document contents in a scrollable div. I call this function from the parent:
$(function(){
if (/iPhone|iPod|iPad/.test(navigator.userAgent)) {
$("iframe").bind("load",function() {
var b = this.contentWindow.document.body;
var div = $(document.createElement("div"));
var divCSS = {
'height' : $(this.parentNode).height(),
'width' : $(this.parentNode).width(),
'overflow' : 'scroll'
}
div.css(divCSS);
// Move the body's children into this wrapper
while (b.firstChild)
{
div.append(b.firstChild);
}
// Append the wrapper to the body
$(b).append(div);
});
}
})
Christopher Zimmermann on 8 August, 2012: Thanks David, Nice approach! I appreciate you adding your code to the post. In my case however I need the independent web context that an Iframe provides for the Javascript & CSS.
Helle on 18 June, 2013: Thanks you saved my world today! thanks also for the whole article!
Rich Brill on 12 November, 2014: @David, thanks for this approach and thank you @Christopher for an interesting article.
@David I found your approach didn’t quite work for me unless I put a height on the div containing the body (all within the iframe document).
I had problems using Kendo UI Editor from Kendo version v2013.2.918 on an iPad and your little script inspired me to try it using jQuery:
$(‘iframe.k-content’).contents().find(‘body’).wrapInner(”);
Rich Brill on 12 November, 2014: @Christopher, sorry my code above seems to have been ‘filtered’ by your blog. The string inside the wrapInner method is supposed to be:
an opening div tag with the following attribute in it:
style=”overflow:auto;-webkit-overflow-scrolling:touch;height: 100%”
and then a closing div tag. Cheers!
Deb on 2 August, 2012: One (https://topherzee.github.io/iframe-on-ipad-blog/iframe-js.html) of your approaches works for me but I am facing a problem. The website I am working on requires to display multiple (dynamic number of) PDFs on one page. While PDFs can be scrolled with two fingers with the help of your code, they are not centered and are cutting off on the right side.
Do you have the same problem? Do you have an idea how to make them centered within the iFrame?
Thanks a lot for this post and this is the only solution I found that works with a little tweak.
Christopher Zimmermann on 8 August, 2012: Hi Deb, I’m glad the post has helped you a bit. I have not worked with PDF’s in an iframe, I assume that there is not much control on how they are displayed. Could you perhaps solve the problem by the positioning of the iframe in the page (rather then the positioning of the PDF in the iframe)? For example – could you apply a negative left margin (margin-left: -200px;) to the iframe?
I have not tried that – just an idea.
Good luck.
Also, Im curious: What was the tweak that you needed to use to get one of my approaches to work?
Bob Jansen on 27 September, 2012: User csdco (https://github.com/fancyapps/fancyBox/issues/2#issuecomment-5997068) has provided a simple answer that solved the issue for me.
———————————–
It’s much easier to control overflowed divs than it is iframes, and the scrolling + blank content issues are working all the way back to iOS 4, where previously I wasn’t even able to get the 2 finger scrolling to work (in iframes).
It goes something like this:
a-file.html:
…
Silvia on 29 November, 2012: Have u ever use “include ” instead of iframe ? “Server side include” ~ It is similar to PHP’s include ~ Need a sever to test the result , but I don’t have a server…XD
Squriber on 14 February, 2013: If you have control over the contents in your iframe, add the -webkit-transform: translate3d(0,0,0) CSS style to force hardware rendering to get rid of the blank space when the iFrame is scrolled (iOS5 bug). Try something like this:
$(‘iframe’).load(function(){
$(‘iframe’).contents().find(‘body’).css(‘-webkit-transform’, ‘translate3d(0, 0, 0)’);
});
jaewik@gmail.com on 20 March, 2013: You could even load another document in with AJAX. In our case we require an iframe so that we have a separate context of CSS and Javascript.
Tobias on 10 October, 2013: Great explanation, which helped me a lot! I finally used the following approach (since I required a “sticky-top-right” container):
HTML:
CSS:
#container {
width: 210px;
height: 100%;
display: block;
z-index:100000001;
position:fixed;
top: 0px;
right: 0px;
-webkit-overflow-scrolling: touch;
}
#frame {
height: 100%;
width:195px;
z-index:100000002;
}
// And some jQuery:
if (/iPhone|iPod|iPad/.test(navigator.userAgent)) {
$(“#container”).css({ overflow: ‘scroll’ });
}
Maybe this helps others, too.
John on 4 December, 2013: Thanks, Christopher and Tobias! I had the similar problem, which you’ve solved!
Christopher Zimmermann on 18 November, 2013: Cool – thanks for your input.
Timothy Nott on 10 March, 2014: Even though the object example didn’t work for you, I am infinitely grateful that you included it — it does the job perfectly for my use case. Thanks! Why on earth can’t they just FIX this?
Ben Pearson on 2 April, 2014: Thanks very much for this post Christopher. I found it very helpful.
Mike on 5 August, 2014: I am having an issue where the iframe on the ipad just will not expand to teh full width no matter what. Has anyone seen that?
Robert Kirkwood on 25 September, 2015: I am seeing that as well. I have tried many CSS tricks to no avail. This is a big issue for us.
Alan on 23 August, 2014: Thanks for the post. Confirms I’m on the right path for my project. One issue I’m finding is links within the iFrame. How do you scroll the parent DIV back to the top when a user clicks a link to load new content in iFrame?
Was using the following code:
window.parent.parent.scrollto(0,0);
This doesn’t work when setting the parent DIV with -webkit-overflow-scrolling: touch;
thinsoldier on 20 October, 2014: Forcing hardware rendering on the page within the iframe by using a css 3d transform will fix the problem with some parts not being rendered.
But there is another problem if you have a form submit button on the page within your iframe. It will not work if it is very far down the page (or it might work once but if the form is displayed again due to user input error it won’t work a second time). But if you style a div to look like a submit button and use JS on that div to force the form.submit() event you can get the form to work.
Oh, and a form submit button, within an iframe, within an iframe – is out of the question.
Raja on 10 June, 2015: Hi , I have a strange problem, I have a cordova application that provides an Iframe to show another application(web ) content, i have few text fields on the iframe content, when I tap on the text field it opens the key board but when I tap any where on the out side of the test box, keyboard is loosing its focus, now I can not type anything in the text box, it works if I explicitly dismiss the key board and tap text box. this is not an issue in android platform