1

I have a simple UI that allows users on desktop browsers to click on a cell in a grid, and while holding the mouse button down, move the mouse around to highlight multiple cells very quickly. I accomplish this with mousedown, mouseover and mouseup events on the document, and using a Boolean flag to dictate whether the mouse button is being held down our not. It works fine on desktop browsers.

The problem is with mobile browsers though. Those mouse events don't exist, and I know we need to use the touch events instead, but after extensive searching around Google, SO, etc., I cannot find a consistent, workable way to do the same sort of thing on mobile browsers.

The closest I've seen is capturing the start with touchstart, and then tracking finger movement with touchmove and the evt.touches[0].clientX/Y properties. Is there a better/simpler way to do this, or are we forced to basically check screen coordinates with clientX/Y to figure out which DOM element we're "hovering over" and highlighting the DOM element accordingly?

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
HartleySan
  • 7,404
  • 14
  • 66
  • 119
  • 1
    You are certainly on the right track with `touchstart`, `touchmove` and `touchend`, apparently there is `document.elementFromPoint(event.clientX, event.clientY);` , per [this thread](https://stackoverflow.com/a/4405541/9898651) – Adam H Feb 28 '20 at 23:00
  • That `document.elementFromPoint` method is super handy. That will save a lot of time (hopefully). Thanks. – HartleySan Feb 28 '20 at 23:02

2 Answers2

0

I think you're on to the right solution. I had to do something similar this week. One piece of advice is:

When the onTouchStart event fires, set the onTouchMove event on the document, that way it fires even if the user moves beyond the element that triggered the initial event. (That may not be relevant to your specific UI)

Gerard
  • 768
  • 5
  • 20
  • Thanks, Gerard. Yes, I did set `touchmove` on the `document` object, so I got that going. So basically, for each `touchmove`, I have to calculate whether I'm newly hovering over a DOM element or hovering away from an existing DOM element, and doing everything I want to do accordingly? Essentially, we have to re-code `mouseover` and `mouseout` for touch events manually, yes? Ugh! What a pain. – HartleySan Feb 28 '20 at 22:58
0

Thanks to everyone's help in solving this problem. The document.elementFromPoint method was really the key that made it all possible without a ton of extra code.

Here's an example of the code used allow the grid-cell highlighting I was looking for on both desktop and mobile when a used clicks the mouse / taps their finger on the screen and starts moving the mouse / their finger around:

var isTouchDevice = 'ontouchstart' in document.documentElement;
var isActivelySelecting = false;
var currHoverTarget = null;

if (isTouchDevice) { // Mobile version
    document.addEventListener('touchstart', (evt) => {
        var target = evt.target;

        if (target.classList.contains('class-name-of-selectable-grid-cells')) {
            isActivelySelecting = true;
            currHoverTarget = target;
            toggleSelection(target); // Function for storing selected cells in an array

            // For turning off text highlighting as you select cells
            document.querySelector('body').classList.add('noHighlighting');

            // Stops screen from scrolling on mobile while selecting cells.
            evt.preventDefault();
        }
    }, {
        passive: false // Needed to avoid errors in some browsers.
    });

    document.addEventListener('touchmove', (evt) => {
        if (isActivelySelecting) {
            var target = evt.target;

            if (target.classList.contains('class-name-of-selectable-grid-cells')) {
                var x = evt.touches[0].clientX;
                var y = evt.touches[0].clientY;
                var hoveredElem = document.elementFromPoint(x, y); // The secret sauce

                // Only true when going from one DOM element to another.
                // Basically simulates the mouseover event for desktop browsers.
                if (hoveredElem !== currHoverTarget) {
                    currHoverTarget = hoveredElem;
                    toggleSelection(hoveredElem); // Function for storing selected cells in an array
                }
            }
        }
    });

    document.addEventListener('touchend', () => {
        isActivelySelecting = false;
        currHoverTarget = false;

        document.querySelector('body').classList.remove('noHighlighting');
    });
} else { // Desktop version
    document.addEventListener('mousedown', (evt) => {
        var target = evt.target;

        if (target.classList.contains('class-name-of-selectable-grid-cells')) {
            isActivelySelecting = true;
            toggleSelection(target); // Function for storing selected cells in an array

            // For turning off text highlighting as you select cells
            document.querySelector('body').classList.add('noHighlighting');
        }
    });

    document.addEventListener('mouseover', (evt) => {
        if (isActivelySelecting) {
            var target = evt.target;

            if (target.classList.contains('class-name-of-selectable-grid-cells')) {
                toggleSelection(target); // Function for storing selected cells in an array
            }
        }
    });

    document.addEventListener('mouseup', () => {
        isActivelySelecting = false;

        document.querySelector('body').classList.remove('noHighlighting');
    });
}
HartleySan
  • 7,404
  • 14
  • 66
  • 119