In this tutorial lets get touchy feely with JavaScript, but examining its
touch related events and how they are used to detect and respond to touch
and swipe events. With touch based devices ever growing in numbers, grasping
these events is as essential as understanding the age old mouse events.
Examples in this tutorial can be appreciated in both touch and non-touch
enabled devices, with the later falling back to your trusted mouse instead.
Ready to give those fingers a bit of a workout? Lets go!
JavaScript Touch Events
So lets dive right into it. The following lists the supported
touch events in JavaScript:
JavaScript Touch Events
Event Name |
Description |
touchstart |
Triggers when the user makes contact with the touch
surface and creates a touch point inside the element the event is bound to. |
touchmove |
Triggers when the user moves the touch point across the
touch surface. |
touchend |
Triggers when the user removes a touch point from the
surface. It fires regardless of whether the touch point is removed while
inside the bound-to element, or outside, such as if the user's finger
slides out of the element first or even off the edge of the screen. |
touchenter |
Triggers when the touch point enters the bound-to
element. This event does not bubble. |
touchleave |
Triggers when the touch point leaves the bound-to
element. This event does not bubble. |
touchcancel |
Triggers when the touch point no longer registers on the
touch surface. This can occur if the user has moved the touch point
outside the browser UI or into a plugin, for example, or if an alert
modal pops up. |
These events can be attached to any element on the page, and
is passed an event object containing details about the touch point, such as
its coordinates on the page. Use element.addEventListener()
to
attach the event(s), for example to the BODY of the page:
window.addEventListener( 'load' , function (){ |
document.body.addEventListener( 'touchstart' , function (e){ |
alert(e.changedTouches[0].pageX) |
Here I've attached the "
touchstart
" event to
document.body
once the page has loaded (you may want to do this
on
DOMContentLoaded
instead). Inside the anonymous function for
touchstart
, we look at the
changedTouches
object
of the
Event object, which contains information on each touch point
initiated by that touch event on the touch surface. Here we're only interested in the first
touch point (ie: finger) that has made contact, specifically, its
pageX
coordinate on the page when the touch is made.
The
Event object whenever a touch
event is fired holds a wealth of information about the touch action; you
already saw its
changedTouches
object, which contains information on touch points
changed since the last touch event . Lets take the above example a bit
further now, by bringing in the
touchmove
and
touchend
events to show the
distance traveled by a touch action from beginning to end on a DIV, from a
finger touching down on an object to lifting up.
Example (mouse simulation added for non touch devices):
Touch Me!
Status: touchend
Resting x coordinate: 1033px
Touch then move your finger to see the current state of the
touch and the distance traveled. The HTML markup for the DIV consists simply
of:
<div class= "box" id= "box1" > |
<h3 id= "statusdiv" >Status</h3> |
The script looks like this:
window.addEventListener( 'load' , function (){ |
var box1 = document.getElementById( 'box1' ) |
var statusdiv = document.getElementById( 'statusdiv' ) |
box1.addEventListener( 'touchstart' , function (e){ |
var touchobj = e.changedTouches[0] |
startx = parseInt(touchobj.clientX) |
statusdiv.innerHTML = 'Status: touchstart<br /> ClientX: ' + startx + 'px' |
box1.addEventListener( 'touchmove' , function (e){ |
var touchobj = e.changedTouches[0] |
var dist = parseInt(touchobj.clientX) - startx |
statusdiv.innerHTML = 'Status: touchmove<br /> Horizontal distance traveled: ' + dist + 'px' |
box1.addEventListener( 'touchend' , function (e){ |
var touchobj = e.changedTouches[0] |
statusdiv.innerHTML = 'Status: touchend<br /> Resting x coordinate: ' + touchobj.clientX + 'px' |
A few points worth mentioning here:
-
We call event.preventDefault()
to prevent the default
action associated with each event from occurring. In the case of
touchstart
and touchend
for instance, if the
bound-to element was a link, not suppressing the default action would
cause the browser to navigate to the link, cutting short our custom
sequence of actions. In the case of touchmove
, calling
event.preventDefault()
stops the browser from scrolling the
page when the user is moving his finger inside the bound-to element.
-
Once again, we access the first element inside
event.changedTouches[]
for each of the touch events to reference
the first touch point made to the element (there could be multiple
fingers used), and examine the clientX
property to get the
horizontal coordinate of the touch point relative to the left edge of
the browser (not including any scroll offsets). This property is
adequate for what we're trying to do here, which is simply to get the
relative distance traveled while a touch is maintained on the element.
-
To get the distance traveled between touchstart
and touchend
events, we define a startx
variable at the touchstart
phase that gets the starting
clientX
position of the touch. Then throughout the
touchmove
event, we get the clientX
position of the
touch and subtract from it the startx
value to get the
distance traveled while the touch point is maintained.
-
Notice how the touchend
event is still
fired and displays the final resting x coordinates even if your finger is outside the bound-to element at the time of
lifting up your finger.
The object event.changedTouches[]
is just one
of numerous properties of the Event object that gets populated during touch
related events. It's high time to look at this object in detail now.
Event object during Touch
The
Event object is this mystical unicorn in JavaScript that contains
information on an event when it occurs, whether it's the URL of a link in an
onclick
event, the
keyCode of the key pressed in an
onkeypress
event etc. With touch related events, the Event object is
populated with a slew of unique properties that give us insight into all
aspects of the touch point, from how many fingers (or toes for that matter
etc) touched down on the touch surface to their precise coordinates on the
screen.
Event Object during Touch
Property |
Description |
altkey |
Boolean indicating whether the alt key was pressed at
time of touch event. |
changedTouches |
A list of Touch objects representing each touch point
directly involved in this event. Specifically:
- In
touchstart , it contains a list of fingers that have made
contact with the touch surface during this touchstart event.
- In
touchmove , it contains a list of fingers that have moved
during this touchmove event.
- In
touchend , it contains a list of fingers that have just been
removed from the touch surface during this touchend event.
- In
touchenter , it contains a list of fingers that have
entered the touch surface during this touchenter event.
- In
touchleave , it contains a list of fingers that have
exited the touch surface during this touchleave event.
You can use the length property to get the number of Touch objects
inside changedTouches[] . |
ctrlKey |
Boolean indicating whether the crtrl key was pressed at
time of touch event. |
metaKey |
Boolean indicating whether the meta key was pressed at
time of touch event. |
shiftKey |
Boolean indicating whether the shift key was pressed at
time of touch event. |
targetTouches |
A list of touch points currently making contact with the
touch surface AND started out from the same element that is the
target of this event.
For example, lets say you bind the touchstart event to a DIV and place
two fingers down on the surface. targetTouches will only contain
information on the finger(s) placed inside the DIV, and not any outside.
You can use the length property to get the number of Touch objects
inside targetTouches[] . |
touches |
A list of Touch objects representing all touch points
currently in contact with the touch surface, regardless of which element
a touch point is on at the moment. |
type |
The type of event that triggered the Event object, such
as touchstart , touchmove , etc. |
target |
The target element of the touches associated with this
event. |
So for example, during a touchstart
event, the Event
object's touches
property lets us access all touch points currently in
contact with touch surface in general
document.body.addEventListener( 'touchstart' , function (e){ |
var touchlist = e.touches |
for ( var i=0; i<touchlist.length; i++){ |
The Event object's three properties evt.changedTouches
,
evt.targetTouches
, and evt.touches
are all list objects containing a
list of Touch objects, one Touch object for each touch point made. It is
through a Touch object you get details about a specific touch point, such as
its coordinates on the screen, its unique identifier to help you identify
which touch point is which, and so on. You saw in the beginning some code
that accesses a Touch object contained inside evt.changedTouches
:
box1.addEventListener( 'touchstart' , function (e){ |
var touchobj = e.changedTouches[0] |
startx = parseInt(touchobj.clientX) |
Here e.changedTouches[0]
is a Touch object, with
clientX
being one property of the Touch object. Lets take a formal look at the Touch
object now:
Touch object
Property |
Description |
identifier |
An value to help uniquely identify each touch point
currently in contact with the touch surface. The value starts at 0 for
the first unique touch point on the surface, 1 for the second etc. This
value is maintained for each touch point until the user's finger is
lifted off the surface.Lets say the user puts two fingers down on an
element. Each finger at this point is assigned a unique identifier. When
you move the fingers, you can use each touch point's identifier to
identify which touch point is which. |
screenX |
The x coordinate of the touch point relative to the left
edge of the user's screen. |
screenY |
The y coordinate of the touch point relative to the top
edge of the user's screen. |
clientX |
The x coordinate of the touch point relative to the left
edge of the viewport, not including scroll offsets. |
clientY |
The y coordinate of the touch point relative to the top
edge of the viewport, not including scroll offsets. |
pageX |
The x coordinate of the touch point relative to the left
edge of the viewport, including scroll offsets. |
pageY |
The y coordinate of the touch point relative to the top
edge of the viewport, including scroll offsets. |
radiusX |
The radius of the ellipse which most closely defines the
touching area (e.g. finger, stylus) along the x-axis. |
radiusY |
The radius of the ellipse which most closely defines the
touching area (e.g. finger, stylus) along the y-axis. |
rotationAngle |
The angle (in degrees) that the ellipse described by
radiusX and radiusY is rotated clockwise about its center. |
force |
Returns the force of the touch point in the form of an
integer between 0 and 1, where 0 is no force as detected by the device,
and 1, the highest. |
target |
The target element of the touch point; in other words,
the element the touch point landed on, which may be different
from the element its corresponding touch event was originally bounded
to. In the following, this always returns the BODY element, while
Touch.target returns the element the finger actually touched down on, which
could be a DIV, a SPAN etc:
document.body.addEventListener( 'touchstart' , function (e){ |
var touchobj = e.changedTouches[0] |
console.log( this .tagName) |
console.log(touchobj.target) |
|
The properties of the Touch object you'll most frequently be
accessing are those relating to coordinates, to help you determine where,
and with a little Math, in what direction and how fast a touch action is
performed.
Lets rewind now back to the Event object and talk a bit more
about the Touches
, changedTouches
, and targetTouches
properties, to help
more clearly explain their differences:
-
Touches: A list of all touch points currently making
contact with the touch surface.
-
changedTouches: A list of touch points involved in this
event. For example, in a touchmove
event,
changedTouches
contains only a list of touch points that are currently
moving, whereas Touches
would contain all touch points
currently on the surface.
-
targetTouches: A list of touch points currently making
contact with the touch surface AND started out from the same
element that is the target of this event.
For example, lets say you bind the touchstart
event to a DIV and place
two fingers down on the surface. targetTouches
will only contain
information on the finger(s) placed inside the DIV, and not any outside.
Andrei at
Stackoverflow gave a very illuminating example that clarifies the subtle
differences between these three properties:
- When I put a finger down, all three lists will have the same
information. It will be in changedTouches because putting the finger
down is what caused the event
- When I put a second finger down, touches will have two items, one
for each finger. targetTouches will have two items only if the finger
was placed in the same node as the first finger. changedTouches will
have the information related to the second finger, because it’s what
caused the event
- If I put two fingers down at exactly the same time, it’s possible to
have two items in changedTouches, one for each finger
- If I move my fingers, the only list that will change is
changedTouches and will contain information related to as many fingers
as have moved (at least one).
- When I lift a finger, it will be removed from touches, targetTouches
and will appear in changedTouches since it’s what caused the event
- Removing my last finger will leave touches and targetTouches empty,
and changedTouches will contain information for the last finger
Moving an object using touch
Using touch
to move a DIV horizontally or vertically across the screen is very simple.
Take a look at the below, which moves a DIV horizontally across a track when
touched and dragged:
Example (mouse simulation added for non touch devices):
window.addEventListener( 'load' , function (){ |
var box2 = document.getElementById( 'box2' ), |
box2.addEventListener( 'touchstart' , function (e){ |
touchobj = e.changedTouches[0] |
boxleft = parseInt(box2.style.left) |
startx = parseInt(touchobj.clientX) |
box2.addEventListener( 'touchmove' , function (e){ |
touchobj = e.changedTouches[0] |
var dist = parseInt(touchobj.clientX) - startx |
box2.style.left = ( (boxleft + dist > 380)? 380 : (boxleft + dist < 0)? 0 : boxleft + dist ) + 'px' |
<div id= "track" class= "track" > |
<div id= "box2" style= "left:0; top:0" >Drag Me</div> |
The outer #track
DIV is a relatively positioned element,
while the #box2
DIV contained inside is absolutely positioned.
We get #box2
DIV's initial left position and the x coordinate of the touch point at the
touchstart
event. Note I'm using touchobj.clientX
here; we could have easily
used touchobj.pageX
instead, it doesn't matter, since we're only using this
property to help ascertain the relative distance traveled by touch point.
During the touchmove
event, we calculate the distance
traveled by the moving touch point, by getting its current x coordinate and
subtracting from that the initial x coordinate. Then, to move the #box2
DIV,
we add that distance to the DIV's initial left position, throwing in a lower
and upper limit of 0 and 380px, so to prevent the DIV from moving outside
the parent DIV. And with that our DIV box now moves with our finger!
Detecting a swipe (left, right, top or down) using touch
Swiping in touch is the act of quickly moving your finger
across the touch surface in a certain direction. There is currently no "onswipe
" event
in JavaScript, which means it's up to us to implement one using the
available touch events, plus define just when a swipe is a, well, "swipe".
Lets first define when a movement across the touch surface
should be considered a swipe. There are two variables at play here- the
distance traveled by the user's finger on the x or y-axis from touchstart
to
touchend
, and, the time it took. Based on these two factors, we can decide
whether that action qualifies as a swipe and in what direction.
With that said, lets put ideas into action and see how to
go about detecting a swipe right (from left to right). Once we can do that,
detecting swipe in the other 3 directions is pretty much identical. For this exercise we'll stipulate that a right swipe has
occurred when the user has moved his finger across the touch surface a
minimum of 150px horizontally in 200 ms or less from left to right.
Furthermore, there should be no more than 100px traveled vertically, to
avoid "false positives" whereby the user swipes diagonally across, which we
don't want to qualify as a swipe right.
Example (mouse simulation added for non touch devices):
Swipe Me
window.addEventListener( 'load' , function (){ |
var touchsurface = document.getElementById( 'touchsurface' ), |
function handleswipe(isrightswipe){ |
touchsurface.innerHTML = 'Congrats, you\'ve made a <span style="color:red">right swipe!</span>' |
touchsurface.innerHTML = 'Condition for right swipe not met yet' |
touchsurface.addEventListener( 'touchstart' , function (e){ |
touchsurface.innerHTML = '' |
var touchobj = e.changedTouches[0] |
startTime = new Date().getTime() |
touchsurface.addEventListener( 'touchmove' , function (e){ |
touchsurface.addEventListener( 'touchend' , function (e){ |
var touchobj = e.changedTouches[0] |
dist = touchobj.pageX - startX |
elapsedTime = new Date().getTime() - startTime |
var swiperightBol
= (elapsedTime <= allowedTime && dist >= threshold
&& Math.abs(touchobj.pageY - startY) <= 100) |
handleswipe(swiperightBol) |
<div id= "touchsurface" >Swipe Me</div> |
Inside touchend
, we check that the dist traveled from
touchstart
to touchend
is a positive number above the specified threshold
value (ie: 150), since in a right swipe, that dist should always be positive
based on the equation used (versus negative for a left swipe). At the same
time, we make sure any vertical lateral movement traveled is less than 100px
to weed out diagonal swipes. Since the vertical movement can occur either above
the starting touch point or below, we use Math.abs()
when getting the
absolute vertical dist traveled so both scenarios are covered when comparing it to our
vertical threshold value of 100.
A generic swipe detecting function
Now that we
got right swipe down, lets create a more generic function that detects
swiping in either of the 4 directions (left, right, up, or down):
function swipedetect(el, callback){ |
handleswipe = callback || function (swipedir){} |
touchsurface.addEventListener( 'touchstart' , function (e){ |
var touchobj = e.changedTouches[0] |
startTime = new Date().getTime() |
touchsurface.addEventListener( 'touchmove' , function (e){ |
touchsurface.addEventListener( 'touchend' , function (e){ |
var touchobj = e.changedTouches[0] |
distX = touchobj.pageX - startX |
distY = touchobj.pageY - startY |
elapsedTime = new Date().getTime() - startTime |
if (elapsedTime <= allowedTime){ |
if (Math.abs(distX) >= threshold && Math.abs(distY) <= restraint){ |
swipedir = (distX < 0)? 'left' : 'right' |
else if (Math.abs(distY) >= threshold && Math.abs(distX) <= restraint){ |
swipedir = (distY < 0)? 'up' : 'down' |
swipedetect()
accepts two parameters, the element to bind
the touch events to, plus a function to execute when a swipe has occurred.
The function parameter "swipedir
" tells you the type of swipe that was just
made with five possible values: "none", "left", "right", "top", or "down".
The below uses the swipedetect()
function to show a "left", "right", "top", or "down"
background image (overlaid on top of a default background image) depending
on the swipe that has just occurred:
Example (mouse simulation added for non touch devices):
The code used is:
window.addEventListener( 'load' , function (){ |
var el = document.getElementById( 'touchsurface2' ) |
var inner = document.getElementById( 'inner' ) |
swipedetect(el, function (swipedir){ |
var bgimage = swipedir + 'arrow.png' |
inner.style.background = 'transparent url(' + bgimage + ') center center no-repeat' |
hidetimer = setTimeout( function (){ |
inner.style.background = '' |
The HTML markup is:
We bind swipedetect()
to "#touchsurface2
",
and whenever a valid swipe has occurred inside it, we change the "#inner
"
DIV's background image accordingly to reflect the type of swipe that has
just occurred.
Monitoring touch actions at every stage, swipe image gallery
On the previous page, you saw how to detect swipes on a
touch surface, and packaged that knowledge into a generic swipedetect()
function:
swipedetect(el, function (swipedir){ |
swipedir contains either "none" , "left" , "right" , "top" , or "down" |
alert( 'You just swiped left!' ) |
That's all fine and dandy, but swipedetect()
is limited in
that it only lets us react to after a swipe has been made, and not during,
or as the user is moving his finger across the touch surface. The later is
useful in applications that need to react in tandem to a touch movement
across the touch surface, such as an image gallery whereby the user can drag
his finger to get a preview of the next or previous slide.
A generic ontouch function
Lets set out to create a generic ontouch()
function that can
be used to execute custom code at every step of a touch action, from the
finger's initial contact with the surface, movement across, to lifting the
finger up to end it. Surprisingly it's not much different from our swipedetect()
function, only that we'll be scrutinizing the
touchmove
event
more closely this time:
function ontouch(el, callback){ |
handletouch = callback || function (evt, dir, phase, swipetype, distance){} |
touchsurface.addEventListener( 'touchstart' , function (e){ |
var touchobj = e.changedTouches[0] |
startTime = new Date().getTime() |
handletouch(e, 'none' , 'start' , swipeType, 0) |
touchsurface.addEventListener( 'touchmove' , function (e){ |
var touchobj = e.changedTouches[0] |
distX = touchobj.pageX - startX |
distY = touchobj.pageY - startY |
if (Math.abs(distX) > Math.abs(distY)){ |
dir = (distX < 0)? 'left' : 'right' |
handletouch(e, dir, 'move' , swipeType, distX) |
dir = (distY < 0)? 'up' : 'down' |
handletouch(e, dir, 'move' , swipeType, distY) |
touchsurface.addEventListener( 'touchend' , function (e){ |
var touchobj = e.changedTouches[0] |
elapsedTime = new Date().getTime() - startTime |
if (elapsedTime <= allowedTime){ |
if (Math.abs(distX) >= threshold && Math.abs(distY) <= restraint){ |
else if (Math.abs(distY) >= threshold && Math.abs(distX) <= restraint){ |
handletouch(e, dir, 'end' , swipeType, (dir == 'left' || dir == 'right' )? distX : distY) |
During each phase of a touch action- "start", "move", and
"end", we get things like the direction of the touch point, distance
traveled, and at the "end" phase, whether the touch movement constituted a
swipe in one of the four directions, based on the same rules as that defined
inside swipedetect()
we saw earlier. The following illustrates
ontouch
in
action to show various info about a touch action over a DIV:
Example (mouse simulation added for non touch devices):
Dir: down
Phase: end
Swipe Type: none
Distance: 210
window.addEventListener( 'load' , function (){ |
var el = document.getElementById( 'touchsurface2' ) |
ontouch(el, function (evt, dir, phase, swipetype, distance){ |
touchreport += '<b>Dir:</b> ' + dir + '<br />' |
touchreport += '<b>Phase:</b> ' + phase + '<br />' |
touchreport += '<b>Swipe Type:</b> ' + swipetype + '<br />' |
touchreport += '<b>Distance:</b> ' + distance + '<br />' |
el.innerHTML = touchreport |
A swipe image gallery
With the ontouch
function, we can use it to create a swipe
image gallery that responds to not just swiping for changing a slide, but
dragging to get a preview of the next slide. Take a look at the below:
Example (drag or swipe to move gallery, mouse simulation
added for non touch device):
-moz-transition: all 100ms ease- in -out; |
-webkit-transition: all 100ms ease- in -out; |
transition: all 100ms ease- in -out; |
window.addEventListener( 'load' , function (){ |
var el = document.getElementById( 'swipegallery' ) |
var gallerywidth = el.offsetWidth |
var ul = el.getElementsByTagName( 'ul' )[0] |
var liscount = ul.getElementsByTagName( 'li' ).length, curindex = 0, ulLeft = 0 |
ul.style.width = gallerywidth * liscount + 'px' |
ontouch(el, function (evt, dir, phase, swipetype, distance){ |
ulLeft = parseInt(ul.style.left) || 0 |
else if (phase == 'move' && (dir == 'left' || dir == 'right' )){ |
var totaldist = distance + ulLeft |
ul.style.left = Math.min(totaldist, (curindex+1) * gallerywidth) + 'px' |
else if (phase == 'end' ){ |
if (swipetype == 'left' || swipetype == 'right' ){ |
curindex = (swipetype == 'left' )? Math.min(curindex+1, liscount-1) : Math.max(curindex-1, 0) |
ul.style.left = -curindex * gallerywidth + 'px' |
<div id= "swipegallery" class= "touchgallery" > |
<li><img src= "../script/script2/food1.jpg" /></li> |
<li><img src= "../script/script2/food2.jpg" /></li> |
<li><img src= "../script/script2/food3.jpg" /></li> |
<li><img src= "../script/script2/food4.jpg" /></li> |
<li><img src= "../script/script2/food5.jpg" /></li> |
Markup wise our gallery consists of a relatively positioned
main DIV with an absolutely positioned UL element inside it. Each of the LI
elements (images) are floated left so they appear side by side horizontally.
We set the total width of the UL element to the width of the gallery DIV
multiplied by the number of LI elements inside our script. With such a set
up, to show the 2nd image for example, we'd simply set the left position of
the UL element to (nth image -1) * width of gallery DIV (ie: 1 * 350).
Now to our ontouch
function, which like a magic
wand is what transforms our otherwise static series of images into a touch
and swipe image gallery! We call ontouch()
and pass in a
reference to the gallery's DIV container as the first parameter, or the
element to monitor touch actions on. The anonymous function that follows
lets us respond to all touch actions taking place inside on a granular
level. When the user first touches down on the gallery, we get the current
UL element's left position (0 when page first loads). Then as the user moves
his finger across the touch surface, if the dir
is left or
right we access the distance
parameter to get the distance
traveled horizontally. That value when added to the UL's initial left
position gives us the new left position of the UL, which we then set the UL
to, causing the gallery to move left or right in tandem with the user's
finger movement.
When the user lifts his finger off the gallery (phase equals
"end
"), we use the swipetype
parameter to
determine if a legitimate left or right swipe has occurred (versus just a
slow drag for example). If so, we increment or decrement the current index (curindex
)
of the gallery before moving the gallery to show the new image.
And there you have it. As you can see, implementing touch
friendly UIs in JavaScript is relatively straightforward thanks to the Touch
API
.
No comments :
Post a Comment