javascript 69 lines · 10 steps

Building a swipe-to-dismiss gesture in JS

How touch events track a horizontal drag and either fling an element away or snap it back.

Explained by highlit
1const SWIPE_THRESHOLD = 80;
2const MAX_TRANSLATE = 120;
3 
4export function attachSwipeToDismiss(element, onDismiss) {
5 let startX = 0;
6 let startY = 0;
7 let currentX = 0;
8 let tracking = false;
9 let horizontal = false;
10 
11 function onTouchStart(e) {
12 const touch = e.touches[0];
13 startX = touch.clientX;
14 startY = touch.clientY;
15 currentX = 0;
16 tracking = true;
17 horizontal = false;
18 element.style.transition = '';
19 }
20 
21 function onTouchMove(e) {
22 if (!tracking) return;
23 const touch = e.touches[0];
24 const dx = touch.clientX - startX;
25 const dy = touch.clientY - startY;
26 
27 if (!horizontal) {
28 if (Math.abs(dx) < 10 && Math.abs(dy) < 10) return;
29 horizontal = Math.abs(dx) > Math.abs(dy);
30 if (!horizontal) {
31 tracking = false;
32 return;
33 }
34 }
35 
36 e.preventDefault();
37 currentX = Math.max(-MAX_TRANSLATE, Math.min(MAX_TRANSLATE, dx));
38 element.style.transform = `translateX(${currentX}px)`;
39 element.style.opacity = String(1 - Math.abs(currentX) / (MAX_TRANSLATE * 1.5));
40 }
41 
42 function onTouchEnd() {
43 if (!tracking) return;
44 tracking = false;
45 element.style.transition = 'transform 0.25s ease, opacity 0.25s ease';
46 
47 if (Math.abs(currentX) >= SWIPE_THRESHOLD) {
48 const direction = currentX > 0 ? 1 : -1;
49 element.style.transform = `translateX(${direction * window.innerWidth}px)`;
50 element.style.opacity = '0';
51 element.addEventListener('transitionend', () => onDismiss(element), { once: true });
52 } else {
53 element.style.transform = '';
54 element.style.opacity = '';
55 }
56 }
57 
58 element.addEventListener('touchstart', onTouchStart, { passive: true });
59 element.addEventListener('touchmove', onTouchMove, { passive: false });
60 element.addEventListener('touchend', onTouchEnd);
61 element.addEventListener('touchcancel', onTouchEnd);
62 
63 return () => {
64 element.removeEventListener('touchstart', onTouchStart);
65 element.removeEventListener('touchmove', onTouchMove);
66 element.removeEventListener('touchend', onTouchEnd);
67 element.removeEventListener('touchcancel', onTouchEnd);
68 };
69}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Detecting gesture direction early lets you claim horizontal swipes while leaving vertical scrolling untouched.
  2. 2Clamping the drag distance and toggling CSS transitions keeps the follow-the-finger feel separate from the snap-back animation.
  3. 3Returning a teardown function makes an event-heavy setup safe to unbind when the element goes away.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Building a swipe-to-dismiss gesture in JS — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code