{"id":267821,"date":"2018-04-09T06:00:46","date_gmt":"2018-04-09T13:00:46","guid":{"rendered":"http:\/\/css-tricks.com\/?p=267821"},"modified":"2018-09-30T00:33:13","modified_gmt":"2018-09-30T07:33:13","slug":"simple-swipe-with-vanilla-javascript","status":"publish","type":"post","link":"https:\/\/css-tricks.com\/simple-swipe-with-vanilla-javascript\/","title":{"rendered":"Simple Swipe with Vanilla JavaScript"},"content":{"rendered":"

I used to think implementing swipe gestures had to be very difficult, but I have recently found myself in a situation where I had to do it and discovered the reality is nowhere near as gloomy as I had imagined.<\/p>\n

This article is going to take you, step by step, through the implementation with the least amount of code I could come up with. So, let’s jump right into it!<\/p>\n

<\/p>\n

The HTML Structure<\/h3>\n

We start off with a .container<\/code> that has a bunch of images inside:<\/p>\n

<div class='container'>\r\n  <img src='img1.jpg' alt='image description'\/>\r\n  ...\r\n<\/div><\/code><\/pre>\n

Basic Styles<\/h3>\n

We use display: flex<\/code> to make sure images go alongside each other with no spaces in between. align-items: center<\/code> middle aligns them vertically. We make both the images and the container take the width<\/code> of the container’s parent (the body<\/code> in our case).<\/p>\n

.container {\r\n  display: flex;\r\n  align-items: center;\r\n  width: 100%;\r\n  \r\n  img {\r\n    min-width: 100%; \/* needed so Firefox doesn't make img shrink to fit *\/\r\n    width: 100%; \/* can't take this out either as it breaks Chrome *\/\r\n  }\r\n}<\/code><\/pre>\n

The fact that both the .container<\/code> and its child images have the same width<\/code> makes these images spill out on the right side (as highlighted by the red outline) creating a horizontal scrollbar, but this is precisely what we want:<\/p>\n

\"Screenshot
The initial layout (see live demo<\/a>).<\/figcaption><\/figure>\n

Given that not all the images have the same dimensions and aspect ratio, we have a bit of white space above and below some of them. So, we’re going to trim that by giving the .container<\/code> an explicit height<\/code> that should pretty much work for the average aspect ratio of these images and setting overflow-y<\/code> to hidden<\/code>:<\/p>\n

.container {\r\n  \/* same as before *\/\r\n  overflow-y: hidden;\r\n  height: 50vw;\r\n  max-height: 100vh;\r\n}<\/code><\/pre>\n

The result can be seen below, with all the images trimmed to the same height<\/code> and no empty spaces anymore:<\/p>\n

\"Screenshot
The result after images are trimmed by overflow-y<\/code> on the .container<\/code> (see live demo<\/a>).<\/figcaption><\/figure>\n

Alright, but now we have a horizontal scrollbar on the .container<\/code> itself. Well, that’s actually a good thing for the no JavaScript case.<\/p>\n

Otherwise, we create a CSS variable --n<\/code> for the number of images and we use this to make .container<\/code> wide enough to hold all its image children that still have the same width as its parent (the body<\/code> in this case):<\/p>\n

.container {\r\n  --n: 1;\r\n  width: 100%;\r\n  width: calc(var(--n)*100%);\r\n  \r\n  img {\r\n    min-width: 100%;\r\n    width: 100%;\r\n    width: calc(100%\/var(--n));\r\n  }\r\n}<\/code><\/pre>\n

Note that we keep the previous width<\/code> declarations as fallbacks. The calc()<\/code> values won’t change a thing until we set --n<\/code> from the JavaScript after getting our .container<\/code> and the number of child images it holds:<\/p>\n

const _C = document.querySelector('.container'), \r\n      N = _C.children.length;\r\n\r\n_C.style.setProperty('--n', N)<\/code><\/pre>\n

Now our .container<\/code> has expanded to fit all the images inside:<\/p>\n

Layout with expanded container (live demo<\/a>).<\/figcaption><\/figure>\n

Switching Images<\/h3>\n

Next, we get rid of the horizontal scrollbar by setting overflow-x: hidden<\/code> on our container’s parent (the body<\/code> in our case) and we create another CSS variable that holds the index of the currently selected image (--i<\/code>). We use this to properly position the .container<\/code> with respect to the viewport via a translation (remember that %<\/code> values inside translate()<\/code> functions are relative to the dimensions of the element we have set this transform<\/code> on):<\/p>\n

body { overflow-x: hidden }\r\n\r\n.container {\r\n  \/* same styles as before *\/\r\n  transform: translate(calc(var(--i, 0)\/var(--n)*-100%));\r\n}<\/code><\/pre>\n

Changing the --i<\/code> to a different integer value greater or equal to zero, but smaller than --n<\/code>, brings another image into view, as illustrated by the interactive demo below (where the value of --i<\/code> is controlled by a range input):<\/p>\n

See the Pen<\/a> by thebabydino (@thebabydino<\/a>) on CodePen<\/a>.<\/p>\n

Alright, but we don’t want to use a slider to do this.<\/p>\n

The basic idea is that we’re going to detect the direction of the motion between the \"touchstart\"<\/code> (or \"mousedown\"<\/code>) event and the \"touchend\"<\/code> (or \"mouseup\"<\/code>) and then update --i<\/code> accordingly to move the container such that the next image (if there is one) in the desired direction moves into the viewport.<\/p>\n

function lock(e) {};\r\n\r\nfunction move(e) {};\r\n\r\n_C.addEventListener('mousedown', lock, false);\r\n_C.addEventListener('touchstart', lock, false);\r\n\r\n_C.addEventListener('mouseup', move, false);\r\n_C.addEventListener('touchend', move, false);<\/code><\/pre>\n

Note that this will only work for the mouse if we set pointer-events: none<\/code> on the images.<\/p>\n

.container {\r\n  \/* same styles as before *\/\r\n\r\n  img {\r\n    \/* same styles as before *\/\r\n    pointer-events: none;\r\n  }\r\n}<\/code><\/pre>\n

Also, Edge needs to have touch events enabled from about:flags<\/strong> as this option is off by default:<\/p>\n

\"Screenshot
Enabling touch events in Edge.<\/figcaption><\/figure>\n

Before we populate the lock()<\/code> and move()<\/code> functions, we unify the touch and click cases:<\/p>\n

function unify(e) { return e.changedTouches ? e.changedTouches[0] : e };<\/code><\/pre>\n

Locking on \"touchstart\"<\/code> (or \"mousedown\"<\/code>) means getting and storing the x<\/var> coordinate into an initial coordinate variable x0<\/code>:<\/p>\n

let x0 = null;\r\n\r\nfunction lock(e) { x0 = unify(e).clientX };<\/code><\/pre>\n

In order to see how to move our .container<\/code> (or if we even do that because we don’t want to move further when we have reached the end), we check if we have performed the lock()<\/code> action, and if we have, we read the current x<\/var> coordinate, compute the difference between it and x0<\/code> and resolve what to do out of its sign and the current index:<\/p>\n

let i = 0;\r\n\r\nfunction move(e) {\r\n  if(x0 || x0 === 0) {\r\n    let dx = unify(e).clientX - x0, s = Math.sign(dx);\r\n  \r\n    if((i > 0 || s < 0) && (i < N - 1 || s > 0))\r\n      _C.style.setProperty('--i', i -= s);\r\n\t\r\n    x0 = null\r\n  }\r\n};<\/code><\/pre>\n

The result on dragging left\/ right can be seen below:<\/p>\n

\"Animated
Switching between images on swipe (live demo<\/a>). Attempts to move to the right on the first image or left on the last one do nothing as we have no other image before or after, respectively.<\/figcaption><\/figure>\n

The above is the expected result and the result we get in Chrome for a little bit of drag and Firefox. However, Edge navigates backward and forward when we drag left or right, which is something that Chrome also does on a bit more drag.<\/p>\n

\"Animated
Edge navigating the pageview backward or forward on left or right swipe.<\/figcaption><\/figure>\n

In order to override this, we need to add a \"touchmove\"<\/code> event listener:<\/p>\n

_C.addEventListener('touchmove', e => {e.preventDefault()}, false)<\/code><\/pre>\n

Alright, we now have something functional in all browsers<\/a>, but it doesn’t look like what we’re really after… yet!<\/p>\n

Smooth Motion<\/h3>\n

The easiest way to move towards getting what we want is by adding a transition<\/code>:<\/p>\n

.container {\r\n  \/* same styles as before *\/\r\n  transition: transform .5s ease-out;\r\n}<\/code><\/pre>\n

And here it is, a very basic swipe effect in about 25 lines of JavaScript and about 25 lines of CSS:<\/p>\n

Working swipe effect (live demo<\/a>).<\/figcaption><\/figure>\n

Sadly, there’s an Edge bug<\/a> that makes any transition<\/code> to a CSS variable-depending calc()<\/code> translation fail. Ugh, I guess we have to forget about Edge for now.<\/p>\n

Refining the Whole Thing<\/h3>\n

With all the cool swipe effects out there, what we have so far doesn’t quite cut it, so let’s see what improvements can be made.<\/p>\n

Better Visual Cues While Dragging<\/h4>\n

First off, nothing happens while<\/em> we drag, all the action follows the \"touchend\"<\/code> (or \"mouseup\"<\/code>) event. So, while we drag, we have no indication of what’s going to happen next. Is there a next image to switch to in the desired direction? Or have we reached the end of the line and nothing will happen?<\/p>\n

To take care of that, we tweak the translation amount a bit by adding a CSS variable --tx<\/code> that’s originally 0px<\/code>:<\/p>\n

transform: translate(calc(var(--i, 0)\/var(--n)*-100% + var(--tx, 0px)))<\/code><\/pre>\n

We use two more event listeners: one for \"touchmove\"<\/code> and another for \"mousemove\"<\/code>. Note that we were already preventing backward and forward navigation in Chrome using the \"touchmove\"<\/code> listener:<\/p>\n

function drag(e) { e.preventDefault() };\r\n\r\n_C.addEventListener('mousemove', drag, false);\r\n_C.addEventListener('touchmove', drag, false);<\/code><\/pre>\n

Now let’s populate the drag()<\/code> function! If we have performed the lock()<\/code> action, we read the current x<\/em> coordinate, compute the difference dx<\/code> between this coordinate and the initial one x0<\/code> and set --tx<\/code> to this value (which is a pixel value).<\/p>\n

function drag(e) {\r\n  e.preventDefault();\r\n\r\n  if(x0 || x0 === 0)  \r\n    _C.style.setProperty('--tx', `${Math.round(unify(e).clientX - x0)}px`)\r\n};<\/code><\/pre>\n

We also need to make sure to reset --tx<\/code> to 0px<\/code> at the end and remove the transition<\/code> for the duration of the drag. In order to make this easier, we move the transition<\/code> declaration on a .smooth<\/code> class:<\/p>\n

.smooth { transition: transform .5s ease-out; }<\/code><\/pre>\n

In the lock()<\/code> function, we remove this class from the .container<\/code> (we’ll add it again at the end on \"touchend\"<\/code> and \"mouseup\"<\/code>) and also set a locked<\/code> boolean variable, so we don’t have to keep performing the x0 || x0 === 0<\/code> check. We then use the locked<\/code> variable for the checks instead:<\/p>\n

let locked = false;\r\n\r\nfunction lock(e) {\r\n  x0 = unify(e).clientX;\r\n  _C.classList.toggle('smooth', !(locked = true))\r\n};\r\n\r\nfunction drag(e) {\r\n  e.preventDefault();\r\n  if(locked) { \/* same as before *\/ }\r\n};\r\n\r\nfunction move(e) {\r\n  if(locked) {\r\n    let dx = unify(e).clientX - x0, s = Math.sign(dx);\r\n\r\n    if((i > 0 || s < 0) && (i < N - 1 || s > 0))\r\n    _C.style.setProperty('--i', i -= s);\r\n    _C.style.setProperty('--tx', '0px');\r\n    _C.classList.toggle('smooth', !(locked = false));\r\n    x0 = null\r\n  }\r\n};<\/code><\/pre>\n

The result can be seen below. While we’re still dragging<\/em>, we now have a visual indication of what’s going to happen next:<\/p>\n

Swipe with visual cues while dragging (live demo<\/a>).<\/figcaption><\/figure>\n

Fix the transition-duration<\/code><\/h4>\n

At this point, we’re always using the same transition-duration<\/code> no matter how much of an image’s width<\/code> we still have to translate after the drag. We can fix that in a pretty straightforward manner by introducing a factor f<\/code>, which we also set as a CSS variable to help us compute the actual animation duration:<\/p>\n

.smooth { transition: transform calc(var(--f, 1)*.5s) ease-out; }<\/code><\/pre>\n

In the JavaScript, we get an image’s width<\/code> (updated on \"resize\"<\/code>) and compute for what fraction of this we have dragged horizontally:<\/p>\n

let w;\r\n\r\nfunction size() { w = window.innerWidth };\r\n\r\nfunction move(e) {\r\n  if(locked) {\r\n    let dx = unify(e).clientX - x0, s = Math.sign(dx), \r\n        f = +(s*dx\/w).toFixed(2);\r\n\r\n    if((i > 0 || s < 0) && (i < N - 1 || s > 0)) {\r\n      _C.style.setProperty('--i', i -= s);\r\n      f = 1 - f\r\n    }\r\n\t\t\r\n    _C.style.setProperty('--tx', '0px');\r\n    _C.style.setProperty('--f', f);\r\n    _C.classList.toggle('smooth', !(locked = false));\r\n    x0 = null\r\n  }\r\n};\r\n\r\nsize();\r\n\r\naddEventListener('resize', size, false);<\/code><\/pre>\n

This now gives us a better result<\/a>.<\/p>\n

Go back if insufficient drag<\/h4>\n

Let’s say that we don’t want to move on to the next image if we only drag a little bit below a certain threshold. Because now, a 1px<\/code> difference during the drag means we advance to the next image and that feels a bit unnatural.<\/p>\n

To fix this, we set a threshold at let’s say 20%<\/code> of an image’s width<\/code>:<\/p>\n

function move(e) {\r\n  if(locked) {\r\n    let dx = unify(e).clientX - x0, s = Math.sign(dx), \r\n        f = +(s*dx\/w).toFixed(2);\r\n\r\n    if((i > 0 || s < 0) && (i < N - 1 || s > 0) && f > .2) {\r\n      \/* same as before *\/\r\n    }\r\n\t\t\r\n    \/* same as before *\/\r\n  }\r\n};<\/code><\/pre>\n

The result can be seen below:<\/p>\n

We only advance to the next image if we drag enough (live demo<\/a>).<\/figcaption><\/figure>\n

Maybe Add a Bounce?<\/h4>\n

This is something that I’m not sure was a good idea, but I was itching to try anyway: change the timing function so that we introduce a bounce. After a bit of dragging the handles on cubic-bezier.com, I came up with a result<\/a> that seemed promising:<\/p>\n

\"Animated
What our chosen cubic B\u00e9zier timing function looks like compared to a plain ease-out<\/code>.<\/figcaption><\/figure>\n
transition: transform calc(var(--f)*.5s) cubic-bezier(1, 1.59, .61, .74);<\/code><\/pre>\n
Using a custom CSS timing function to introduce a bounce (live demo<\/a>).<\/figcaption><\/figure>\n

How About the JavaScript Way, Then?<\/h4>\n

We could achieve a better degree of control over more natural-feeling and more complex bounces by taking the JavaScript route for the transition. This would also give us Edge support.<\/p>\n

We start by getting rid of the transition<\/code> and the --tx<\/code> and --f<\/code> CSS variables. This reduces our transform<\/code> to what it was initially:<\/p>\n

transform: translate(calc(var(--i, 0)\/var(--n)*-100%));<\/code><\/pre>\n

The above code also means --i<\/code> won’t necessarily be an integer anymore. While it remains an integer while we have a single image fully into view, that’s not the case anymore while we drag or during the motion after triggering the \"touchend\"<\/code> or \"mouseup\"<\/code> events.<\/p>\n

\"Annotated
For example, while we have the first image fully in view, --i<\/code> is 0<\/code>. While we have the second one fully in view, --i<\/code> is 1<\/code>. When we’re midway between the first and the second, --i<\/code> is .5<\/code>. When we have a quarter of the first one and three quarters of the second one in view, --i<\/code> is .75<\/code>.<\/figcaption><\/figure>\n

We then update the JavaScript to replace the code parts where we were updating these CSS variables. First, we take care of the lock()<\/code> function, where we ditch toggling the .smooth<\/code> class and of the drag()<\/code> function, where we replace updating the --tx<\/code> variable we’ve ditched with updating --i<\/code>, which, as mentioned before, doesn’t need to be an integer anymore:<\/p>\n

function lock(e) {\r\n  x0 = unify(e).clientX;\r\n  locked = true\r\n};\r\n\r\nfunction drag(e) {\r\n  e.preventDefault();\r\n\t\r\n  if(locked) {\r\n    let dx = unify(e).clientX - x0, \r\n      f = +(dx\/w).toFixed(2);\r\n\t\t\r\n    _C.style.setProperty('--i', i - f)\r\n  }\r\n};<\/code><\/pre>\n

Before we also update the move()<\/code> function, we introduce two new variables, ini<\/code> and fin<\/code>. These represent the initial value we set --i<\/code> to at the beginning of the animation and the final value we set the same variable to at the end of the animation. We also create an animation function ani()<\/code>:<\/p>\n

let ini, fin;\r\n\r\nfunction ani() {};\r\n\r\nfunction move(e) {\r\n  if(locked) {\r\n    let dx = unify(e).clientX - x0, \r\n        s = Math.sign(dx), \r\n        f = +(s*dx\/w).toFixed(2);\r\n\t\t\r\n    ini = i - s*f;\r\n\r\n    if((i > 0 || s < 0) && (i < N - 1 || s > 0) && f > .2) {\r\n      i -= s;\r\n      f = 1 - f\r\n    }\r\n\r\n    fin = i;\r\n    ani();\r\n    x0 = null;\r\n    locked = false;\r\n  }\r\n};<\/code><\/pre>\n

This is not too different from the code we had before. What has changed is that we’re not setting any CSS variables in this function anymore but instead set the ini<\/code> and the fin<\/code> JavaScript variables and call the animation ani()<\/code> function.<\/p>\n

ini<\/code> is the initial value we set --i<\/code> to at the beginning of the animation that the \"touchend\"<\/code>\/ \"mouseup\"<\/code> event triggers. This is given by the current position we have when one of these two events fires.<\/p>\n

fin<\/code> is the final value we set --i<\/code> to at the end of the same animation. This is always an integer value because we always end with one image fully into sight, so fin<\/code> and --i<\/code> are the index of that image. This is the next image in the desired direction if we dragged enough (f > .2<\/code>) and if there is a next image in the desired direction ((i > 0 || s < 0) && (i < N - 1 || s > 0)<\/code>). In this case, we also update the JavaScript variable storing the current image index (i<\/code>) and the relative distance to it (f<\/code>). Otherwise, it’s the same image, so i<\/code> and f<\/code> don’t need to get updated.<\/p>\n

Now, let’s move on to the ani()<\/code> function. We start with a simplified linear version<\/a> that leaves out a change of direction.<\/p>\n

const NF = 30;\r\n\r\nlet rID = null;\r\n\r\nfunction stopAni() {\r\n  cancelAnimationFrame(rID);\r\n  rID = null\r\n};\r\n\r\nfunction ani(cf = 0) {\r\n  _C.style.setProperty('--i', ini + (fin - ini)*cf\/NF);\r\n\t\r\n  if(cf === NF) {\r\n    stopAni();\r\n    return\r\n  }\r\n\t\r\n  rID = requestAnimationFrame(ani.bind(this, ++cf))\r\n};<\/code><\/pre>\n

The main idea here is that the transition between the initial value ini<\/code> and the final one fin<\/code> happens over a total number of frames NF<\/code>. Every time we call the ani()<\/code> function, we compute the progress as the ratio between the current frame index cf<\/code> and the total number of frames NF<\/code>. This is always a number between 0<\/code> and 1<\/code> (or you can take it as a percentage, going from 0%<\/code> to 100%<\/code>). We then use this progress value to get the current value of --i<\/code> and set it in the style attribute of our container _C<\/code>. If we got to the final state (the current frame index cf<\/code> equals the total number of frames NF<\/code>, we exit the animation loop). Otherwise, we just increment the current frame index cf<\/code> and call ani()<\/code> again.<\/p>\n

At this point, we have a working demo with a linear JavaScript transition:<\/p>\n

Version with linear JavaScript transition (live demo<\/a>).<\/figcaption><\/figure>\n

However, this has the problem we initially had in the CSS case: no matter the distance, we have to have to smoothly translate our element over on release (\"touchend\"<\/code> \/ \"mouseup\"<\/code>) and the duration is always the same because we always animate over the same number of frames NF<\/code>.<\/p>\n

Let’s fix that!<\/p>\n

In order to do so, we introduce another variable anf<\/code> where we store the actual number of frames we use and whose value we compute in the move()<\/code> function, before calling the animation function ani()<\/code>:<\/p>\n

function move(e) {\r\n  if(locked) {\r\n    let dx = unify(e).clientX - x0, \r\n      s = Math.sign(dx), \r\n      f = +(s*dx\/w).toFixed(2);\r\n\t\t\r\n    \/* same as before *\/\r\n\r\n    anf = Math.round(f*NF);\r\n    ani();\r\n\r\n    \/* same as before *\/\r\n  }\r\n};<\/code><\/pre>\n

We also need to replace NF<\/code> with anf<\/code> in the animation function ani()<\/code>:<\/p>\n

function ani(cf = 0) {\r\n  _C.style.setProperty('--i', ini + (fin - ini)*cf\/anf);\r\n\t\r\n  if(cf === anf) { \/* same as before *\/ }\r\n\t\r\n  \/* same as before *\/\r\n};<\/code><\/pre>\n

With this, we have fixed the timing issue!<\/p>\n

Version with linear<\/code> JavaScript transition at constant speed (live demo<\/a>).<\/figcaption><\/figure>\n

Alright, but a linear timing function isn’t too exciting.<\/p>\n

We could try the JavaScript equivalents of CSS timing functions such as ease-in<\/code>, ease-out<\/code> or ease-in-out<\/code> and see how they compare. I’ve already explained in a lot of detail how to get these in the previously linked article<\/a>, so I’m not going to go through that again and just drop the object with all of them into the code:<\/p>\n

const TFN = {\r\n  'linear': function(k) { return k }, \r\n  'ease-in': function(k, e = 1.675) {\r\n    return Math.pow(k, e)\r\n  }, \r\n  'ease-out': function(k, e = 1.675) {\r\n    return 1 - Math.pow(1 - k, e)\r\n  }, \r\n  'ease-in-out': function(k) {\r\n    return .5*(Math.sin((k - .5)*Math.PI) + 1)\r\n  }\r\n};<\/code><\/pre>\n

The k<\/code> value is the progress, which is the ratio between the current frame index cf<\/code> and the actual number of frames the transition happens over anf<\/code>. This means we modify the ani()<\/code> function a bit if we want to use the ease-out<\/code> option for example:<\/p>\n

function ani(cf = 0) {\r\n  _C.style.setProperty('--i', ini + (fin - ini)*TFN['ease-out'](cf\/anf));\r\n\t\r\n  \/* same as before *\/\r\n};<\/code><\/pre>\n
Version with ease-out<\/code> JavaScript transition (live demo<\/a>).<\/figcaption><\/figure>\n

We could also make things more interesting by using the kind of bouncing timing function that CSS cannot give us. For example, something like the one illustrated by the demo below (click to trigger a transition):<\/p>\n

See the Pen<\/a> by thebabydino (@thebabydino<\/a>) on CodePen<\/a>.<\/p>\n

The graphic for this would be somewhat similar to that of the easeOutBounce<\/code><\/a> timing function from easings.net.<\/p>\n

\"Animated
Graphical representation of the timing function.<\/figcaption><\/figure>\n

The process for getting this kind of timing function is similar to that for getting the JavaScript version of the CSS ease-in-out<\/code> (again, described in the previously linked article<\/a> on emulating CSS timing functions with JavaScript).<\/p>\n

We start with the cosine function on the [0, 90\u00b0]<\/code> interval (or [0, \u03c0\/2]<\/code> in radians) for no bounce, [0, 270\u00b0]<\/code> ([0, 3\u00b7\u03c0\/2]<\/code>) for 1<\/code> bounce, [0, 450\u00b0]<\/code> ([0, 5\u00b7\u03c0\/2]<\/code>) for 2<\/code> bounces and so on… in general it’s the [0, (n + \u00bd)\u00b7180\u00b0]<\/code> interval ([0, (n + \u00bd)\u00b7\u03c0]<\/code>) for n<\/code> bounces.<\/p>\n

See the Pen<\/a> by thebabydino (@thebabydino<\/a>) on CodePen<\/a>.<\/p>\n

The input of this cos(k)<\/code> function is in the [0, 450\u00b0]<\/code> interval, while its output is in the [-1, 1]<\/code> interval. But what we want is a function whose domain is the [0, 1]<\/code> interval and whose codomain is also the [0, 1]<\/code> interval.<\/p>\n

We can restrict the codomain to the [0, 1]<\/code> interval by only taking the absolute value |cos(k)|<\/code>:<\/p>\n

See the Pen<\/a> by thebabydino (@thebabydino<\/a>) on CodePen<\/a>.<\/p>\n

While we got the interval we wanted for the codomain, we want the value of this function at 0<\/code> to be 0<\/code> and its value at the other end of the interval to be 1<\/code>. Currently, it’s the other way around, but we can fix this if we change our function to 1 - |cos(k)|<\/code>:<\/p>\n

See the Pen<\/a> by thebabydino (@thebabydino<\/a>) on CodePen<\/a>.<\/p>\n

Now we can move on to restricting the domain from the [0, (n + \u00bd)\u00b7180\u00b0]<\/code> interval to the [0, 1]<\/code> interval. In order to do this, we change our function to be 1 - |cos(k\u00b7(n + \u00bd)\u00b7180\u00b0)|<\/code>:<\/p>\n

See the Pen<\/a> by thebabydino (@thebabydino<\/a>) on CodePen<\/a>.<\/p>\n

This gives us both the desired domain and codomain, but we still have some problems.<\/p>\n

First of all, all our bounces have the same height, but we want their height to decrease as k<\/code> increases from 0<\/code> to 1<\/code>. Our fix in this case is to multiply the cosine with 1 - k<\/code> (or with a power of 1 - k<\/code> for a non-linear decrease in amplitude). The interactive demo below shows how this amplitude changes for various exponents a<\/code> and how this influences the function we have so far:<\/p>\n

See the Pen<\/a> by thebabydino (@thebabydino<\/a>) on CodePen<\/a>.<\/p>\n

Secondly, all the bounces take the same amount of time, even though their amplitudes keep decreasing. The first idea here is to use a power of k<\/code> inside the cosine function instead of just k<\/code>. This manages to make things weird<\/a> as the cosine doesn’t hit 0<\/code> at equal intervals anymore, meaning we don’t always get that f(1) = 1<\/code> anymore which is what we’d always need from a timing function we’re actually going to use. However, for something like a = 2.75<\/code>, n = 3<\/code> and b = 1.5<\/code>, we get a result that looks satisfying, so we’ll leave it at that, even though it could be tweaked for better control:<\/p>\n

\"Screenshot
The timing function we want to try.<\/figcaption><\/figure>\n

This is the function we try out in the JavaScript if we want some bouncing to happen.<\/p>\n

const TFN = {\r\n  \/* the other function we had before *\/\r\n  'bounce-out': function(k, n = 3, a = 2.75, b = 1.5) {\r\n    return 1 - Math.pow(1 - k, a)*Math.abs(Math.cos(Math.pow(k, b)*(n + .5)*Math.PI))\r\n  }\r\n};<\/code><\/pre>\n

Hmm, seems a bit too extreme in practice:<\/p>\n

Version with a bouncing JavaScript transition (live demo<\/a>).<\/figcaption><\/figure>\n

Maybe we could make n<\/code> depend on the amount of translation we still need to perform from the moment of the release. We make it into a variable which we then set in the move()<\/code> function before calling the animation function ani()<\/code>:<\/p>\n

const TFN = {\r\n  \/* the other function we had before *\/\r\n  'bounce-out': function(k, a = 2.75, b = 1.5) {\r\n    return 1 - Math.pow(1 - k, a)*Math.abs(Math.cos(Math.pow(k, b)*(n + .5)*Math.PI))\r\n  }\r\n};\r\n\r\nvar n;\r\n\r\nfunction move(e) {\r\n  if(locked) {\r\n    let dx = unify(e).clientX - x0, \r\n      s = Math.sign(dx), \r\n      f = +(s*dx\/w).toFixed(2);\r\n    \r\n    \/* same as before *\/\r\n\t\t\r\n    n = 2 + Math.round(f)\r\n    ani();\r\n    \/* same as before *\/\r\n  }\r\n};<\/code><\/pre>\n

This gives us our final result:<\/p>\n

Version with the final bouncing JavaScript transition (live demo<\/a>).<\/figcaption><\/figure>\n

There’s definitely still room for improvement, but I don’t have a feel for what makes a good animation, so I’ll just leave it at that. As it is, this is now functional cross-browser (without have any of the Edge issues that the version using a CSS transition has) and pretty flexible.<\/p>\n","protected":false},"excerpt":{"rendered":"

I used to think implementing swipe gestures had to be very difficult, but I have recently found myself in a situation where I had to do it and discovered the reality is nowhere near as gloomy as I had imagined. This article is going to take you, step by step, through the implementation with the […]<\/p>\n","protected":false},"author":225572,"featured_media":268675,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_bbp_topic_count":0,"_bbp_reply_count":0,"_bbp_total_topic_count":0,"_bbp_total_reply_count":0,"_bbp_voice_count":0,"_bbp_anonymous_reply_count":0,"_bbp_topic_count_hidden":0,"_bbp_reply_count_hidden":0,"_bbp_forum_subforum_count":0,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"c2c_always_allow_admin_comments":false,"footnotes":"","jetpack_publicize_message":"A new tutorial for making swipe gestures with vanilla JavaScript by @thebabydino.","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":[]},"categories":[4],"tags":[612,432,600,1388],"jetpack_publicize_connections":[],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2018\/03\/javascript-swipe-slider.gif?fit=640%2C320&ssl=1","jetpack-related-posts":[{"id":247731,"url":"https:\/\/css-tricks.com\/pure-css-horizontal-scrolling\/","url_meta":{"origin":267821,"position":0},"title":"Pure CSS Horizontal Scrolling","date":"November 29, 2016","format":false,"excerpt":"The web is a rather vertical place. You read a web site like you read a physical page: left to right, top to bottom. But sometimes, you want to step away from the verticality of it all and do something crazy: make a horizontal list. Or even crazier, a horizontal\u2026","rel":"","context":"In "Article"","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":240734,"url":"https:\/\/css-tricks.com\/creating-wavescroll\/","url_meta":{"origin":267821,"position":1},"title":"Creating Wavescroll","date":"April 19, 2016","format":false,"excerpt":"This article is a walkthough of how I made this demo of a unique way scroll through panels: CodePen Embed Fallback The code in this demo is (hopefully) pretty straightforward and easy-to-understand. No npm modules or ES2015 stuff here, I went with classic jQuery, SCSS, and HTML (plus a little\u2026","rel":"","context":"In "Article"","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":21975,"url":"https:\/\/css-tricks.com\/the-javascript-behind-touch-friendly-sliders\/","url_meta":{"origin":267821,"position":2},"title":"The JavaScript Behind Touch-Friendly Sliders","date":"June 13, 2013","format":false,"excerpt":"Kevin Foley explains how to build a swipeable gallery on touch devices.","rel":"","context":"In "Article"","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":255301,"url":"https:\/\/css-tricks.com\/managing-state-css-reusable-javascript-functions-part-2\/","url_meta":{"origin":267821,"position":3},"title":"Managing State in CSS with Reusable JavaScript Functions – Part 2","date":"May 30, 2017","format":false,"excerpt":"In my previous article, which shall now retroactively be known as Managing State in CSS with Reusable JavaScript Functions - Part 1, we created a powerful reusable function which allows us to quickly add, remove and toggle stateful classes via click. One of the reasons I wanted to share this\u2026","rel":"","context":"In "Article"","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":21656,"url":"https:\/\/css-tricks.com\/slider-with-sliding-backgrounds\/","url_meta":{"origin":267821,"position":4},"title":"Slider with Sliding Backgrounds","date":"May 17, 2013","format":false,"excerpt":"Among the many super nice design features of the Yahoo! Weather app for iOS is the transition between city screens. The background image doesn't just move away as the screen moves from one screen to the next, the background image itself slides. It appears to be hiding some of the\u2026","rel":"","context":"In "Article"","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":333769,"url":"https:\/\/css-tricks.com\/recreating-game-elements-for-the-web-the-among-us-card-swipe\/","url_meta":{"origin":267821,"position":5},"title":"Recreating Game Elements for the Web: The Among Us Card Swipe","date":"February 9, 2021","format":false,"excerpt":"As a web developer, I pay close attention to the design of video games. From the HUD in Overwatch to the catch screen in Pokemon Go to hunting in Oregon Trail, games often have interesting mechanics and satisfying interactions, many of which inspire my own coding games at Codepip. Beyond\u2026","rel":"","context":"In "Article"","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/css-tricks.com\/wp-content\/uploads\/2021\/02\/credt-card-swipe-game.png?fit=1200%2C600&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]}],"_links":{"self":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts\/267821"}],"collection":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/users\/225572"}],"replies":[{"embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/comments?post=267821"}],"version-history":[{"count":90,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts\/267821\/revisions"}],"predecessor-version":[{"id":277147,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/posts\/267821\/revisions\/277147"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/media\/268675"}],"wp:attachment":[{"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/media?parent=267821"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/categories?post=267821"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/css-tricks.com\/wp-json\/wp\/v2\/tags?post=267821"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}