From c5cea4d234755a0560da06422b0432af440f05a9 Mon Sep 17 00:00:00 2001 From: Quentin Roy Date: Mon, 12 Jun 2017 14:49:55 +0800 Subject: [PATCH 1/3] Implement support for multitouch. --- src/js/options.js | 10 ++++++++ src/js/scope_events.js | 55 +++++++++++++++++++++++++++-------------- src/js/scope_helpers.js | 33 ++++++++++++++++++++++--- src/js/scope_start.js | 3 +-- 4 files changed, 78 insertions(+), 23 deletions(-) diff --git a/src/js/options.js b/src/js/options.js index ff4424e2..bd15fc45 100644 --- a/src/js/options.js +++ b/src/js/options.js @@ -262,6 +262,14 @@ }; } + function testMultitouch ( parsed, entry ) { + parsed.multitouch = entry; + + if ( typeof entry !== 'boolean' ){ + throw new Error("noUiSlider (" + VERSION + "): 'multitouch' option must be a boolean."); + } + } + function testTooltips ( parsed, entry ) { if ( entry === false ) { @@ -371,6 +379,7 @@ 'limit': { r: false, t: testLimit }, 'padding': { r: false, t: testPadding }, 'behaviour': { r: true, t: testBehaviour }, + 'multitouch': { r: true, t: testMultitouch }, 'ariaFormat': { r: false, t: testAriaFormat }, 'format': { r: false, t: testFormat }, 'tooltips': { r: false, t: testTooltips }, @@ -383,6 +392,7 @@ 'connect': false, 'direction': 'ltr', 'behaviour': 'tap', + 'multitouch': false, 'orientation': 'horizontal', 'cssPrefix' : 'noUi-', 'cssClasses': { diff --git a/src/js/scope_events.js b/src/js/scope_events.js index a76a4162..e78bb61b 100644 --- a/src/js/scope_events.js +++ b/src/js/scope_events.js @@ -31,26 +31,27 @@ function eventEnd ( event, data ) { // The handle is no longer active, so remove the class. - if ( scope_ActiveHandle ) { - removeClass(scope_ActiveHandle, options.cssClasses.active); - scope_ActiveHandle = false; - } - - // Remove cursor styles and text-selection events bound to the body. - if ( event.cursor ) { - scope_Body.style.cursor = ''; - scope_Body.removeEventListener('selectstart', preventDefault); + if ( data.handle ) { + removeClass(data.handle, options.cssClasses.active); + scope_ActiveHandlesCount -= 1; } // Unbind the move and end events, which are added on 'start'. - scope_Listeners.forEach(function( c ) { + data.listeners.forEach(function( c ) { scope_DocumentElement.removeEventListener(c[0], c[1]); }); - // Remove dragging class. - removeClass(scope_Target, options.cssClasses.drag); + if ( scope_ActiveHandlesCount === 0 ) { + // Remove dragging class. + removeClass(scope_Target, options.cssClasses.drag); + setZindex(); - setZindex(); + // Remove cursor styles and text-selection events bound to the body. + if ( event.cursor ) { + scope_Body.style.cursor = ''; + scope_Body.removeEventListener('selectstart', preventDefault); + } + } data.handleNumbers.forEach(function(handleNumber){ fireEvent('change', handleNumber); @@ -62,25 +63,35 @@ // Bind move events on document. function eventStart ( event, data ) { + var handle; if ( data.handleNumbers.length === 1 ) { - var handle = scope_Handles[data.handleNumbers[0]]; + var handleOrigin = scope_Handles[data.handleNumbers[0]]; + handle = handleOrigin.children[0]; + scope_ActiveHandlesCount += 1; // Ignore 'disabled' handles - if ( handle.hasAttribute('disabled') ) { + if ( handleOrigin.hasAttribute('disabled') ) { return false; } // Mark the handle as 'active' so it can be styled. - scope_ActiveHandle = handle.children[0]; - addClass(scope_ActiveHandle, options.cssClasses.active); + addClass(handle, options.cssClasses.active); } // A drag should never propagate up to the 'tap' event. event.stopPropagation(); + // Record the event listeners. + var listeners = []; + // Attach the move and end events. var moveEvent = attachEvent(actions.move, scope_DocumentElement, eventMove, { + // The event target has changed so we need to propagate the original one so that we keep + // relying on it to extract target touches. + target: event.target, + handle: handle, + listeners: listeners, startCalcPoint: event.calcPoint, baseSize: baseSize(), pageOffset: event.pageOffset, @@ -90,14 +101,22 @@ }); var endEvent = attachEvent(actions.end, scope_DocumentElement, eventEnd, { + target: event.target, + handle: handle, + listeners: listeners, handleNumbers: data.handleNumbers }); var outEvent = attachEvent("mouseout", scope_DocumentElement, documentLeave, { + target: event.target, + handle: handle, + listeners: listeners, handleNumbers: data.handleNumbers }); - scope_Listeners = moveEvent.concat(endEvent, outEvent); + // We want to make sure we pushed the listeners in the listener list rather than creating + // a new one as it has already been passed to the event handlers. + listeners.push.apply(listeners, moveEvent.concat(endEvent, outEvent)); // Text selection isn't an issue on touch devices, // so adding cursor styles can be skipped. diff --git a/src/js/scope_helpers.js b/src/js/scope_helpers.js index 5a8e7606..4e96f0ef 100644 --- a/src/js/scope_helpers.js +++ b/src/js/scope_helpers.js @@ -22,7 +22,7 @@ return false; } - e = fixEvent(e, data.pageOffset); + e = fixEvent(e, data.pageOffset, data.target || element); // Handle reject of multitouch if ( !e ) { @@ -66,7 +66,7 @@ } // Provide a clean event with standardized offset values. - function fixEvent ( e, pageOffset ) { + function fixEvent ( e, pageOffset, target ) { // Filter the event to register the type, which can be // touch, mouse or pointer. Offset changes need to be @@ -83,8 +83,35 @@ pointer = true; } - if ( touch ) { + // In the event that multitouch is activated, the only thing one handle should be concerned + // about is the touches that originated on top of it. + if ( touch && options.multitouch ) { + // Returns true if a touch originated on the target. + var isTouchOnTarget = function (touch) { + return touch.target === target || target.contains(touch.target); + }; + // In the case of touchstart events, we need to make sure there is still no more than one + // touch on the target so we look amongst all touches. + if (e.type === 'touchstart') { + var targetTouches = Array.prototype.filter.call(e.touches, isTouchOnTarget); + // Do not support more than one touch per handle. + if ( targetTouches.length > 1 ) { + return false; + } + x = targetTouches[0].pageX; + y = targetTouches[0].pageY; + } else { + // In the other cases, find on changedTouches is enough. + var targetTouch = Array.prototype.find.call(e.changedTouches, isTouchOnTarget); + // Cancel if the target touch has not moved. + if ( !targetTouch ) { + return false; + } + x = targetTouch.pageX; + y = targetTouch.pageY; + } + } else if ( touch ) { // Fix bug when user touches with two or more fingers on mobile devices. // It's useful when you have two or more sliders on one page, // that can be touched simultaneously. diff --git a/src/js/scope_start.js b/src/js/scope_start.js index e4e84d5e..f7c9d27e 100644 --- a/src/js/scope_start.js +++ b/src/js/scope_start.js @@ -11,14 +11,13 @@ function closure ( target, options, originalOptions ){ var scope_Base; var scope_Handles; var scope_HandleNumbers = []; - var scope_ActiveHandle = false; + var scope_ActiveHandlesCount = 0; var scope_Connects; var scope_Spectrum = options.spectrum; var scope_Values = []; var scope_Events = {}; var scope_Self; var scope_Pips; - var scope_Listeners = null; var scope_Document = target.ownerDocument; var scope_DocumentElement = scope_Document.documentElement; var scope_Body = scope_Document.body; From 8ead94c870400fa221344da31d13c7ac2fb63513 Mon Sep 17 00:00:00 2001 From: Quentin Roy Date: Tue, 13 Jun 2017 15:05:10 +0800 Subject: [PATCH 2/3] Documentation of the multitouch option. --- documentation/slider-options.php | 31 ++++++++++++++++++++++ documentation/slider-options/multitouch.js | 11 ++++++++ 2 files changed, 42 insertions(+) create mode 100644 documentation/slider-options/multitouch.js diff --git a/documentation/slider-options.php b/documentation/slider-options.php index 686a85b8..ae75b368 100644 --- a/documentation/slider-options.php +++ b/documentation/slider-options.php @@ -387,3 +387,34 @@ + + + + +

Multitouch

+ +
+
+

Set the multitouch option to true to allow simultaneous interaction with a slider and other content on the page (e.g. another slider). It is even possible to simultaneously control several handles of the same slider.

+ +
+
+
+
+ +
+ +
+ Default +
false
+ + Accepted values +
true, false
+
+ +
+ +
+ +
+
diff --git a/documentation/slider-options/multitouch.js b/documentation/slider-options/multitouch.js new file mode 100644 index 00000000..aed840ba --- /dev/null +++ b/documentation/slider-options/multitouch.js @@ -0,0 +1,11 @@ +[1, 2, 3].forEach(function (i) { + slider = document.getElementById('slider-multitouch-' + i); + noUiSlider.create(slider, { + start: [20, 80], + multitouch: true, + range: { + min: 0, + max: 100 + } + }); +}); From dec3a6c760e52a20ebd493c40083667604f7baf4 Mon Sep 17 00:00:00 2001 From: Quentin Roy Date: Tue, 13 Jun 2017 15:17:57 +0800 Subject: [PATCH 3/3] Fix a minor bug making disabled handle to be counted in the activeHandlesCount. This fix allows the drag class to be properly removed when a drag stops, even if disabled handles are the target of touches. --- src/js/scope_events.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/js/scope_events.js b/src/js/scope_events.js index e78bb61b..2bc2bd6c 100644 --- a/src/js/scope_events.js +++ b/src/js/scope_events.js @@ -67,14 +67,15 @@ if ( data.handleNumbers.length === 1 ) { var handleOrigin = scope_Handles[data.handleNumbers[0]]; - handle = handleOrigin.children[0]; - scope_ActiveHandlesCount += 1; // Ignore 'disabled' handles if ( handleOrigin.hasAttribute('disabled') ) { return false; } + handle = handleOrigin.children[0]; + scope_ActiveHandlesCount += 1; + // Mark the handle as 'active' so it can be styled. addClass(handle, options.cssClasses.active); }