Skip to content

Commit f964821

Browse files
authored
fix(slider): support mouse & touch all the time
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.
1 parent 4d5303c commit f964821

1 file changed

Lines changed: 67 additions & 50 deletions

File tree

src/definitions/modules/slider.js

Lines changed: 67 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ $.fn.slider = function(parameters) {
7070

7171
$module = $(this),
7272
$currThumb,
73+
touchIdentifier,
7374
$thumb,
7475
$secondThumb,
7576
$track,
@@ -86,7 +87,6 @@ $.fn.slider = function(parameters) {
8687
secondPos,
8788
offset,
8889
precision,
89-
isTouch,
9090
gapRatio = 1,
9191
previousValue,
9292

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

107-
isTouch = module.setup.testOutTouch();
108107
module.setup.layout();
109108
module.setup.labels();
110109

@@ -175,14 +174,6 @@ $.fn.slider = function(parameters) {
175174
}
176175
}
177176
},
178-
testOutTouch: function() {
179-
try {
180-
document.createEvent('TouchEvent');
181-
return true;
182-
} catch (e) {
183-
return false;
184-
}
185-
},
186177
customLabel: function() {
187178
var
188179
$children = $labels.find('.label'),
@@ -236,9 +227,6 @@ $.fn.slider = function(parameters) {
236227
module.bind.globalKeyboardEvents();
237228
module.bind.keyboardEvents();
238229
module.bind.mouseEvents();
239-
if(module.is.touch()) {
240-
module.bind.touchEvents();
241-
}
242230
if (settings.autoAdjustLabels) {
243231
module.bind.windowEvents();
244232
}
@@ -251,7 +239,7 @@ $.fn.slider = function(parameters) {
251239
$(document).on('keydown' + eventNamespace + documentEventID, module.event.activateFocus);
252240
},
253241
mouseEvents: function() {
254-
module.verbose('Binding mouse events');
242+
module.verbose('Binding mouse and touch events');
255243
$module.find('.track, .thumb, .inner').on('mousedown' + eventNamespace, function(event) {
256244
event.stopImmediatePropagation();
257245
event.preventDefault();
@@ -264,27 +252,20 @@ $.fn.slider = function(parameters) {
264252
$module.on('mouseleave' + eventNamespace, function(event) {
265253
isHover = false;
266254
});
267-
},
268-
touchEvents: function() {
269-
module.verbose('Binding touch events');
270-
$module.find('.track, .thumb, .inner').on('touchstart' + eventNamespace, function(event) {
271-
event.stopImmediatePropagation();
272-
event.preventDefault();
273-
module.event.down(event);
274-
});
275-
$module.on('touchstart' + eventNamespace, module.event.down);
255+
// All touch events are invoked on the element where the touch *started*. Thus, we can bind them all
256+
// on the thumb(s) and don't need to worry about interference with other components, i.e. no dynamic binding
257+
// and unbinding required.
258+
$module.find('.thumb')
259+
.on('touchstart' + eventNamespace, module.event.touchDown)
260+
.on('touchmove' + eventNamespace, module.event.move)
261+
.on('touchend' + eventNamespace, module.event.up)
262+
.on('touchcancel' + eventNamespace, module.event.touchCancel);
276263
},
277264
slidingEvents: function() {
278265
// these don't need the identifier because we only ever want one of them to be registered with document
279266
module.verbose('Binding page wide events while handle is being draged');
280-
if(module.is.touch()) {
281-
$(document).on('touchmove' + eventNamespace, module.event.move);
282-
$(document).on('touchend' + eventNamespace, module.event.up);
283-
}
284-
else {
285-
$(document).on('mousemove' + eventNamespace, module.event.move);
286-
$(document).on('mouseup' + eventNamespace, module.event.up);
287-
}
267+
$(document).on('mousemove' + eventNamespace, module.event.move);
268+
$(document).on('mouseup' + eventNamespace, module.event.up);
288269
},
289270
windowEvents: function() {
290271
$window.on('resize' + eventNamespace, module.event.resize);
@@ -294,24 +275,22 @@ $.fn.slider = function(parameters) {
294275
unbind: {
295276
events: function() {
296277
$module.find('.track, .thumb, .inner').off('mousedown' + eventNamespace);
297-
$module.find('.track, .thumb, .inner').off('touchstart' + eventNamespace);
298278
$module.off('mousedown' + eventNamespace);
299279
$module.off('mouseenter' + eventNamespace);
300280
$module.off('mouseleave' + eventNamespace);
301-
$module.off('touchstart' + eventNamespace);
281+
$module.find('.thumb')
282+
.off('touchstart' + eventNamespace)
283+
.off('touchmove' + eventNamespace)
284+
.off('touchend' + eventNamespace)
285+
.off('touchcancel' + eventNamespace);
302286
$module.off('keydown' + eventNamespace);
303287
$module.off('focusout' + eventNamespace);
304288
$(document).off('keydown' + eventNamespace + documentEventID, module.event.activateFocus);
305289
$window.off('resize' + eventNamespace);
306290
},
307291
slidingEvents: function() {
308-
if(module.is.touch()) {
309-
$(document).off('touchmove' + eventNamespace);
310-
$(document).off('touchend' + eventNamespace);
311-
} else {
312-
$(document).off('mousemove' + eventNamespace);
313-
$(document).off('mouseup' + eventNamespace);
314-
}
292+
$(document).off('mousemove' + eventNamespace);
293+
$(document).off('mouseup' + eventNamespace);
315294
},
316295
},
317296

@@ -341,10 +320,31 @@ $.fn.slider = function(parameters) {
341320
module.bind.slidingEvents();
342321
}
343322
},
323+
touchDown: function(event) {
324+
event.preventDefault(); // disable mouse emulation and touch-scrolling
325+
event.stopImmediatePropagation();
326+
if(touchIdentifier !== undefined) {
327+
// ignore multiple touches on the same slider --
328+
// we cannot handle changing both thumbs at once due to shared state
329+
return;
330+
}
331+
$currThumb = $(event.target);
332+
var touchEvent = event.touches ? event : event.originalEvent;
333+
touchIdentifier = touchEvent.targetTouches[0].identifier;
334+
if(previousValue === undefined) {
335+
previousValue = module.get.currentThumbValue();
336+
}
337+
},
344338
move: function(event) {
345-
event.preventDefault();
339+
if(event.type == 'mousemove') {
340+
event.preventDefault(); // prevent text selection etc.
341+
}
342+
if(module.is.disabled()) {
343+
// touch events are always bound, so we need to prevent touch-sliding on disabled sliders here
344+
return;
345+
}
346346
var value = module.determine.valueFromEvent(event);
347-
if($currThumb === undefined) {
347+
if(event.type == 'mousemove' && $currThumb === undefined) {
348348
var
349349
eventPos = module.determine.eventPos(event),
350350
newPos = module.determine.pos(eventPos)
@@ -381,13 +381,26 @@ $.fn.slider = function(parameters) {
381381
},
382382
up: function(event) {
383383
event.preventDefault();
384+
if(module.is.disabled()) {
385+
// touch events are always bound, so we need to prevent touch-sliding on disabled sliders here
386+
return;
387+
}
384388
var value = module.determine.valueFromEvent(event);
385389
module.set.value(value);
386390
module.unbind.slidingEvents();
391+
touchIdentifier = undefined;
387392
if (previousValue !== undefined) {
388393
previousValue = undefined;
389394
}
390395
},
396+
touchCancel: function(event) {
397+
event.preventDefault();
398+
touchIdentifier = undefined;
399+
if (previousValue !== undefined) {
400+
module.update.value(previousValue);
401+
previousValue = undefined;
402+
}
403+
},
391404
keydown: function(event, first) {
392405
if(settings.preventCrossover && module.is.range() && module.thumbVal === module.secondThumbVal) {
393406
$currThumb = undefined;
@@ -500,9 +513,6 @@ $.fn.slider = function(parameters) {
500513
},
501514
smooth: function() {
502515
return settings.smooth || $module.hasClass(settings.className.smooth);
503-
},
504-
touch: function() {
505-
return isTouch;
506516
}
507517
},
508518

@@ -766,12 +776,19 @@ $.fn.slider = function(parameters) {
766776
return value;
767777
},
768778
eventPos: function(event) {
769-
if(module.is.touch()) {
779+
if(event.type === "touchmove" || event.type === "touchend") {
780+
var
781+
touchEvent = event.touches ? event : event.originalEvent,
782+
touch = touchEvent.changedTouches[0]; // fall back to first touch if correct touch not found
783+
for(var i=0; i < touchEvent.touches.length; i++) {
784+
if(touchEvent.touches[i].identifier === touchIdentifier) {
785+
touch = touchEvent.touches[i];
786+
break;
787+
}
788+
}
770789
var
771-
touchEvent = event.changedTouches ? event : event.originalEvent,
772-
touches = touchEvent.changedTouches[0] ? touchEvent.changedTouches : touchEvent.touches,
773-
touchY = touches[0].pageY,
774-
touchX = touches[0].pageX
790+
touchY = touch.pageY,
791+
touchX = touch.pageX
775792
;
776793
return module.is.vertical() ? touchY : touchX;
777794
}

0 commit comments

Comments
 (0)