Skip to content

Commit

Permalink
fix(slider): support mouse & touch all the time
Browse files Browse the repository at this point in the history
As proposed in #1988, I did a rework of the touch event handling of the slider component. The new touch event code
does not prevent mouse input (fixes [slider] cannot move slider using mouse on iPad #1988)
tracks Touch identifier to avoid confusion by other fingers on the touch screen
handles touchcancel events in a sensible way
does no dynamic binding and unbinding to all touch events on the page. In combination with the second point, this enables multitouch sliding of multiple sliders on the same page (I know, nobody asked for this, but it works. ;) ) – Unfortunately, multitouch sliding of the first and second thumb of the same slider is not easy to implement due global state within each slider module.
only binds to touchstart events on the .thumb element to avoid accidential sliding while scrolling the page on a mobile device (obsoletes [slider] Add option to disable touch-/mouse-sliding on the track #1987)
The last point can be considered a disadvantage/regression compared to the old code, if you consider touch-sliding anywhere on the slider as a feature. However, in my experience, it's really annoying to change a slider position (which has immediate real-world effects in my home automation software) when scrolling the page and accidentially hitting a slider. Using the mouse, one can still slide the slider anywhere on its track.
  • Loading branch information
mhthies committed Apr 18, 2022
1 parent 4d5303c commit f964821
Showing 1 changed file with 67 additions and 50 deletions.
117 changes: 67 additions & 50 deletions src/definitions/modules/slider.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ $.fn.slider = function(parameters) {

$module = $(this),
$currThumb,
touchIdentifier,
$thumb,
$secondThumb,
$track,
Expand All @@ -86,7 +87,6 @@ $.fn.slider = function(parameters) {
secondPos,
offset,
precision,
isTouch,
gapRatio = 1,
previousValue,

Expand All @@ -104,7 +104,6 @@ $.fn.slider = function(parameters) {
currentRange += 1;
documentEventID = currentRange;

isTouch = module.setup.testOutTouch();
module.setup.layout();
module.setup.labels();

Expand Down Expand Up @@ -175,14 +174,6 @@ $.fn.slider = function(parameters) {
}
}
},
testOutTouch: function() {
try {
document.createEvent('TouchEvent');
return true;
} catch (e) {
return false;
}
},
customLabel: function() {
var
$children = $labels.find('.label'),
Expand Down Expand Up @@ -236,9 +227,6 @@ $.fn.slider = function(parameters) {
module.bind.globalKeyboardEvents();
module.bind.keyboardEvents();
module.bind.mouseEvents();
if(module.is.touch()) {
module.bind.touchEvents();
}
if (settings.autoAdjustLabels) {
module.bind.windowEvents();
}
Expand All @@ -251,7 +239,7 @@ $.fn.slider = function(parameters) {
$(document).on('keydown' + eventNamespace + documentEventID, module.event.activateFocus);
},
mouseEvents: function() {
module.verbose('Binding mouse events');
module.verbose('Binding mouse and touch events');
$module.find('.track, .thumb, .inner').on('mousedown' + eventNamespace, function(event) {
event.stopImmediatePropagation();
event.preventDefault();
Expand All @@ -264,27 +252,20 @@ $.fn.slider = function(parameters) {
$module.on('mouseleave' + eventNamespace, function(event) {
isHover = false;
});
},
touchEvents: function() {
module.verbose('Binding touch events');
$module.find('.track, .thumb, .inner').on('touchstart' + eventNamespace, function(event) {
event.stopImmediatePropagation();
event.preventDefault();
module.event.down(event);
});
$module.on('touchstart' + eventNamespace, module.event.down);
// All touch events are invoked on the element where the touch *started*. Thus, we can bind them all
// on the thumb(s) and don't need to worry about interference with other components, i.e. no dynamic binding
// and unbinding required.
$module.find('.thumb')
.on('touchstart' + eventNamespace, module.event.touchDown)
.on('touchmove' + eventNamespace, module.event.move)
.on('touchend' + eventNamespace, module.event.up)
.on('touchcancel' + eventNamespace, module.event.touchCancel);
},
slidingEvents: function() {
// these don't need the identifier because we only ever want one of them to be registered with document
module.verbose('Binding page wide events while handle is being draged');
if(module.is.touch()) {
$(document).on('touchmove' + eventNamespace, module.event.move);
$(document).on('touchend' + eventNamespace, module.event.up);
}
else {
$(document).on('mousemove' + eventNamespace, module.event.move);
$(document).on('mouseup' + eventNamespace, module.event.up);
}
$(document).on('mousemove' + eventNamespace, module.event.move);
$(document).on('mouseup' + eventNamespace, module.event.up);
},
windowEvents: function() {
$window.on('resize' + eventNamespace, module.event.resize);
Expand All @@ -294,24 +275,22 @@ $.fn.slider = function(parameters) {
unbind: {
events: function() {
$module.find('.track, .thumb, .inner').off('mousedown' + eventNamespace);
$module.find('.track, .thumb, .inner').off('touchstart' + eventNamespace);
$module.off('mousedown' + eventNamespace);
$module.off('mouseenter' + eventNamespace);
$module.off('mouseleave' + eventNamespace);
$module.off('touchstart' + eventNamespace);
$module.find('.thumb')
.off('touchstart' + eventNamespace)
.off('touchmove' + eventNamespace)
.off('touchend' + eventNamespace)
.off('touchcancel' + eventNamespace);
$module.off('keydown' + eventNamespace);
$module.off('focusout' + eventNamespace);
$(document).off('keydown' + eventNamespace + documentEventID, module.event.activateFocus);
$window.off('resize' + eventNamespace);
},
slidingEvents: function() {
if(module.is.touch()) {
$(document).off('touchmove' + eventNamespace);
$(document).off('touchend' + eventNamespace);
} else {
$(document).off('mousemove' + eventNamespace);
$(document).off('mouseup' + eventNamespace);
}
$(document).off('mousemove' + eventNamespace);
$(document).off('mouseup' + eventNamespace);
},
},

Expand Down Expand Up @@ -341,10 +320,31 @@ $.fn.slider = function(parameters) {
module.bind.slidingEvents();
}
},
touchDown: function(event) {
event.preventDefault(); // disable mouse emulation and touch-scrolling
event.stopImmediatePropagation();
if(touchIdentifier !== undefined) {
// ignore multiple touches on the same slider --
// we cannot handle changing both thumbs at once due to shared state
return;
}
$currThumb = $(event.target);
var touchEvent = event.touches ? event : event.originalEvent;
touchIdentifier = touchEvent.targetTouches[0].identifier;
if(previousValue === undefined) {
previousValue = module.get.currentThumbValue();
}
},
move: function(event) {
event.preventDefault();
if(event.type == 'mousemove') {
event.preventDefault(); // prevent text selection etc.
}
if(module.is.disabled()) {
// touch events are always bound, so we need to prevent touch-sliding on disabled sliders here
return;
}
var value = module.determine.valueFromEvent(event);
if($currThumb === undefined) {
if(event.type == 'mousemove' && $currThumb === undefined) {
var
eventPos = module.determine.eventPos(event),
newPos = module.determine.pos(eventPos)
Expand Down Expand Up @@ -381,13 +381,26 @@ $.fn.slider = function(parameters) {
},
up: function(event) {
event.preventDefault();
if(module.is.disabled()) {
// touch events are always bound, so we need to prevent touch-sliding on disabled sliders here
return;
}
var value = module.determine.valueFromEvent(event);
module.set.value(value);
module.unbind.slidingEvents();
touchIdentifier = undefined;
if (previousValue !== undefined) {
previousValue = undefined;
}
},
touchCancel: function(event) {
event.preventDefault();
touchIdentifier = undefined;
if (previousValue !== undefined) {
module.update.value(previousValue);
previousValue = undefined;
}
},
keydown: function(event, first) {
if(settings.preventCrossover && module.is.range() && module.thumbVal === module.secondThumbVal) {
$currThumb = undefined;
Expand Down Expand Up @@ -500,9 +513,6 @@ $.fn.slider = function(parameters) {
},
smooth: function() {
return settings.smooth || $module.hasClass(settings.className.smooth);
},
touch: function() {
return isTouch;
}
},

Expand Down Expand Up @@ -766,12 +776,19 @@ $.fn.slider = function(parameters) {
return value;
},
eventPos: function(event) {
if(module.is.touch()) {
if(event.type === "touchmove" || event.type === "touchend") {
var
touchEvent = event.touches ? event : event.originalEvent,
touch = touchEvent.changedTouches[0]; // fall back to first touch if correct touch not found
for(var i=0; i < touchEvent.touches.length; i++) {
if(touchEvent.touches[i].identifier === touchIdentifier) {
touch = touchEvent.touches[i];
break;
}
}
var
touchEvent = event.changedTouches ? event : event.originalEvent,
touches = touchEvent.changedTouches[0] ? touchEvent.changedTouches : touchEvent.touches,
touchY = touches[0].pageY,
touchX = touches[0].pageX
touchY = touch.pageY,
touchX = touch.pageX
;
return module.is.vertical() ? touchY : touchX;
}
Expand Down

0 comments on commit f964821

Please sign in to comment.