Adding Touch and Gesture Detection to Web Pages on iPhone OS

Session 508 WWDC 2010

Safari on iPad literally puts the Web right in your hands. See how to enable direct manipulation of web content using the DOM Touch JavaScript API to detect touch and gesture events such as pinch and rotation. Learn to augment your web content with the kinds of rich interactions that you typically see in native applications, and see how to integrate DOM touch events with other prominent cutting-edge web technologies.

Hi everyone.

My name is Paul.

And this is Adding Touch and Gesture Detection to Web Pages on iPhone OS.

I'm an engineer on Safari for iOS.

Why are you here today?

You're here because you have a website for iPad, iPhone or you're thinking of building one.

And you want it to feel great.

iPad, iPhone, they're amazing.

They let you hold the web in your hands.

But maybe what's missing is you can't touch your web content.

Your users, your customers, and you want to be able to build web content for fingers, things that you can move around the screen just by dragging them, pinch to zoom or rotate with two fingers.

We can do these things using just web technologies, HTML, CSS, JavaScript.

You don't need Objective-C, you don't a native application.

We'll talk about how you can do that today.

And we can build really amazing, rich web applications for these platforms for iPhone, iPad, iPod touch.

Things that when they're sitting on the home screen of your customer's phones and devices, they feel just like native applications.

We'll talk about some tips, techniques and ways we can do that.

So, we're going to cover three main things today.

We're going to talk about the touch event API and the touch event model.

What touch events are, how they get delivered to your application, how you use them.

We're going to talk about building controls, reusable interface widgets, a few really rich controllers that aren't part of the standard HTML5 repertoire.

And along the way we're going to be using the Document Object Model and some CSS transforms to build some really nice, complex, rich interfaces.

So, hopefully everyone here is already familiar with HTML, JavaScript, and CSS.

This is going to be a very code heavy presentation, but don't worry about trying to copy everything down in your notes, in your laptops.

All the sample code that's presented here on the slides today is attached to the session.

You can get it from the WWDC Attendee site later.

And hopefully you're familiar with the basics of the Document Object Model.

Now, you don't need to know the 15 parameters you need to use in order to create a mouse event programmatically.

Hopefully though you're able to use a document.get ElementById to pull a DOM node out of a HTML document.

So let's talk about touch events.

The touch event API.

Why do we even have these touch events?

We already have mouse events.

Why do we need a separate set of events to describe your fingers?

And the answer is your fingers are different from mice.

There's a couple really big differences.

First, you have more than one finger.

Most computers only have one mouse.

Second, you can remove your fingers entirely from the equation.

You can lift your fingers off the screen and put them down in a completely different location.

I'm talking too much.

Let me show you what I'm talking about.

And I have this really simple demo.

It's got these three bubbles, mouse down, mouse move, mouse up.

And every time this page receives one of those events from the browser, they're going to light up.

So if I move the mouse around the screen, we'll get a series of mouse-move events.

And if I click, we'll get a mouse-down event, followed by a mouse-up event.

Now, on an iPad, iPhone or iPod touch it works a little bit differently.

Ok, I've got the exact same page loaded here on my iPad.

Thank you.

And I'm going to tap on the screen to click.

And what happens is we get all three of those events at once.

We get a mouse move and a mouse down and a mouse up all delivered at the same time.

And in fact, if you notice, if I tap there's a little bit of a delay.

Safari for iOS needs just a brief second to figure out what the user's trying to do.

Is the user going to double tap to zoom in to an element?

Is the user going to pan the page?

It needs just that really brief delay to figure out if we're trying to click on something or if we're using some other sort of gesture.

So mouse events are different than touch events.

Now I have another page right here with the touch events.

Touch events are different than mouse events.

Safari doesn't take any time to look at the touches and figure out what's going on.

It delivers them immediately.

So if I place my finger down on the screen, we'll immediately receive a touch startevent.

And as I move my finger around the screen, we'll receive a series of touchmove events.

And when I lift my finger off the screen, we'll receive a touchend event.

These are dispatched to your web content first.

You get a first crack at all of these input events.

Now we have more than one finger, I've mentioned this already.

And we'll receive these events for each finger and they might be interleaved.

So if I put one finger down, I receive a touchstart for that first finger.

I put another finger down, I'll receive a touchstart for that second finger.

Then I'll receive touchmoves as my fingers move on the screen.

And touchend when I lift them.

Now there's a fourth event on this screen and it's called touchcancel.

Touchcancel means something unusual kind of happened.

Touches were interrupted.

Maybe you got a phone call or you hit the screen with your whole hand.

Normally that works.

Apparently I can't be unusual enough today.

Touchcancel means something unusual happened.

Your touches were interrupted.

Basically all the touches are being reset.

So if you're keeping track of any state, you need to reset that.

So I talked about four basic events in the touch event model, four events that are delivered to your page.

There's touchstart, that gets sent every time a finger is placed on the screen; touchmove, gets sent every time a finger moves on the screen; touchend when a finger is removed from the screen; and touchcancel, touches have been interrupted, reset all of your state, start over.

Using all of these things, we can build a button.

"But wait," all of you are saying, "We have buttons.

We have lots of buttons.

We have input buttons and the button in element, why, why do we need to build a button with touch events?"

I can think of two reasons we might want to build our own custom button with touch events.

First, remember how there is that really short delay while Safari is trying to figure out if you're trying to click on an element or pan around?

If we use touch events, we get first crack at those events and we can build something that responds immediately to user input.

Second, all those mouse events come at once.

If we want to build a button that say looks depressed when you're pressing down on it, we'll need to use touch events for that.

So, the markup for this is going to be really simple.

We're going to build a button out of a div.

You can use a button or an input element if you'd like to be a little more semantic, but that brings with it some default browser style.

For this example, we're just going to use a div element.

And in the JavaScript side, we need a callback, something that happens when that button is tapped.

We could show or hide another part of the user interface or take the user to a different page.

Whatever happens when we tap that button, that's our callback method.

Then we'll use document.getElementById to find that button on the DOM, hold onto a reference for the element and we'll add an event listener, touchstart seems like a natural place to start.

We'll pass in the callback so that when we tap that button, we do something.

Let's take a look at how this works.

So this is a button, very simple, it's a div with a touch start handler.

And I'm going to place my finger on it and the button was tapped.

But that doesn't really feel right.

The button is tapped as soon as I put my finger down on the screen.

Normally we want to put our finger down, wait and then when we lift our finger, that's when we want the event to be dispatched.

So we'll make a really quick modification to the code.

Instead of registering on touchstart, let's listen for a touchend event and see how that works instead.

Modified button, I can place my finger down on the button and then when I lift my finger, we get our callback function called.

That feels a lot more natural.

So far, there's nothing that complicated about this button.

It doesn't really do much, it doesn't do any of the really cool things that I said we could do, like make it look depressed when we're pressing down on it.

And our code is already starting to get a little messy.

We're pulling out elements individually, we're registering these sort of floating methods.

We want to keep our code clean.

We want to keep it reusable, especially with touch events where we have to keep track of a lot of details.

So let's wrap it all up into a JavaScript class.

I have a constructor, capital B button it takes in 2 parameters, an elementID and a callback.

We'll use the exact same getelementbyID to pull the element out of the DOM, store it for later use.

We'll keep the callback around for later use.

And then we'll add our event listener near a touchstart and touchend.

There is something kind of unusual about this form of addEventListener.

Normally addEventListener takes three properties.

It takes the name of the event, it takes the callback method and then it takes in this false Boolean that we always pass in.

But here I'm passing in an object.

I'm passing in this.

This is a somewhat unusual form of addEventListener.

But what happens is if you register events passing in an object, when that event occurs, the handleEvent method, it's a special name, handleEvent.

That gets called on the object in question.

So instead of having to keep around the closure and store this and put it into self and create anonymous inline function so we can call on the right object later, this is one way we can get around that.

I want to do something kind of tricky to keep our code a little simple.

Event.type on a DOM event is the name of the event, touchstart or touchend, something like that.

I'm using subscript notation to look on this on our button instance to find a property named touchstart or touchend.

And then I'm going to make sure it's a function.

And if it is, I'm going to call it.

This is a bit unusual but what this means is we don't have to sit around and register touchstart handler for touchstart events and touchend handler for touchend events on our, on our button class, on our prototype, we just create methods called touchstart and touchend.

And those get called when we get our events.

In order to do our highlighted state, I'm just going to set a classing, add a new class highlighted on the actual element.

Then in CSS we can do whatever we need to do.

We can change the background image or make it a different color or make it explode into 1000 tiny pieces.

And on touchend we also want to call our callback function.

In the CSS, I'm using something kind of neat to create this lozenge shape.

If you set a height and then use a -border-radius to round off those edges, you can get a lozenge shape without having to use any sort of images.

And then the background I'm using webkit-gradient to create that sort of really nice, light to dark effect.

When the button is highlighted, you just reverse the gradient so it looks depressed instead.

It's really simple way to create a button that looks really nice, looks very visual without using any images.

So let's take a look at how this button works now.

With our new event handlers and that extra CSS styling, when I tap down on the button, it looks depressed.

It responds immediately and when I remove my finger, it pops back out, the callback gets called.

It's a really nice way of keeping your code simple.

Now, we've handled touchstart and touchend, but there's that fourth mysterious touchcancel event.

We need to make sure we handle this.

Touchcancel can happen for a variety of reasons.

A JavaScript alert pops up, you get a phone call while you're using your web page.

And it's important that you reset any state, anything that you're doing with touch events.

So in this case, on touchcancel, we'll simply remove that extra class.

Otherwise you might come back from a phone call and your button is stuck depressed.

And that's one way that we can build a button that responds immediately to touch events.

So we've talked about the four basic touch events.

Touchstart, fingers placed on the screen.

Touchmove, a finger on the screen moves.

Touchend, a finger has been removed from the screen.

And touchcancel, touches have been interrupted, don't forget to handle this.

For the rest of the examples I'm not going to walk through explicitly what I do on touchcancel.

But look at the sample code and make sure you handle touchcancel.

But that's just the start.

It tells us when fingers on the screen, what they're doing, but it doesn't really give us information about the fingers themselves.

What if we need to know where a finger is on the screen?

If we think back to mouse events and we look at a mouse event object, it looks pretty familiar.

We can immediately look at that object and figure out what button is down, the left or the right mouse button, where on the screen the cursor is, whether or not any modifier keys are being held down.

But if we look at a touch event, it looks really small.

There's not much there.

What is this changedTtouches?

Where are my screen coordinates?

The important thing to remember is that a touch event, because we have multiple fingers, is a list of touches.

What you're given is actually a list of touches.

There are three lists actually.

There's the touches list.

That's all the touches that are currently active or down on the screen.

So if I have five fingers on the screen, those five finger will be in the touches array.

There's changedTouches.

These are the fingers that have changed since the last event.

If I have two fingers on the screen and I'm moving one, and the other is staying still, changedTouches will contain only the finger that's moving.

And touches will contain both of them.

And lastly there's targetTouches.

These are touches that are inside the target element.

So if I'm tapping on an image for example, then the fingers that are inside the bounds of that image are inside of targetTouches.

Now these lists are more or less array like, so they have a length property.

So you could look at e.touches.length, event.touches.length and that's the number of fingers currently on the screen.

Or if we wanted to get the first finger inside the target element, we can use subscript notation, loop over them with arrays.

All those sorts of things we're familiar with, with arrays, will work on these touch lists.

So a touch event is a list of objects, is a list of touches.

Let's look at the individual touch.

The first property on a touch is an identifier.

This is a unique number that you can use to keep track of a single finger across multiple events.

Don't hold onto a touch object itself, you'll prevent it from being garbage collected.

Hold on to this number instead if you need to keep track of an individual finger.

Then there's target.

This is the DOM node that is the target.

So if we're tapping on that image, then this will be the image.

And then there's two sets of coordinates, screen coordinates and page coordinates.

If we tap here on this iPhone 4, screen coordinates are the position of the tap from the top right hand corner of the screen.

And if we double tap to zoom in say and we tap in the same location, page coordinates are the location of that tap relative from the top right hand corner of the page.

This is in CSS pixels.

So if you're going to be moving an element around to respond to a tap, this is probably the set of coordinates you want to use.

If you're simply interested in whether or not the user is tapping in the top half or the bottom half of the screen, that's what screen coordinates are for.

With all this information we can go on and build another really cool control, a slider.

Something that has a track and a knob and we can move it around.

Mark up is going to be really simple.

We're going to use a container div and then inside of that two divs, one for our track, one for our knob.

We'll have classes so we can get to those.

And then there's the constructor.

We're going to bundle this up to an object just like we did on the button so we can reuse it.

We'll pull the main parent, the container div out of the documents tree with getElementById.

Then we'll pull the knob out by getting getElementsByClassname, because we want to have more than one slider on the page, we need to use class names and not IDs in order to pull these out.

Let's also look at the width of that knob, offsetWidth is the actual width of that inside the rendered tree.

We'll use this to do a little bit of math so we don't drag the knob off the end of the track.

And then we'll also grab the bar out with getElementsByClassname and look at its width too.

The touchstart handler is going to be really simple.

At the start, we're going to look at the event and look into that targetTouches array.

Remember targetTouches is the list of fingers that is inside the target element.

We'll grab the first finger and grab its page X coordinate, location in CSS pixels on the page.

And we'll store that and then we'll call this method, this.moveKnobTo.

We'll get to this, but this is where we're actually going to do all the work.

Then on touchmove, we're just going to call moveKnobTo and again we're going to look at the targetTouches array, pull the first finger out, get its page location.

The moveKnobTo method is where all the work is going to be done.

First, we need to figure out how far the finger is moved.

So we stored that start X at the beginning when we, we got our first touchstart.

And we're just going to figure out how far the finger is from where it started.

Then we're going to do a little bit of math to make sure that we clamp the position of the knob to the ends of the track.

We don't want to be able to drag the knob all the way off to the right.

And then we'll just set the left style property inline of the knob.

And that's going to position it along the track.

We'll hold onto the starting position for the next time we call it through this loop and then we'll notify our callback and say now that the slider has been moved, here's where it is, do whatever you need to do.

Now, for performance reasons, instead of setting the left property on that, on that knob, we might want to use this instead, a webkitTransform.

Left will work, but if we want the best possible performance, this is really important for building user interface widgets.

Use webkitTransform instead with a translate3d property.

We'll construct a string, set the X transform to the X offset and then Y and Z offsets to 0.

Under certain conditions this will actually trigger hardware compositing, so we'll reload, we'll offload the positioning of this knob to the GPU.

It'll work super fast.

And that's the basics of building something that will track your finger.

I've been talking too much.

So, instead of showing you this myself, I'd like to invite up Greg Bolsinga, my coworker.

And he's going to show you these controls and a few others in a demo we've been working on.

Greg?

[ Applause ]

Hello, I'm Greg Bolsinga, I work on iPhone or iOS, WebKit, and Safari.

So with all those parts that Paul put together for us, I built this little scrapbook application.

It's just a web page, we have an image here.

And what you can do to this is play with your image and move it around with single touches.

And I created this only using HTML, JavaScript, and CSS.

The only non-text, non-code portions are these images.

So the first thing that I set up was playing with some of the CSS properties.

And what I can do is set the border width of the picture.

These are all regular CSS properties.

I can set the border radius, if you can see that curving down here.

Another thing you can do is I played with the box shadow.

I have this little scroller here where we could see more properties that I could set on here.

And with the box shadow we could pop it out either direction.

We'll move it down here.

Put a little blur on it so it looks pretty, looks blurry up there.

And so we have our image.

The other thing that I did with this is with the multitouch I could move these things around and I'm setting the CSS transforms that Paul mentioned.

And I'm just translating it here, but we also support gesture events.

So as soon as you put more than one finger down, I could start rotating this image as well as scaling it in.

And what I'll play with now is I have these masks.

These masks are all HTML canvases.

So I drew all these masks.

I'm going to be using the same canvas that drew these buttons as I'll have on here.

So for example, I can mask the image with just a circle.

Or I can pick other fun shapes like the star and I could still interact with the image here.

Zoom in and rotate it, even through the mask.

And I'm going to take the mask off so we can see the other images that I have.

[ Silence ]

Re-translate this.

And what I have here is another little scroller inside of a scroller and you could pick new images.

And do the same fun effects with it.

This will all, the code and the pictures for this will all be up on our website.

So again, that's all HTML, JavaScript, and CSS using touch events, gesture events and CSS effects and transforms.

[ Applause ]

Thanks Greg.

Greg mentioned this, but the only images in that demo are the four photos on the thumbnails.

Everything else was canvas, CSS, really cool tricks like that.

So Greg introduced something that I've been kind of talking about a lot and these are multitouch devices.

You can have more than one finger, but we haven't talked about that yet.

We haven't talked about dealing with a second finger.

There's a separate set of events that you receive once a second finger comes into the picture.

And this is very much like the earlier event demos, the button, the bubbles just highlight when we receive one of these events on the page.

And there's three gesture events.

Gesture events start once a second finger is placed on the screen.

So I'll place one finger on the screen.

And when I place the second finger on the screen, we receive a gesturestart event.

And so then I have two fingers on the screen and I move them around and receive a series of gesturechange events.

And then when I remove a second finger, we get a gestureend event.

Now these events are not exclusive from touch events.

We'll receive both at the same time.

So here both touch events and gesture event bubbles first finger on the screen will send a touchstart.

Second finger on the screen will send a gesturestart and a touchstart for the second finger.

We'll receive both touchmoves and gesturechanges as I move my fingers.

And we'll receive touchends and gestureends for all of those as I remove them.

One more time, touchstart, second finger sends both touchstart and gesturestart.

These events are all interleaved.

And gestureend and touchends when I remove those fingers.

So there are three events in the gesture event model.

Gesturestart, second finger on the screen.

Gesturechange, something is going on with those multiple fingers.

Gestureend, second finger removed from the screen.

There's one thing missing here you might notice, gesturecancel, there is no gesturecancel.

But because you receive both touch events and gesture events at the same time, you still need to handle it, listen for touchcancel even if all you're doing is gesture handling events.

The gesture event object is actually really simple and looks a lot more familiar.

First, there's scale.

Scale is multiplier, it's a float.

If I put two fingers on the screen and they're 100 pixels apart, and I move those two fingers apart so they're now 200 pixels apart, then that scale property is going to be 2.0.

My fingers are twice as far as when I put them on the screen.

If I put my fingers down at 100 pixels apart and bring them together so they're only 50 pixels apart, that scale is going to be 0.5.

My fingers are half as far apart as they used to be.

Then there's rotation.

Rotation is, are you ready for this?

Degrees clockwise relative to the starting position.

So if I have 2 fingers and I rotate my fingers like this, that rotation is going to be 90, 90 degrees clockwise relative from the starting position.

And then there's page coordinates.

Again these are coordinates from the top right hand corner of the page, they're in CSS pixels and this is the center of the gesture or the center of where all your fingers are.

So with all of that, we now have the information we need to build the rest of the picture frame.

We've already basically built the translation, the panning of the picture.

That's just tracking fingers in their X and Y coordinates, but the multitouch part which is new.

The picture frame is actually deceptively easy to build.

It contains only two parts.

The image is actually larger than the frame.

We can use something like overflow: none on the frame to crop the image down.

But the frame is a separate element that contains the image.

It's just a div.

We give it a class pictureFrame and then underneath it is the image.

That's it, they're the only two parts to building this frame, two elements.

When we set overflow: none on that frame, we crop the image.

And when we do all these panning effects and these rotation effects, all we're doing is we're moving the image behind the frame.

The frame stays in the same place.

We're scaling the image, applying these webkitTransforms.

And when overflow: none is in effect, we get this really nice sort of in frame editing effect.

So the mark up is super simple, container div, image on the inside of that.

Our constructor is going to take just one parameter element, we don't really have a callback for this.

And we're going to hold on to a little more state.

Gesture events, the information they report, the scale and the rotation, they're relative to the start of the gesture.

So we'll hold on both to the current scale and rotation of the picture and the scale and the rotation when the gesture starts.

And then we pull the image out with getElementsByTagName.

And then we listen for gesturechange events.

Again this is just for the rotation and the scaling.

The panning we want to use touch events.

Gesturechange is going to be really straight forward.

We look at the scale of when we started the gesture.

And we multiply it by the gesture scale.

So if the image starts out at 200% and we bring our fingers together so that the scale on the gesture event is 0.5, we multiply them and we shrink our image back down to original size.

Then rotation, there are degrees relative from the starting location, so we just add that.

And then we call this mysterious transformImage method.

TransformImage is where we're actually going to apply the properties and do the work.

It's a single line.

We're going to set an inline webkitTransform style and build up this string.

First we're going to translate the image.

If we're positioning it 100 pixels left, 100 pixels up, we're going to set both X and Y transforms there.

Then we're going to scale the image.

WebkitTransform takes in the same sort of units that the gesture event will report.

So it's easy just to translate this over into CSS.

And then the rotation, the unit for rotation is degrees, deg.

And we're going to build up this string, that concatenation is really kind of ugly in code.

But what we're building is a string that looks like this.

Translate3d, X and Y coordinate, scale, and rotate.

And that's it.

That's all we need to do to build our picture.

All we need to do to handle scaling and rotation.

I want to step back and talk about the DOM Event Model.

All of these events, mouse events, touch events, gesture events, these all fall under the system called DOM events.

Here we have a DOM tree.

There's two of these picture frame controllers in the same document.

And we're going to send an event down to say we tap on the second picture frame control.

So we have an event and using hit testing or something like that, we figure out where this event is going.

It's going to that second image in the document.

The event is going to start at the top.

It's going to start at your Window object, from the browser and it's going to work its way down to the specific event it's trying to get to.

It's going to work its way down to the .

This is called the capture phase.

This event is going to be dispatched to every one of these elements all the way from Window to Document, , , then eventually our picture frame control, the containing

, and then the .

And any one of these elements that have a capture phase event listener we'll get to handle this event.

The event will reach our target element, the image, and then it's going to start working its way back the other way.

This is called the bubbling phase.

The event starts at the most specific element and starts working its way back up to the top.

Once it reaches the top, returns to the browser from whence it came.

Normally we only work in the bubbling phase.

Remember how addEventListener has three properties.

It takes in the name of the event, the callback or object and then false.

We always just put false in there.

That third Boolean is whether or not we're adding our event listener to the capture phase or the bubble phase.

If it's false, it's in the bubble phase and if it's true, we're listening in the capture phase.

We normally don't want that.

We normally want to start from the most specific element and then let event bubble up to less specific elements.

But occasionally capture events are a really useful thing.

If you're having a problem with a web page and you're trying to debug it, you might want to add a capture event listener on document.

It'll see basically every event that goes down into your page.

So with this in mind, let's go back to these really cool controls we're building and we'll talk about this single finger scrolling pane.

Now on desktop, if we want this area where we have content and we can scroll it and it stays within this nice little frame, one way we might do that is by using a div with overflow set to auto.

The child contents larger than the, than the div, we'll get scroll bars, we can use scroll wheels, all that sort of stuff.

This actually works on iPhone and iOS.

But you have to use two fingers at once to scroll that content.

It's not really discoverable.

A lot of people have trouble finding that.

So instead we're going to build an area that we can scroll using just one finger, using touch events.

So the mark up or the actual implementation for this is extremely simple.

In fact we've basically already built it.

It's got two parts.

It has the content underneath.

The child layer, the content layer I suppose we can call it.

This contains whatever we need it to contain.

It could be lists, it could be images, it could be other scrolling panes because we're programs, we like recursion, we like putting things into things because we can.

And then outside of that there's the panel container.

This'll have overflow: none set, it'll stay fixed on the page and when we receive touch events, we'll simply translate that content layer, we'll move that content layer around using webkitTransform.

And we'll get a really nice single finger scrolling area.

The mark up we're going to use for this is really simple.

There's an outer scrollable class, this is the, this is the frame.

And then inside of that is our content layer.

It can contain whatever we want it to contain.

So let's take a look at this.

I have here one of these scrolling views, these scroll panels, scroll containers.

And inside of it, it's a list, it just counts from 1 to 8.

And I'm going to use a single finger and start scrolling and that doesn't work very well.

As I'm scrolling, as I'm trying to scroll this container, the page is panning too.

What's going on here?

It really helps if we go back to the DOM Event Model, the DOM tree to understand what's going on.

This is a DOM tree where we have two of these scrolling containers and we're receiving a touchmove event.

Touchmove is how we're panning that content.

So the touchmove starts the Content, it moves to the Container, we actually do something like scroll our content.

And then the event keeps going.

It keeps bubbling.

It goes all the way back up to the browser and the browser gets to handle it.

Safari gets a crack at that event.

Now Safari's default action, what Safari usually does on a touchmove event is it pans the page.

This isn't necessarily the case with say a desktop browser where a mouse move pans, pans the page or scrolls the page.

But these default actions always bubble up to the browser and the browser does its default thing.

Instead of letting that happen, what we really want to do is after we handle the touchmove event, after we scroll out content, we want to let Safari know that we've handled the event.

We've taken care of it.

Safari shouldn't do its default thing.

And there's a method for that.

It's called preventDefault.

We call this on the event, the event gets canceled.

It's marked as already having been handled.

The event will still continue to propagate, it'll go all the way back up to Safari, but once it reaches there, Safari will see the event has been handled and it won't do its default action.

So if we go back to our scroller and we add that preventDefault call and we try sending touchmoves to scroll.

Now it works great.

We have this pane that we can scroll with just a single finger.

Now I said we can put anything we want in here.

We can put another scroller perhaps.

So let's try that.

Here's another scroller, the individual items are a little bit larger, but we still have these same numbers and we're counting up.

And then I have this imbedded lists that counts letters instead, you can count letters I suppose.

And let's try scrolling, we're going to see that it works.

Terribly. They're both scrolling.

They're both scrolling at the same time.

If I scroll on the outer one, it's fine.

But as I'm trying to scroll this internal, this interior letter list, both scrollers are trying to scroll.

What's going on here?

Again, it really helps if we go back to the DOM Event Model, see what's going on with these events.

Touchmove starts at the interior scroller, it moves up to the Container and we cancel it.

We mark it as handled.

But then the event keeps propagating.

It goes to the exterior container, the exterior scroller and that also scrolls.

Now we could do a couple of things here.

We could say check to see if the event has been canceled and not scroll our outer container, or we could do something a little bit different.

Once we've handled the touchmove event, we're done with that touchmove event.

We really don't want anything else to get that touchmove event.

So we call this other method.

StopPropagation.

It does what it says on the box.

It destroys the event.

No other event handler will receive that touchmove event.

We'll be the last bit of code to see that event.

So the outer container never sees it.

It never acknowledges that exists.

We can give this a try.

Instead of calling just preventDefault, let's call stopPropagation on that interior scroller first.

The outer one works and the inner one works too.

So we can scroll our two lists independently of each other.

[ Applause ]

This is one of those things that you actually have to try.

You actually have to feel.

It's amazing how with CSS transforms and touch events you can get this really responsive.

And we've built our scrolling pane.

We now have these areas that we can scroll with just one finger in our web content.

So we've talked about a ton of things today.

And since I've got you all looking at me and paying attention still, I hope, while I've got your attention I want to talk, just share a couple of last minute totally super awesome tips.

First, go try this.

There's a lot of details that are always inherent when you're building any kind of UI, especially any kind of really rich UI.

Give it a try.

Download the sample code.

All of those event bubbles and that scrapbook demo, they're both available from the WWDC attendee site.

If you log in, find this session number 508 and then click the More Info inside that grid view, you can download the sample code, check it out.

We're doing a few other things, a few that we kind of glossed over in this presentation.

So give it a try.

As you're experimenting, remember, multitouch.

So the touch event that you get, the touch event that your event handler has delivered is a list of touches and there's three of them.

Second, I stopped doing this, I got lazy part way through the presentation.

Don't get lazy yourselves.

Handle touchcancel.

They're going to come when you least expect it.

It means you have to reset state.

So if you need to reset any variables that you're holding onto or maybe reset some CSS styles, make sure you do that handle touchcancel.

And lastly, actually test on your iPhone on your iPad.

The simulator is an awesome tool.

I love using it for development work, but nothing really beats actually trying this stuff on hardware.

You're going to find things that you didn't realize when you're using the simulator like maybe your buttons are too small for your fingers.

Or maybe your text needs to be bigger.

Or maybe your hand obscures more of the screen than you think it did.

When we're building sites for desktop, we might build a menu that pops underneath the mouse cursor.

But if you think about how your hands work, if you tap that menu, it's going to pop underneath your hands.

That's not so good.

Instead you might want to build a menu that pops above where you're touching.

So test on hardware.

We've talked about a bunch of things today.

We've talked about touch events and gesture events.

The APIs, how they're delivered, what they look like, four touch events, three gesture events, they come interleaved.

We've talked about strategies for building reusable interface components, how to keep your code organized with this sort of code.

You try using an object as that second parameter to addEventListener.

You'd be surprised how much it can really clean up your event handling code.

And lastly, there is a ton of built in power to CSS transforms and the DOM Event Model.

Instead of reinventing the wheel and building additional state handling information tracking events by hand, use what's already there.

Use the power that's already there.

Try this out.

Really, there's, for touch events especially, nothing beats actually writing code yourself.

If you have additional questions, you can always contact Vicky Murley, our Safari Technologies Evangelist.

There's documentation on developer.apple.com/safari, information about touch events and the touch event model and how they're delivered.

If you're really interested in learning more about DOM events and how they work, the DOM Level 3 Events Specification spells all of this and all of it is regular specification gory.

If you're really interested, try starting with the DOM Level 2 Event Specification.

It contains all the stuff we talked about today like stopPropagation, preventDefault, but it's a bit easier to read.

And there's the Developer Forums.

You can ask questions in there.

There's a forum specifically for building web applications for iPhone.

Apple, Inc. AAPL
1 Infinite Loop Cupertino CA 95014 US