Mobile

Detecting the Use of a Touch Screen

Here’s a fun one: Ever have some UI/UX interaction that needed to be one thing for mouses and another thing for touch devices? Ever used Modernizr? If you have, you may be familiar with this gem:

Warning: Indicates if the browser supports the Touch Events spec, and does not necessarily reflect a touchscreen device

You might also know that modern browsers sometimes report .has-touchevents when there’s a trackpad connected to the system. For instance, when you’re on a laptop, even if you don’t have a touchscreen connected. This phenomenon can be observed in some builds of Chrome on some versions of Windows.
The Modernizr detect for touch screen basically just checks to see if the window contains the dom events for touch. So it only detects if the browser supports touch, not if it is actually utilizing touch hardware. To my knowledge, JavaScript running in browsers currently has no way of determining if a touch device is being used.
So what should you do? The best practice answer is: don’t do anything for either that wouldn’t work correctly for both. Unfortunately, this isn’t always the case in real-life, especially when dealing with client requirements or complex UX interactions. Recently I ran into an issue where I had to detect the use of a touchscreen instead of mouse use for a WCAG accessibility update. Long-story-short: The feature I was having to fix wasn’t compatible with on-screen keyboards, so I had to come up with a way to detect touch screen usage the most accurately I could.

Enter Tactus

In attempting to find an existing solution, I stumbled upon the quintessential “has touch events” JavaScript detect.

if ('ontouchstart' in window || navigator.msMaxTouchPoints > 0) {...}

This would only get me part of the way there and is moderately useful, considering this is what Modernizr uses. Instead of only detecting the existence of touch events, I decided that I’d have to listen for them to see if a touch device is actually being used. This is what I came up with:

// Make sure that the `WEB` namespace object exists and the `utils` container exists within it
WEB = WEB || {};
WEB.utils = WEB.utils || {};
// Begin Tactus
WEB.utils.tactus = (function() {
    // Set variable to cache the results of isTouch
    var _isTouchCached = null;
    // Set a variable detecting the existence of touch events
    var _hasEvents = 'ontouchstart' in window || navigator.msMaxTouchPoints > 0;
    // Set a flag variable to keep track of if the mouse/touch event listeners were already added
    var _listenersAdded = false;
    // Call the isTouch method initially
    isTouch();
    // Reveal the methods
    return {
        isTouch: isTouch, // WEB.utils.tactus.isTouch();
        est: isTouch // WEB.utils.tactus.est();
    };
    // Default isTouch check
    function isTouch() {
        // If the cached variable already has a value
        if (_isTouchCached !== null) {
            // Just return the cached variable and don't bother checking again
            return _isTouchCached;
        }
        // If the browser supports touch events
        if (_hasEvents) {
            // If the mouse/touch event listeners haven't been added yet
            if (!_listenersAdded) {
                // Add the mouse/touch event listeners
                _addListeners();
            }
            // If the browser doesn't support touch events
        } else {
            // Set the cached variable to false
            _isTouchCached = false;
        }
        // Return the current value of the cached variable
        return _isTouchCached;
    }
    // The function to add the mouse/touch event listeners
    function _addListeners() {
        // If the mouse/touch event listeners haven't been added yet
        if (!_listenersAdded) {
            // Add the mouse event listener
            window.addEventListener('mousemove', _checkForMouse);
            // Add the touch event listener
            window.addEventListener('touchstart', _checkForTouch);
        }
        // Set the flag to true to indicate that the mosue/touch event listeners have been added
        _listenersAdded = true;
    }
    // The mouse event listener
    function _checkForMouse() {
        // Set the cached variable to false
        _isTouchCached = false;
        // Remove the mouse/touch event listeners
        _removeListeners();
    }
    // The touch event listener
    function _checkForTouch() {
        // Set the cached variable to true
        _isTouchCached = true;
        // Remove the mouse/touch event listeners
        _removeListeners();
    }
    // The function to remove the mouse/touch event listeners
    function _removeListeners() {
        // Remove the mouse event listener
        window.removeEventListener('mousemove', _checkForMouse);
        // Remove the touch event listener
        window.removeEventListener('touchstart', _checkForTouch);
    }
}());

This utility uses the revealing module pattern and a namespaced object and utils container.
We only want to listen for either the mousemove event, or the touchstart event once. Whenever either of them is fired, it sets the  variable appropriately and removes the events. Any time the isTouch() function is called, it returns the cached variable; once either of the event listeners has fired, it will be set and every subsequent call to isTouch() will return the same boolean value.
On DOM ready, this function will fire; it will attach the event listeners and begin waiting for the user’s input. Unfortunately, any other checks for isTouch() that happen on DOM ready (before the user has a chance to move their mouse or touch their screen) will all return null. I chose null since it is falsey. Any calls to check isTouch() that execute before user input will mimic the behavior of the user using the mouse only; think of this as the default. You can then use future calls to isTouch() as a progressive enhancement for touchscreen devices.
This could be made into a promise or observable paradigm so that the call wouldn’t actually return until the user interacts; though the use of promises is outside the scope of this post.
TL;DR
If you wait for the user’s input, with either a mousemove, or a touchstart event, you can more accurately determine if the user is actually using a touch device.
I look forward to seeing examples of this being used or altered to include the use of promises or observables.
 
 

About the Author

More from this Author

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Subscribe to the Weekly Blog Digest:

Sign Up
Categories