In this chapter, we will cover:
Moving an element with touch events
Detecting and handling orientation event
Rotating an HTML element with gesture events
Making a carousel with swipe events
Manipulating image zoom with gesture events
One of the biggest differences between mobile and desktop is the way in which we interact with the screen. On a desktop screen, we use a mouse to move and click events to control the interaction. On a mobile screen, the interaction comes from touch and gesture events. In this chapter, we will see some of the events that are unique to touch screens (for example, two finger events) and how you can leverage these features to build something unique for mobile.
On a mobile screen, we interact with elements using touch events. Because of that, we can move an HTML element on the screen with our fingers.
For this example, we will be using jQuery. First, let's create a new HTML file, and name it ch03r01.html
.
In your HTML document, use the following code:
<!doctype html> <html> <head> <title>Mobile Cookbook</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> #square { width: 100px; height: 100px; background:#ccc; position:absolute; } </style> </head> <body> <div id="main"> <div id="square"> </div> </div> <script src="http://code.jquery.com/jquery-1.5.2.min.js"></script> <script src="http://code.jquery.com/mobile/1.0a4.1/jquery. mobile-1.0a4.1.min.js"></script> <script> $('#square').bind('touchmove',function(e){ e.preventDefault(); var touch = e.originalEvent.touches[0] || e.originalEvent.changedTouches[0]; var elm = $(this).offset(); var x = touch.pageX - elm.left/2; var y = touch.pageY - elm.top/2; $(this).css('left', x+'px'), $(this).css('top', y+'px'), }); </script> </body> </html>
First, we register the square div
with a touchmove
event.
You can detect the touch position relative to the page which is touch.pageX
and touch.pageY
in our example. We use the finger position minus half the width and height of the square div
element, so it feels like we are moving with the div
center as the registration point.
var x = touch.pageX - elm.left/2; var y = touch.pageY - elm.top/2;
We apply the x and y values to the square element using CSS position. This is the 'moving' action.
$(this).css('left', x+'px'), $(this).css('top', y+'px'),
You may have realized that, at the top of this example, there is one line as follows:
var touch = e.originalEvent.touches[0] || e.originalEvent.changedTouches[0];
Now you might be wondering what it does. Mobile Safari does not allow the e.touches
and e.changedTouches
properties on event objects to be copied to another object. You can get around this issue by using e.originalEvent
. You could read more about it here:
http://www.the-xavi.com/articles/trouble-with-touch-events-jquery.
jQuery mobile is a set of components. If you want to dig into all the mobile-related events, you can find them at:
https://github.com/shichuan/jquery-mobile/blob/master/js/jquery.mobile.event.js.
Zepto is a more lightweight alternative to jQuery that you could consider using if your main target is WebKit-based browsers. You can find out more about it at:
On mobile browsers, if your site is built based on a fluid layout, it shouldn't be affected by orientation change. But for a highly interactive site, sometimes you may want to handle orientation change in a special way.
Now let's start creating the HTML and Script to detect and handle orientation event.
Enter the following code:
<!doctype html> <html> <head> <title>Mobile Cookbook</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> html, body { padding: none; margin: none; } </style> <link rel="stylesheet" href="http://code.jquery.com/mobile/1.0/jquery.mobile-1.0.min.css" /> <script src="http://code.jquery.com/jquery-1.6.4.min.js"></script> <script src="http://code.jquery.com/mobile/1.0/jquery.mobile-1.0.min.js"></script> </head> <body> <div id="a"> </div> <script> var metas = document.getElementsByTagName('meta'), var i; if (navigator.userAgent.match(/iPhone/i)) { for (i=0; i<metas.length; i++) { if (metas[i].name == "viewport") { metas[i].content = "width=device-width, minimum-scale=1.0, maximum-scale=1.0"; } } document.addEventListener("gesturestart", gestureStart, false); } function gestureStart() { for (i=0; i<metas.length; i++) { if (metas[i].name == "viewport") { metas[i].content = "width=device-width, minimum-scale=0.25, maximum-scale=1.6"; } } } </script> <script> $(window).bind('orientationchange',function(event){ updateOrientation(event.orientation); }) function updateOrientation(orientation) { $("#a").html("<p>"+orientation.toUpperCase()+"</p>"); } </script> </body> </html>
Now, render this code in your mobile browser and rotate the screen to view in both portrait and landscape mode. In portrait mode, the text output will be 'PORTAIT'.
When we rotate the screen to landscape mode, the text will be 'LANDSCAPE'.
By listening to window.onorientationchange
event, we could get the orientationchange
event, when it occurs; we get the event.orientation
parsed to the function to output the result.
At times, you may want to lock the orientation of the screen if let's say when building a game. For a native application, this can be easy, but for a web app, this can be a bit difficult to achieve.
Let's create a one-page screen that locks to only landscape mode. Note that this is a proof-of-concept, and to create really sophisticated apps or game requires more calculation and handling.
Create a document and name it ch03r02_b.html
, and enter the following code
<!doctype html> <html> <head> <title>Mobile Cookbook</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="css/style.css"> <style> body { font-family: 'Kranky', serif; font-size: 36px; font-style: normal; font-weight: 400; word-spacing: 0em; line-height: 1.2; } html { background:#F1F2CE; } html, body, #screen { padding:0; margin:0; } #screen { text-align:center; -moz-transform:rotate(90deg); -webkit-transform:rotate(90deg); -o-transform:rotate(90deg); -ms-transform:rotate(90deg); } #screen div { padding-top:130px; } @media screen and (min-width: 321px){ #screen { text-align:center; -moz-transform:rotate(0deg); -webkit-transform:rotate(0deg); -o-transform:rotate(0deg); -ms-transform:rotate(0deg); } #screen div { padding-top:70px; } } </style> </head> <body> <div id="screen"> <div id="loader">enter the game</div> </div> <script> var metas = document.getElementsByTagName('meta'), var i; if (navigator.userAgent.match(/iPhone/i)) { for (i=0; i<metas.length; i++) { if (metas[i].name == "viewport") { metas[i].content = "width=device-width, minimum-scale=1.0, maximum-scale=1.0"; } } document.addEventListener("gesturestart", gestureStart, false); } function gestureStart() { for (i=0; i<metas.length; i++) { if (metas[i].name == "viewport") { metas[i].content = "width=device-width, minimum-scale=0.25, maximum-scale=1.6"; } } } window.onorientationchange = function() { update(); } function update() { switch(window.orientation) { case 0: // Portrait case 180: // Upside-down Portrait var cWidth = window.innerWidth; var cHeight = window.innerHeight; document.getElementById("screen").style.width = cHeight-36+'px'; document.getElementById("screen").style.height = cWidth+'px'; break; case -90: // Landscape: turned 90 degrees counter-clockwise case 90: // Landscape: turned 90 degrees clockwise var cWidth = window.innerWidth; var cHeight = window.innerHeight; document.getElementById("screen").style.width = "100%"; document.getElementById("screen").style.height = "auto"; break; } } update(); </script> </body> </html>
Now if you render the page in your browser, you will see the following screen. In portrait mode, it suggests to the user the game/application is designed to be viewed in landscape mode:
When you rotate the screen from portrait to landscape mode, it looks normal:
In this example, we used transform:rotate
from CSS3 to rotate the screen to 90 degrees when viewed in portrait mode:
#screen { text-align:center; -moz-transform:rotate(90deg); -webkit-transform:rotate(90deg); -o-transform:rotate(90deg); -ms-transform:rotate(90deg); }
The mode the user is in can be determined by window.orientation
. There are four values: -90, 0, 90, 180. The device is in landscape mode when the degree is -90 and 90. And it's in portrait mode when the degree is 0 and 180.
switch(window.orientation) { case 0: // Portrait case 180: // Upside-down Portrait //... break; case -90: // Landscape: turned 90 degrees counter-clockwise case 90: // Landscape: turned 90 degrees clockwise //... break; }
With this, you can determine the orientation of the screen.
For the official reference, you could visit Safari's online guide at:
Although mobile web is catching up, if you are developing a highly interactive application, always keep in mind that even the slowest native app still performs faster than an HTML app. If you are deciding to use HTML5 to build an app, you also have to keep all the hacks and browser inconsistencies in mind.
Target device: iOS, Android, Symbian
On Mobile Safari, you can detect the degrees of rotation when people use two fingers to do a rotation on the screen. Because of that, we can use our fingers to rotate an element on the screen!
Add the following code to ch03r03.html
and render it in your mobile browser:
<!doctype html> <html> <head> <title>Mobile Cookbook</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0"> <style> #main { text-align:center; } #someElm { margin-top:50px; margin-left:50px; width: 200px; height: 200px; background:#ccc; position:absolute; } </style> </head> <body> <div id="main"> <div id="someElm"> </div> </div> <script> var rotation =0 ; var node = document.getElementById('someElm'), node.ongesturechange = function(e){ var node = e.target; //alert(e.rotation); // scale and rotation are relative values, // so we wait to change our variables until the gesture ends node.style.webkitTransform = "rotate(" + ((rotation + e.rotation) % 360) + "deg)"; //alert("rotate(" + ((rotation + e.rotation) % 360) + "deg)"); } node.ongestureend = function(e){ // Update the values for the next time a gesture happens rotation = (rotation + e.rotation) % 360; } </script> </body> </html>
Now use two fingers to rotate the box and you will see something like this:
In this example, we rotate the element when there is an ongesturechange
event triggered. We get the rotation degree by using the following value:
e.target.rotation
You may have noticed that we also listen to ongestureend
event, because if the user has previously rotated, this script will remember the last rotated angle and continue to rotate from there.
For the official reference, you could visit Safari's online guide at:
In this example, we used CSS3's transforms feature. You can find more information about WebKit and CSS transform at WebKit's blog at:
One of the common features of mobile devices is swiping. When you browse photos in your photo gallery, you swipe left and right to navigate from one picture to another. On Android devices, you swipe down to unlock the phone. On a mobile browser, you can use swipe as well.
Enter the following code:
<!doctype html> <html> <head> <title>Mobile Cookbook</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> html, body { padding:0; margin:10px auto; } #checkbox { border:5px solid #ccc; width:30px; height:30px; } #wrapper { width:210px; height:100px; position:relative; overflow:hidden; margin:0 auto; } #inner { position:absolute; width:630px; } #inner div { width:200px; height:100px; margin:0 5px; background:#ccc; float:left; } .full-circle { background-color: #ccc; height: 10px; -moz-border-radius:5px; -webkit-border-radius: 5px; width: 10px; float:left; margin:5px; } .cur { background-color: #555; } #btns { width:60px; margin:0 auto; } </style> </head> <body> <div id="main"> <div id="wrapper"> <div id="inner"> <div></div> <div></div> <div></div> </div> </div> <div id="btns"> <div class="full-circle cur"></div> <div class="full-circle"></div> <div class="full-circle"></div> </div> </div> <script src="http://code.jquery.com/jquery-1.5.2.min.js"></script> <script src="http://code.jquery.com/mobile/1.0a4.1/jquery.mobile-1.0a4.1.min.js"></script> <script> var curNum = 0; $('#wrapper').swipeleft(function () { $('#inner').animate({ left: '-=210' }, 500, function() { // Animation complete. curNum +=1; $('.full-circle').removeClass('cur'), $('.full-circle').eq(curNum).addClass('cur'), }); }); $('#wrapper').swiperight(function () { $('#inner').animate({ left: '+=210' }, 500, function() { // Animation complete. curNum -=1; $('.full-circle').removeClass('cur'), $('.full-circle').eq(curNum).addClass('cur'), }); }); </script> </body> </html>
Once you've entered the code in the page, swipe left and right of the viewing area, and you can see the boxes being scrolled horizontally:
We have used a couple of HTML5 techniques in this example. First, we used jQuery Mobile to detect the swipe event. When we use our finger to swipe the page to the left or right, an event listener is assigned:
$('#wrapper').swipeleft(function () { }); $('#wrapper').swiperight(function () { });
When the swipe events are detected, jQuery animation .animate()
is used to create the moving effect:
$('#inner').animate({ left: '+=210' }, 500, function() { // Animation complete. curNum -=1; $('.full-circle').removeClass('cur'), $('.full-circle').eq(curNum).addClass('cur'), });
In this example, we used a CSS3 technique for the circle buttons. You can draw an entire circle using just pure CSS3:
.full-circle { background-color: #ccc; height: 10px; -moz-border-radius:5px; -webkit-border-radius: 5px; border-radius: 5px; width: 10px; }
In this example, we define the width and height of the document to be 10 px, and the border radius to be 5 px. Now you can have a perfect circle in just a couple of lines of CSS!
You can use the Zepto framework to do something similar. It has events such as swipe, swipeLeft, swipeRight, swipeUp, swipeDown
.
YUI has gesture events which you can use to create swipe events. You can read more about this here: Supporting A Swipe Left Gesture:
http://yuiblog.com/sandbox/yui/3.3.0pr3/examples/event/ swipe-gesture.html
Events in jQuery mobile are built in a modular way. Those who want to learn how jQuery made the swipe event can visit:
https://github.com/jquery/jquery-mobile/blob/master/js/jquery.mobile.event.js. The part related to swipe events is under:
$.event.special.swipe = {...}
Vertical, horizontal, and distance threshold are calculated for the event calculation.
On the iPhone, you can resize an element based on zoom detection. On gesture change, you could get the value of the scale factor, and zoom HTML elements based on it.
Enter the following code:
<!doctype html> <html> <head> <title>Mobile Cookbook</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> <style> #frame { width:100px; height:100px; background:#ccc; } </style> </head> <body> <div id="main"> <div id="frame"></div> </div> <script src="http://code.jquery.com/jquery-1.5.2.min.js"></script> <script src="http://code.jquery.com/mobile/1.0a4.1/jquery.mobile-1.0a4.1.min.js"></script> <script> var width = 100, height = 100; var node = document.getElementById('frame'), node.ongesturechange = function(e){ var node = e.target; // scale and rotation are relative values, // so we wait to change our variables until the gesture ends node.style.width = (width * e.scale) + "px"; node.style.height = (height * e.scale) + "px"; } node.ongestureend = function(e){ // Update the values for the next time a gesture happens width *= e.scale; height *= e.scale; } </script> </body> </html>
In this example, we assign the element we want to scale with the ongesturechange
event. The scale factor is determined by the e.target.scale:
width *= e.scale; height *= e.scale;
Gesture events can be tricky, so using them properly is very important. For a two finger multi-touch gesture, the events occur in the following sequence:
touchstart
for finger 1. Sent when the first finger touches the surface.
gesturestart
. Sent when the second finger touches the surface.
touchstart
for finger 2. Sent immediately after gesturestart
when the second finger touches the surface.
gesturechange
for current gesture. Sent when both fingers move while still touching the surface.
gestureend
. Sent when the second finger lifts from the surface.
touchend
for finger 2. Sent immediately after gestureend
when the second finger lifts from the surface.
touchend
for finger 1. Sent when the first finger lifts from the surface.
YUI from Yahoo! has a cross-browser solution for gesture events, but only supports one-finger events. You can find out more about it at: