myDailyPhoto Blog

iPhone Safari Flick Navigation

July 21st, 2008 10:38PM EDT by matthew

After an entire weekend of fiddling with the iPhone SDK I finally threw in the towel; I can program in PHP, JavaScript, JSP, ActionScript and more, but that language is just down right frustrating. The application looked great, sure, but it didn't function at all.

In the midst of all my research for help I stumbled across something that I, like most, had completely forgotten about; the iPhone update wasn't just for native third-party applications, but it also upgraded the existing applications. Yes, that includes Safari. The upgrade for the iPhone's on-board browser added in support for CSS animations and transitions, a JavaScript accessible database, a few new DOM selectors and more. For me this meant that the myDailyPhoto web application could look and feel more like it was a native Cocoa Touch enabled experience. As soon as the idea crossed my mind I sat down to churn out this little test app.

Please keep in mind that this is just a test application and not complete by any means. It should serve as a nice starting point for your own Safari Touch API-enabled web projects, though.

The Idea and The Basics

The idea was to take some of the basic user interaction points present in the core on-board applications and port them over to a Safari-enabled web application. One that's always been rather intriguing is the different gestures that are available, especially the flick navigation. If you don't know what I'm talking about, trying pulling up the photo library on your iPhone and transitioning between photos in the full-screen mode.

After a few minutes of writing out some basic requirements on a pad of paper while simultaneously going over the Apple-provided CSS transition and JavaScript touch APIs the plan seemed fairly clear, so I just started coding away. The general idea was to first set up the transitions using the WebKit animation framework, activating it by some simple mechanism, such as an "onclick" event. Below is the animation code that I used to achieve this (other code exists, too, so be sure to check the source).

.divSlide {
	-webkit-animation-name: "slide-me-to-the-right";
	-webkit-animation-duration: 1s;
}
@-webkit-keyframes "slide-me-to-the-right" {
	from { left: 0px; }
	to { left: 100px; }
}

At this point the DIV you want to slide around shouldn't really have any class, not even "divSlide". It should have a few key CSS properties, though, such as "position: absolute" and a "top" property. Because assigning default classes would be a lot of extra effort (mainly because we replace class names with a function), it's easiest to wrap all of the sliding DIVs into a container and style them like that.

The way this works is that once you assign an element a class with an animation, such as "divSlide" above, it animates into action instead of immediately changing the properties. The animation that plays is the -webkit-animation-name, and the duration of the animation is, of course, -webkit-animation-duration. The animation name has no requirements, it just has to match a WebKit Keyframe function (at first I thought that the names were actual built-in functions, but the engine is smart enough to recognize what you want just from the values present within one of the functions).

The WebKit Keyframe function should be fairly self explanatory; you assign a starting position and an end position. Because we only want the elements to slide from left-to-right, we only need to assign the "left" CSS property.

Making the CSS Animations

If you've got the rest of the applicable CSS properties assigned the element you should now be able to change the class name of the element when an event is fired, and this should start the transition. For instance, we could do this:

<div id="myDiv" style="position: absolute; top: 0px; left: 0px;" onclick="this.className='divSlide'">
	This is some content
</div>

One thing that you may immediately recognize is that the element returns to its' original position as soon as the transition is complete. While this may be a little frustrating, there is a simple solution. Instead of calling the animation straight from the onclick event we could call it from a JavaScript function, and at the end of the function force the position. Let's look at an example:

<script type="text/javascript">
	function slideMe() {
		var myDiv = document.getElementById("myDiv")
		myDiv.className = "divSlide";
		myDiv.style.left = "100px";
	}
</script>

<div id="myDiv" style="position: absolute; top: 0px; left: 0px;" onclick="slideMe()">
	This is some content
</div>

Now we should be set, so let's start taking the slide animation demonstration and make it usable. First create another DIV and position it off the screen to the right, giving it a "left" CSS property of 320px. It should now be residing just off the side of the screen. You may wish to place it a bit further to the right if you want margins around the element, as in our demonstration. So now we should have two DIVs, one positioned on-screen and one positioned off-screen. Apply the same general code that we made above to these two elements to see the slide in action. The first DIV should slide from 0px (or maybe 10px if you gave it some margin) to -320px; create the necessary CSS class and WebKit Keyframe function. Then do the same for our new DIV, except sliding the animation from it's current position to the position of our current on-screen element (e.g. "left; 320px" to "left; 0px"). After some possible troubleshooting this should work beautifully.

Touch-Based JavaScript Events

If you've ever used JavaScript events to fire off functions before, especially controller-based events (mouse clicks, scrolls, and the like), then this should be fairly simple to understand. Apple has essentially provided us with a few new events that replace the mouse events they took away with their touchscreen goodness. There are four of these new events, and all but one are extremely useful. For a more in-depth look at these events you might want to check out the SitePen blog entry about Touching and Gesturing on the iPhone (we sure did!). Now let's take a look at what they are, and how we might add some listeners for them:

touchstartWhen a finger touches the screen
touchmoveWhen that finger moves on the screen
touchendWhen that finger leaves the screen
touchcancelWhen the touch has been canceled *

document.addEventListener("touchstart", touchHandler, false);
document.addEventListener("touchmove", touchHandler, false);
document.addEventListener("touchend", touchHandler, false);

* You may have noticed that I left out the "touchcancel" event from our listeners because, as of today, I can't find any point where this event is fired.

Each of these events, when fired, calls up the "touchHandler()" JavaScript function. This function simply determines which event has been fired and performs the appropriate actions. Let's take a quick peek at some of the basics for this function:

function touchHandler(e) {
	if (e.type == "touchstart") {
		alert("You touched the screen!");
	} else if (e.type == "touchmove") {
		alert("You moved your finger!");
	} else if (e.type == "touchend" || e.type == "touchcancel") {
		alert("You removed your finger from the screen!");
	} else {
		// Nothing
	}
}

From this point on it's fairly simple stuff: keeping track of the currently displayed element, the previous element, and the next element, as well as what the user is trying to gesture. I for one decided to perform this by setting an original X-Coordinate variable ("oX") when a user touches the screen ("touchstart") and calculating the current X-Coordinate when the finger has moved ("touchmove"). If the difference between the starting coordinate and the current coordinate exceeds 100px in either direction, then I call up the function that transitions the current element out in the direction of movement and the next element into its' spot. I choose 100px because after some testing it felt more natural; it wasn't requiring a drag across the entire screen, but also wouldn't register a flick if I barely moved my finger left or right.

Wrap Up

If you play around a bit with the CSS animation and the Touch APIs, as well as take a quick look at the overly-documented code provided for this test application, you should get a grasp of how it works fairly quickly. It's straight-forward enough that someone like myself, who has mediocre JavaScript knowledge, wrote it over the course of an hour.

Side Notes

As of right now there is no consideration for vertical scrolling, as this was just a quick code exercise. Should you desire to actually use this code in an application you need to understand what this means; if you're hiding the window location bar, as most iPhone Safari applications do, then your users won't be able to scroll up to change the URL. You can circumvent this a few different ways; providing a 'Close' button, re-writing the JavaScript to move the window location based on vertical gestures, or even re-writing the JavaScript to only allow flicking gestures within a containing DIV (so that users could place their fingers outside the DIV for a vertical scroll).

As for the terms of use, because I've been asked many times, you may freely use, distribute, and modify this code for any purpose, so long as you attribute the code as outlined in the TOS within the source. As a kind request on my part, please e-mail me if you do anything really awesome with this code. I'd like to see what you all are coming up with; some of the stuff I've come across thus far has been mind-blowing.

Try It Out

http://i.mydailyphoto.com/flick.html   (Requires at least iPhone 2.0)

View the full source