iPad and Single Finger Scrolling in flexmls

by Nick on April 13, 2011

in Web development

Foreword

Today’s post is from Nick Larson, one of FBS’s web development interns who was tasked with making the iPad compatible with flexmls.   He discusses the technical details below how he implemented scrolling on the iPad within flexmls.

-Greg Kilwein


iPad: Single Finger IFrame Scrolling

When we first started testing the iPad for compatibility with flexmls, one major issue was that scrolling did not work in many core places within flexmls. By default on the iPad, not only does scrolling a div require two fingers instead of a single finger, but iframes aren’t able to scroll at all. The goal was to provide single finger scrolling wherever possible.

Thankfully, the iPad provides us with some great built in touch events that we can use to get started solving the problem.  Using a couple of the events, specifically touchstart and touchmove, we are able to create a simple function that can take care of initializing single finger scrolling on any div or other scrolling element aside from iframes (we’ll talk about iframes later). The great news is that this is pretty cross-browser compatible, even amongst touch-capable browsers. I tested it on a couple mobile browsers, and it even seems to work with Internet Explorer on Windows Phone 7! So thanks to coming across a few helpful resources, I came up with this:

function initMobileScroll(ele) {
	var mobileScrollArea = document.getElementById(ele);

	mobileScrollArea.addEventListener('touchstart', function(event){
	    touchstart (event);
	});

	mobileScrollArea.addEventListener('touchmove', function(event){
	    touchmove (event);
	});

	// let’s set the starting point when someone first touches
	function touchstart (e) {
	    startY = e.touches[0].pageY;
	    startX = e.touches[0].pageX;
	}

	// calls repeatedly while the user’s finger is moving
	function touchmove(e) {
	    var touches = e.touches[0];

	    // override the touch event’s normal functionality
        	    e.preventDefault();

	    // y-axis
	    var touchMovedY = startY - touches.pageY;
	    startY = touches.pageY; // reset startY for the next call
	    mobileScrollArea.scrollTop = mobileScrollArea.scrollTop + touchMovedY;

	    // x-axis
	    var touchMovedX = startX - touches.pageX;
	    startX = touches.pageX; // reset startX for the next call
	    mobileScrollArea.scrollLeft = mobileScrollArea.scrollLeft + touchMovedX;
	}	

}

What I initially thought was going to be a huge project, ended up being a simple fix thanks to the built in touch events and a little research.

Now on to the hard part, the iframes. flexmls relies on iframes to display much of the content seen throughout the system. The problem with the iPad is that it doesn’t play nice with frames at all.

For some reason the iPad doesn’t respect their height value. Instead of keeping the desired height and allowing scrolling, the iPad just stretches out the iframe vertically to accommodate all of the content. I feel like this could be a bug with the iPad, or something they weren’t anticipating. Either way, we needed a fix.

Right away I thought of just wrapping a div around it and use that div for scrolling. However, that doesn’t work. The problem with that is when the initMobileScroll function is applied to the iframe and try to scroll, the iframe just captures the touch events instead of the div. Well, after some thought I was able to up with two methods.

The first method, “version 1”, involves placing an absolutely positioned div over the iframe so the touch events stay in the context of the same window and don’t get caught by the iframe. The only problem with this method is that it renders everything in the iframe unclickable (like links). This option would look something like this:

<style type="text/css">
#scroll-wrap {
	position: relative;
	width: 400px;
	height: 500px;
	overflow: auto;
}

#scroll-overlay {
	position: absolute;
	top: 0;
	left: 0;
	/* set the width and height equal to the iframe’s */
	width: 500px;
	height: 850px;
	z-index: 999;
}
</style>

<div id="scroll-wrap">
	<div id="scroll-overlay"></div>
	<iframe width="500" height="850" src="inner.html"></iframe>
</div>

<script type=”text/javascript”>
initMobileScroll(‘scroll-wrap’);
</script>

Well that’s a decent option, but what if users need to be able to click on things in the iframe? The second method, “version 2”, addresses this issue. While it requires the framed content to be hosted on the same domain as the parent document, it definitely works.

What we can do is just keep the iframe hidden and use the “innerHTML” property to pull the content out of the iframe after it’s loaded. We can then place the content into a div, and apply our mobile scroll to the div instead of the iframe.

This method would look something like the following:

<style type="text/css">
#scroll-div {
	position: relative;
	width: 400px;
	height: 500px;
	overflow: auto;
}

#the-frame {
	display: none;
}
</style> 

<div id=”scroll-div”></div>
<iframe id=”the-frame” src=”myframe.html”></iframe>

<script type="text/javascript" charset="utf-8">
	var theFrame = document.getElementById('the-frame');
	theFrame.onload = function() {
		var frameBody = theFrame.contentWindow.document.body.innerHTML;
		document.getElementById('scroll-div').innerHTML = frameBody;
	}
	initMobileScroll('scroll-div');
</script>

That’s pretty much it. Just taking the content from the hidden iframe and throwing it to a div, which behaves much better on an iPad. Now every time the iframe’s source is changed, the div will automatically reload with the new content! One thing to note is that innerHTML only works when the content in the iframe is hosted on the same domain. If it’s not, the browser will block the attempt.

I hope this gives some insight on how to accomplish scrolling on the iPad.

-Nick

{ 11 comments }

smfr June 8, 2011 at 3:00 pm

The expansion of iframes is by design. I think scrolling=”no” will disable it, but I’m not sure if you’ll then be able to scroll the contents from JS.

Greg Kilwein June 8, 2011 at 3:40 pm

smfr, thanks for commenting and for the suggestion. We tried scrolling=”no” and it did prevent the expansion of iframes, but scrolling doesn’t work as you suspected.

Vijayasaharan June 19, 2011 at 12:47 pm

Hi Worked liked a charm , Thanks for the post.
is there any option in the webkit to make the scroll more faster, i have a big list and i would love to see if i can get the scroll to be more fluid..

But this is a great great start for me. Thanks

Nick June 20, 2011 at 7:46 am

Thanks! You could always try adding a multiplier to touchMovedY and touchMovedX. I’m not sure if that would feel natural or not when scrolling, but it would be worth a try.

Greg.Baker August 24, 2011 at 12:28 pm

I believe I am missing something here.

I needed an iFrame to host my main content on an iPad (and any other ‘large-screen’) due to the ‘sliding-content’ page changes common to the iPhone – slide in from right with simultaneous slide out to left for example. In this case, a ‘div’ tag fails to hide the sliding content by fully showing both the incoming and outgoing pages, side by side, as they slide into place. Not a good look.

So I went back to my old iFrame model, originally developed for flash overlay HTML content displays. The iFrame ‘masks’ the the sliding content displaying only a single page view as intended. These content pages are designed with a fixed header and footer at the inside top and bottom of the iFrame display, and a ‘div’ tag with scrollable content in between – my exact desired intent.

This scroller is single-finger scrollable, the content is selectable and the page is swipe-able as well.

So I find the above post a bit perplexing. For me, the ‘div’ tag is/was not a viable solution. But the iFrame is perfect and seems to work fine in my limited testing. ( Note: I do have quite a bit more javascript than listed above. )

gsb

Nick September 7, 2011 at 10:45 am

That’s interesting. Last I looked, people have been having a ton of problems with iframes and scrolling on the iPad. Unless one of the new versions of ios has since fixed some of the problems. In that case, awesome! Just doing a Google search of ‘ipad iframes’ will fill you in on some of the problems I’ve been dealing with.

michael September 7, 2011 at 12:10 am

“One thing to note is that innerHTML only works when the content in the iframe is hosted on the same domain. If it’s not, the browser will block the attempt.”

not sure what this means – the content displayed in the iFrame has to reside on the same server as the web site on which the iframe is displayed? If that is the case, it would defeat the major objective of including iFrames in a website: including content not hosted there.

Thanks for clarifying

Nick September 7, 2011 at 10:29 am

That is correct, the content has to be on the same server due to the same origin policy (http://en.wikipedia.org/wiki/Same_origin_policy).

This might be something to check out though: http://stackoverflow.com/questions/2404947/same-origin-policy-workaround-using-document-domain-in-javascript

In our case, we mainly used iframes in the past to display different features/elements/etc. on a page rather than content from another domain. The Wikipedia page on the iframe element has a pretty good explanation of why we initially chose to use them: http://en.wikipedia.org/wiki/HTML_element#Frames

However, now that there is much better support for XMLHttp requests (in the form of ajax), most (if not all) of these situations we’ve run in to can be avoided.

Greg.Baker September 8, 2011 at 2:10 pm

Hi all. I forgot about this post, sorry.
I used to use iFrames in my base site designs. Also for dynamic HTML content overlaid upon Flash sites. So when I started to do some iThingies testing I again turned to iFrames. They worked well for me. But recently I decided to eliminate the iFrames for “clipped dev” containers. My content comes from my site; directly or indirectly. The iFrames allowed easy “page slides” in-from right, out-to-left, etc. Clipped regions do just as well for that.

Also I use Cubiq.org’s iScroll 4 currently for content scrolling. This allows a fixed header and footer while scrolling the content; basically all I needed. Anyway, here is a link to a stripped version of the scroller in a clipped container (not iFrame). It is designed (a work in process just now) on anything – browsers, ipad, iphone, etc. The iphone is too small so the container disappears.

This uses jQuery as a cross-browser code basis and also used the JavaScriptMVC controller as a “widget” manager. On the example, the buttons and all do not operate; it is for scroller demo only. The scroller works like a dream.

http://software.gypsytrader.com/containerized.html
(The gold text at the top is an active debug log.)

Greg

jm May 23, 2012 at 5:41 am

Just curious though.

Will the use of innerHTML works, if some contents are dynamically created by javascripts and that event handlers were also dynamically attached to the elements?

var frameBody = theFrame.contentWindow.document.body.innerHTML;
document.getElementById(‘scroll-div’).innerHTML = frameBody;

Sample scenario:

Iframe’s src document:


ok

I think func_from_some_js is only available on the src’s context.
So how can we work-around this?

jm

Ryan November 21, 2012 at 10:13 am

This unfortunately does not work anymore with ios6 and Safari :(

Comments on this entry are closed.

Previous post:

Next post: