41

Is there a preferred alternative or best practice for handling OnMouseOver javascript events on touch devices? All I can think is to convert all events to OnMouseClick. Unfortunately this muddles the differences between events triggered by hovering the cursor and events triggered by clicking the cursor.

Are there any alternatives, or work arounds, that are less disruptive to the UX of a webpage that will be used with both mouse devices and touch devices?

Cœur
  • 32,421
  • 21
  • 173
  • 232
Daniel Nill
  • 5,041
  • 9
  • 39
  • 62

5 Answers5

38

Is there a preferred alternative or best practice for handling OnMouseOver javascript events on touch devices?

The short answer is no.

Device-specific events don't have a 1:1 mapping to events from other devices. There is no proper equivalent of 'hovering' when using Touch.

Mouse events (mouseover, mouseout, mousedown, mouseup, mousemove, etc) are specific to the mouse input device. The keyboard has keydown, keypress and keyup. Touch has touchstart, touchmove, touchend and touchcancel. Webkit on the iPhone/iPad/etc has additional gesture start/move/end events that are Apple-specific.

Higher-level, generic events such as focus, blur, click, submit, etc can be triggered by one of these events. A click event, for example, can be triggered using a mouse, touch or keyboard event. (click, btw, is an event with an inappropriate name, which should more properly be called action but because of its Mouse history is still called click).

The preferred (or 'One Web') approach is using the Mouse events for mouse specific things where you can't avoid them and and sticking to the generic events for everything else.

Depending on the WebKit build and the flags used to build it you can trigger some Mouse events on some Touch interfaces in some special cases, but you really don't want to build your UI on that because the only reason these cases exist is to allow mobile-Webkit to get traction in the market.

Touch Events are also inconsistent across platforms. Take a look at ppk's work for some reference if you're doing any mobile/touch stuff, http://quirksmode.org/mobile/tableTouch.html

Michiel Kalkman
  • 2,984
  • 21
  • 25
7

The onmouseover/onmouseout javascript events would translate to the touchenter/touchleave touch events, the problem is these events are just starting to being implemented in browsers (they are part of a W3C draft), currently only firefox support it, so if you are using webkit you'll have to wait for it, or implement your onmouseover feature using touchmove event and looking at the coordinates and to see if they overlap with the coordinates of your html element.

Nelson
  • 43,933
  • 8
  • 62
  • 77
4

Depending on your demands and targeted users, you may be interested in the touch JS APIs available (at least) on mobile Safari and Chrome. Check http://backtothecode.blogspot.com/2009/10/javascript-touch-and-gesture-events.html, for a quick (perhaps a bit dated) intro. I haven't really used these extensively (only targeting iPhone, for instance) but I've been very happy with the results I've gotten so far.

Gustav Barkefors
  • 4,626
  • 24
  • 30
4

Unfortunately I do not know about best practices or a preferred alternative to onmouseover on touch-devices, but in encountering the same question I ended up developing this vanillaJS solution where I count the milliseconds between onmouseenter and onclick, and am therefore able to distinguish between desktop-clicking and mobile-clicking.

Upon investigating the two events in the desktop vs. mobile environments, I discovered that a mobile touch natively fires both events instantly (both within zero milliseconds), in contrast to the small desktop-delay of a few dozen milliseconds, depending on the user's trigger-happiness.

;(function(){
 let
  hover_on_mobile = {
   timer: 0,
      // I don't trust the timer with this,
      // so I'm counting myself:
   milliseconds: 0,
      // also cover the case of the user
      // mouseentering, reading or otherwise
      // waiting, and clicking *then*.
   is_counting: false,
  },
    item = document.querySelector('.mobile-hoverable')
 ;
 hover_on_mobile.reset = function(){
  clearInterval(hover_on_mobile.timer);
  hover_on_mobile.milliseconds = 0;
  hover_on_mobile.is_counting = false;
 };

 // hover.
 item.onmouseenter = function(){

  // preparation for onclick's touch-click detection.
  hover_on_mobile.is_counting = true;
  // count the milliseconds starting on each 
    // mouseenter anew.
  hover_on_mobile.timer = window.setInterval(function() {
   // we only need the first few milliseconds for
      // our touch-click detection.
   if (hover_on_mobile.milliseconds > 50) {
    hover_on_mobile.reset();

   } else {
    hover_on_mobile.milliseconds++;
   }
  }, 1);

  hover_behavior();
 };

 // click.
 item.onclick = function(ev){
  let
   condition1 = hover_on_mobile.milliseconds < 10,
   condition2 = hover_on_mobile.is_counting
  ;
  console.log('clicked', {
   condition1: condition1,
   condition2: condition2,
   timer: hover_on_mobile.timer,
      milliseconds: hover_on_mobile.milliseconds,
   is_counting: hover_on_mobile.is_counting,
  });
  // touch-click detection.
  if (condition1 && condition2) {
   // don't do anything; let the onmouseenter 
      // do the hover routine unhinderedly.
      //
   // if this was an onclick event on an ‹a› tag, 
      // the ev.preventDefault(); call would go here.
      
  } else {
   click_behavior();
  }
  hover_on_mobile.reset();
 };
  
  
  // ----------------------------------------
  // fiddle-specfic.

 // reset indicator, not hover_on_mobile.
 item.onmouseout = reset_indicator;

 function click_behavior() {
  document.querySelector('#indicator .click-text').innerText = 'clicked';
 }

 function hover_behavior() {
  document.querySelector('#indicator .hover-text').innerText = 'hovered';
 }

 function reset_indicator() {
  document.querySelector('#indicator .hover-text').innerText = '-';
  document.querySelector('#indicator .click-text').innerText = '-';
 }

 document.querySelector('#indicator .reset').onclick = reset_indicator;

})();
h1 {
  font-size: 20px;
  line-height: 26px;
}

#indicator {
  margin-top: 15px;
  padding: 20px;
  background-color: #ddd;
}

.mobile-hoverable {
  cursor: pointer;
  background-color: antiquewhite;
  border: 1px outset blanchedalmond;
  border-radius: 4px;
  padding: 10px;
}

.notes {
  font-style: italic;
  font-size: 14px;
}
<div class="root">
  <h1>Imagine you wanted mobile users to click once in order to simulate a desktop-hover and twice for a desktop-click</h1>
  
  <div class="mobile-hoverable">Hover me, click me, compare with mobile-touch device mode.</div>
  
  <div id="indicator">
    <button class="reset">Reset</button>
    <span class="hover-text">-</span>
    <span class="click-text">-</span>
  </div>
  
  <ul class="notes">
    <li>Don't forget to reload the page after changing the mode, for optimal testing evaluation.</li>
    <li>Click event console.logs hover_on_mobile object.</li>
    <li>The fiddle's CSS is irrelevant for this feature.</li>
    <li>Relevant JavaScript is bundled together; irrelevant JavaScript at the end.</li>
    <li>Didn't test browser compatibility specifically for this fiddle but the feature works in Chrome, Firefox, Safari, IE10+.</li>
    <li>Subsequent clicks without onmouseout will only fire the click event, in both environments.</li>
  </ul>
</div>

(… or alternatively as a fiddle)

Here is another fiddle to showcase specifically the timing difference between desktop and mobile environments.

WoodrowShigeru
  • 992
  • 13
  • 17
2

I think I managed to create a good simulation (at least for a specific event handler I wanted to simulate) by combining the 'touchmove' event handler and calling the elementFromPoint() method using the coordinates (stored in the clientX / clientY properties of the object) of the relevant Touch object in the event (I used e.touches[0]).

For a more detailed answer, see my answer for this user's specific use case (filling the cells of a table) based on a solution to my own problem (toggling checkbox states) here: https://stackoverflow.com/a/31711040/1941313.

Alternatively, read the full report I wrote about finding the write event handler design including the sources of for my findings in the following gist: https://gist.github.com/VehpuS/6fd5dca2ea8cd0eb0471

(I would have posted them in stackoverflow but my rep is too low at the moment so I can only provide two links :P)

Hope this helps!

Community
  • 1
  • 1