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
+ }
+ });
+});
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..2bc2bd6c 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,36 @@
// 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]];
// Ignore 'disabled' handles
- if ( handle.hasAttribute('disabled') ) {
+ if ( handleOrigin.hasAttribute('disabled') ) {
return false;
}
+ handle = handleOrigin.children[0];
+ scope_ActiveHandlesCount += 1;
+
// 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 +102,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;