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" if (swipedir =='left') 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){ var touchsurface = el, dir, swipeType, startX, startY, distX, distY, threshold = 150, //required min distance traveled to be considered swipe restraint = 100, // maximum distance allowed at the same time in perpendicular direction allowedTime = 500, // maximum time allowed to travel that distance elapsedTime, startTime, handletouch = callback || function(evt, dir, phase, swipetype, distance){} touchsurface.addEventListener('touchstart', function(e){ var touchobj = e.changedTouches[0] dir = 'none' swipeType = 'none' dist = 0 startX = touchobj.pageX startY = touchobj.pageY startTime = new Date().getTime() // record time when finger first makes contact with surface handletouch(e, 'none', 'start', swipeType, 0) // fire callback function with params dir="none", phase="start", swipetype="none" etc e.preventDefault() }, false) touchsurface.addEventListener('touchmove', function(e){ var touchobj = e.changedTouches[0] distX = touchobj.pageX - startX // get horizontal dist traveled by finger while in contact with surface distY = touchobj.pageY - startY // get vertical dist traveled by finger while in contact with surface if (Math.abs(distX) > Math.abs(distY)){ // if distance traveled horizontally is greater than vertically, consider this a horizontal movement dir = (distX < 0)? 'left' : 'right' handletouch(e, dir, 'move', swipeType, distX) // fire callback function with params dir="left|right", phase="move", swipetype="none" etc } else{ // else consider this a vertical movement dir = (distY < 0)? 'up' : 'down' handletouch(e, dir, 'move', swipeType, distY) // fire callback function with params dir="up|down", phase="move", swipetype="none" etc } e.preventDefault() // prevent scrolling when inside DIV }, false) touchsurface.addEventListener('touchend', function(e){ var touchobj = e.changedTouches[0] elapsedTime = new Date().getTime() - startTime // get time elapsed if (elapsedTime <= allowedTime){ // first condition for awipe met if (Math.abs(distX) >= threshold && Math.abs(distY) <= restraint){ // 2nd condition for horizontal swipe met swipeType = dir // set swipeType to either "left" or "right" } else if (Math.abs(distY) >= threshold && Math.abs(distX) <= restraint){ // 2nd condition for vertical swipe met swipeType = dir // set swipeType to either "top" or "down" } } // Fire callback function with params dir="left|right|up|down", phase="end", swipetype=dir etc: handletouch(e, dir, 'end', swipeType, (dir =='left' || dir =='right')? distX : distY) e.preventDefault() }, false) } // USAGE: /* ontouch(el, function(evt, dir, phase, swipetype, distance){ // evt: contains original Event object // dir: contains "none", "left", "right", "top", or "down" // phase: contains "start", "move", or "end" // swipetype: contains "none", "left", "right", "top", or "down" // distance: distance traveled either horizontally or vertically, depending on dir value if ( phase == 'move' && (dir =='left' || dir == 'right') ) console.log('You are moving the finger horizontally by ' + distance) }) */
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):
<script> window.addEventListener('load', function(){ var el = document.getElementById('touchsurface2') ontouch(el, function(evt, dir, phase, swipetype, distance){ var touchreport = '' 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 }) }, false) </script> <div id="touchsurface2"> </div>
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):
<style> .touchgallery{ position: relative; overflow: hidden; width: 350px; /* default gallery width */ height: 270px; /* default gallery height */ background: #eee; } .touchgallery ul{ list-style: none; margin: 0; padding: 0; left: 0; position: absolute; -moz-transition: all 100ms ease-in-out; /* image transition. Change 100ms to desired transition duration */ -webkit-transition: all 100ms ease-in-out; transition: all 100ms ease-in-out; } .touchgallery ul li{ float: left; display: block; width: 350px; text-align: center; } .touchgallery ul li img{ /* CSS for images within gallery */ max-width: 100%; /* make each image responsive, so its native width can occupy up to 100% of gallery's width, but not beyond */ height: auto; } </style> <script> window.addEventListener('load', function(){ var el = document.getElementById('swipegallery') // reference gallery's main DIV container 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' // set width of gallery to parent container's width * total images ontouch(el, function(evt, dir, phase, swipetype, distance){ if (phase == 'start'){ // on touchstart ulLeft = parseInt(ul.style.left) || 0 // initialize ulLeft var with left position of UL } else if (phase == 'move' && (dir =='left' || dir =='right')){ // on touchmove and if moving left or right var totaldist = distance + ulLeft // calculate new left position of UL based on movement of finger ul.style.left = Math.min(totaldist, (curindex+1) * gallerywidth) + 'px' // set gallery to new left position } else if (phase == 'end'){ // on touchend if (swipetype == 'left' || swipetype == 'right'){ // if a successful left or right swipe is made curindex = (swipetype == 'left')? Math.min(curindex+1, liscount-1) : Math.max(curindex-1, 0) // get new index of image to show } ul.style.left = -curindex * gallerywidth + 'px' // move UL to show the new image } }) // end ontouch }, false) </script> <div id="swipegallery" class="touchgallery"> <ul> <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> </ul> </div>
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
- Introduction to Touch events in JavaScript
- Detecting a swipe using touch
- Monitoring touch actions at every stage, swipe image gallery
End of tutorial