Skip to content

Touch Events — Complete Guide

DodaTech Updated 2026-06-28 7 min read

In this tutorial, you will learn about Touch Events. We cover key concepts, practical examples, and best practices to help you master this topic.

Touch events (touchstart, touchmove, touchend) enable JavaScript to handle multi-touch interactions on mobile devices and touch-enabled screens for gestures and input.

What You'll Learn

  • The touch event lifecycle: touchstart, touchmove, touchend, touchcancel
  • How to access touch points through event.touches, event.targetTouches, event.changedTouches
  • How to implement common gestures: tap, swipe, pinch, rotate
  • How to handle both mouse and touch events together

Why It Matters

Over half of web traffic comes from mobile devices. Touch events power swipe navigation, pinch-to-zoom, pull-to-refresh, drag-and-drop, and drawing on touch screens. Supporting touch is essential for modern web applications.

Real-World Use

  • A photo gallery supports swipe gestures to navigate between images
  • A map application supports pinch-to-zoom and two-finger rotate
  • A mobile game uses multi-touch for dual-stick controls
  • A drawing app captures touch strokes with pressure sensitivity
flowchart LR
  A[Touch on Screen] --> B[touchstart]
  B --> C[Touch Point Created]
  C --> D[touchmove]
  D --> E[Touch Point Moves]
  E --> D
  D --> F[touchend]
  E --> G[touchcancel]
  G --> H[System Interrupts]
  F --> I[Gesture Recognized]

Basic Touch Events

The three primary touch events mirror mouse events but support multiple simultaneous touch points.

const touchArea = document.querySelector('.touch-area');
const touchLog = document.querySelector('.touch-log');

touchArea.addEventListener('touchstart', function(event) {
    // Prevent default to avoid scroll interference
    event.preventDefault();

    console.log('Touch started');
    console.log('Number of touches:', event.touches.length);
    console.log('Target touches:', event.targetTouches.length);
    console.log('Changed touches:', event.changedTouches.length);

    // Get the first touch point
    const touch = event.touches[0];
    console.log('Position:', touch.clientX, touch.clientY);
    console.log('Touch ID:', touch.identifier);

    touchLog.textContent = `Touch start at (${Math.round(touch.clientX)}, ${Math.round(touch.clientY)})`;
});

touchArea.addEventListener('touchmove', function(event) {
    event.preventDefault();
    const touch = event.touches[0];

    touchLog.textContent = `Touch move at (${Math.round(touch.clientX)}, ${Math.round(touch.clientY)})`;
});

touchArea.addEventListener('touchend', function(event) {
    // In touchend, event.touches may be empty
    const touch = event.changedTouches[0];
    console.log('Touch ended at:', touch.clientX, touch.clientY);
    touchLog.textContent = `Touch ended at (${Math.round(touch.clientX)}, ${Math.round(touch.clientY)})`;
});

Expected output: Touching the area logs the start position. Moving the finger updates the log. Lifting the finger logs the end position. The event.touches array contains all active touch points.

Touch Lists

Three touch lists provide different views of the touch state.

touchArea.addEventListener('touchstart', function(event) {
    // event.touches: ALL touch points currently on the screen
    // (including those on other elements)

    // event.targetTouches: touch points ON this element
    // (subset of touches)

    // event.changedTouches: touch points that changed
    // in this event (started, moved, or ended)

    console.log('All touches:', event.touches.length);
    console.log('On this element:', event.targetTouches.length);
    console.log('Changed:', event.changedTouches.length);

    // For touchstart: changedTouches === touches
    // For touchend: changedTouches has the removed touch, touches is empty
    // For touchmove: changedTouches has moved touches

    // Iterate all touch points
    for (let i = 0; i < event.touches.length; i++) {
        const touch = event.touches[i];
        console.log(`Touch ${touch.identifier}: (${touch.clientX}, ${touch.clientY})`);
    }
});

Expected output: The console shows counts for each touch list. On touchstart, all three lists have the same length. On touchend, touches may be empty while changedTouches has the released touch.

Implementing Gestures

Common gestures can be detected by tracking touch positions over time.

const gestureArea = document.querySelector('.gesture-area');
let startX, startY, startTime;
let lastTap = 0;

gestureArea.addEventListener('touchstart', function(event) {
    const touch = event.touches[0];
    startX = touch.clientX;
    startY = touch.clientY;
    startTime = Date.now();
});

gestureArea.addEventListener('touchend', function(event) {
    const touch = event.changedTouches[0];
    const endX = touch.clientX;
    const endY = touch.clientY;
    const deltaX = endX - startX;
    const deltaY = endY - startY;
    const elapsed = Date.now() - startTime;

    // Detect tap (quick touch without much movement)
    if (Math.abs(deltaX) < 10 && Math.abs(deltaY) < 10 && elapsed < 200) {
        console.log('Tap detected');
        handleTap(endX, endY);
    }

    // Detect swipe (fast movement in one direction)
    if (Math.abs(deltaX) > 50 || Math.abs(deltaY) > 50) {
        if (Math.abs(deltaX) > Math.abs(deltaY)) {
            console.log(deltaX > 0 ? 'Swipe Right' : 'Swipe Left');
        } else {
            console.log(deltaY > 0 ? 'Swipe Down' : 'Swipe Up');
        }
    }

    // Detect long press
    if (Math.abs(deltaX) < 20 && Math.abs(deltaY) < 20 && elapsed > 500) {
        console.log('Long press detected');
        handleLongPress(endX, endY);
    }

    // Detect double tap
    const now = Date.now();
    if (now - lastTap < 300) {
        console.log('Double tap detected');
        handleDoubleTap(endX, endY);
    }
    lastTap = now;
});

Expected output: Tapping shows "Tap detected". Swiping identifies the direction. Holding without moving shows "Long press detected". Two quick taps show "Double tap detected".

Multi-Touch: Pinch and Rotate

Track two touch points to detect pinch-to-zoom and rotation gestures.

const pinchArea = document.querySelector('.pinch-area');
let initialDistance = 0;
let initialAngle = 0;
let currentScale = 1;
let currentRotation = 0;

pinchArea.addEventListener('touchstart', function(event) {
    if (event.touches.length === 2) {
        const touch1 = event.touches[0];
        const touch2 = event.touches[1];

        initialDistance = getDistance(touch1, touch2);
        initialAngle = getAngle(touch1, touch2);

        console.log('Pinch start, distance:', initialDistance);
    }
});

pinchArea.addEventListener('touchmove', function(event) {
    event.preventDefault();

    if (event.touches.length === 2) {
        const touch1 = event.touches[0];
        const touch2 = event.touches[1];

        const distance = getDistance(touch1, touch2);
        const angle = getAngle(touch1, touch2);

        // Scale
        const scale = distance / initialDistance;
        currentScale = Math.max(0.5, Math.min(3, scale));
        console.log('Scale:', currentScale.toFixed(2));

        // Rotation
        const rotation = angle - initialAngle;
        currentRotation = rotation;

        // Apply transform
        pinchArea.style.transform = `
            scale(${currentScale}) 
            rotate(${currentRotation}deg)
        `;
    }
});

function getDistance(t1, t2) {
    const dx = t1.clientX - t2.clientX;
    const dy = t1.clientY - t2.clientY;
    return Math.sqrt(dx * dx + dy * dy);
}

function getAngle(t1, t2) {
    const dx = t2.clientX - t1.clientX;
    const dy = t2.clientY - t1.clientY;
    return Math.atan2(dy, dx) * 180 / Math.PI;
}

Expected output: Pinching with two fingers scales the element up or down. Rotating two fingers rotates the element. The scale is clamped between 0.5 and 3.

Handling Both Mouse and Touch

Mobile browsers fire both touch and mouse events. Prevent duplicate handling by tracking the input type.

const interactive = document.querySelector('.interactive');
let isTouchDevice = false;

// Detect touch capability
interactive.addEventListener('touchstart', function(event) {
    isTouchDevice = true;
    console.log('Touch interaction');
    handleStart(event.touches[0].clientX, event.touches[0].clientY);
}, { passive: true });

interactive.addEventListener('touchend', function(event) {
    if (isTouchDevice) {
        handleEnd(event.changedTouches[0].clientX, event.changedTouches[0].clientY);
        // Prevent the subsequent mouse event
        event.preventDefault();
    }
});

// Mouse fallback for non-touch devices
interactive.addEventListener('mousedown', function(event) {
    if (!isTouchDevice) {
        console.log('Mouse interaction');
        handleStart(event.clientX, event.clientY);
    }
});

interactive.addEventListener('mouseup', function(event) {
    if (!isTouchDevice) {
        handleEnd(event.clientX, event.clientY);
    }
});

function handleStart(x, y) {
    console.log('Interaction started at:', x, y);
}

function handleEnd(x, y) {
    console.log('Interaction ended at:', x, y);
}

// Alternative: use Pointer Events API (unified mouse/touch/stylus)
// pointerdown, pointermove, pointerup work for all input types

Expected output: On touch devices, only touch events fire (mouse events are suppressed). On mouse devices, mouse events fire. Both paths call the same handler functions.

Common Mistakes

  1. Not calling preventDefault on touchstart — Without preventDefault, the browser may interpret the touch as a scroll or gesture, causing conflicts with custom interactions.
  2. Using event.touches in touchend — In touchend, event.touches no longer contains the released touch. Use event.changedTouches instead.
  3. Forgetting to handle touchcancel — touchcancel fires when the system interrupts the touch (incoming call, alert, gesture conflict). Always clean up state in touchcancel.
  4. Not accounting for multi-touch in single-touch handlers — If only tracking event.touches[0], you may get the wrong finger when multiple fingers are on screen.
  5. Ignoring 300ms tap delay — Modern browsers remove the delay when viewport meta tag is present. If not, use touch-action: manipulation CSS property.

Practice Questions

  1. What are the three touch lists and when should you use each? touches (all active touches), targetTouches (touches on this element), changedTouches (touches that changed). Use changedTouches in touchend/touchcancel.
  2. How do you prevent the browser from handling a touch as a scroll? Call event.preventDefault() on the touchstart or touchmove event.
  3. How do you detect a swipe gesture? Compare the starting and ending touch positions. If the distance exceeds a threshold (50px) and occurs within a time limit (300ms), it is a swipe.
  4. Challenge: Implement pull-to-refresh. Detecting a downward swipe at the top of the page triggers a refresh animation. When the pull exceeds a threshold, fire a refresh callback. Use CSS transitions for the snap-back animation.

FAQ

What is the difference between touch and pointer events?

Pointer events unify mouse, touch, and stylus into a single API. Touch events are touch-specific. Pointer events have broader browser support but are newer.

How many simultaneous touch points can I track?

It depends on the device. Most modern phones support at least 5 simultaneous touches. iPads support up to 11.

What is touchcancel and when does it fire?

touchcancel fires when the system cancels the touch — incoming call, system alert, or gesture conflict. Always handle it to reset touch state.

Do touch events work on desktop browsers?

Touch events work on desktop browsers with touch screens. For mouse-only desktops, use mouse events or pointer events.

How do I get touch pressure information?

Some devices support event.touches[i].force which returns a value from 0 to 1 indicating pressure. Not all devices support this.

Mini Project

Build an image gallery that supports swipe navigation. Display one image at a time. Swiping left shows the next image, swiping right shows the previous. Add a smooth CSS transition for the slide animation. Show dot indicators below the image indicating the current position. Handle touchcancel by snapping back to the current image without changing the index.

What's Next

Continue with Lesson 20: Intersection Observer to learn how to detect when elements become visible in the viewport for Lazy Loading and animations.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro