As found here - https://github.com/neave/touch-scroll:
(function($) {
// Define default scroll settings
var defaults = {
y: 0,
scrollHeight: 0,
elastic: !navigator.userAgent.match(/android/i),
momentum: true,
elasticDamp: 0.6,
elasticTime: 50,
reboundTime: 400,
momentumDamp: 0.9,
momentumTime: 300,
iPadMomentumDamp: 0.95,
iPadMomentumTime: 1200,
touchTags: ['select', 'input', 'textarea']
};
// Define methods
var methods = {
init: function(options) {
return this.each(function() {
var o = $.extend(defaults, options);
// Prevent double-init, just update instead
if (!!this._init) {
return this.update();
}
this._init = true;
// Define element variables
var $this = $(this),
scrollY = -o.y,
touchY = 0,
movedY = 0,
pollY = 0,
height = 0,
maxHeight = 0,
scrollHeight = 0,
scrolling = false,
bouncing = false,
moved = false,
timeoutID,
isiPad = !!navigator.platform.match(/ipad/i),
hasMatrix = 'WebKitCSSMatrix' in window,
has3d = hasMatrix && 'm11' in new WebKitCSSMatrix();
// Keep bottom of scroll area at the bottom on resize
var update = this.update = function() {
height = $this.height();
if (o.scrollHeight) {
scrollHeight = o.scrollHeight;
} else if ($this.prop) {
scrollHeight = $this.prop('scrollHeight'); // jQuery 1.6 uses .prop(), older versions use .attr()
} else {
scrollHeight = $this.attr('scrollHeight');
}
if (scrollHeight < height) {
scrollHeight = height;
}
maxHeight = height - scrollHeight;
clearTimeout(timeoutID);
clampScroll(false);
};
// Set up initial variables
update();
// Set up transform CSS
$this.css({'-webkit-transition-property': '-webkit-transform',
'-webkit-transition-timing-function': 'cubic-bezier(0,0,0.2,1)',
'-webkit-transition-duration': '0',
'-webkit-transform': cssTranslate(scrollY)});
// Listen for screen size change event
window.addEventListener('onorientationchange' in window ? 'orientationchange' : 'resize', update, false);
// Listen for touch events
$this.bind('touchstart.touchScroll', touchStart);
$this.bind('touchmove.touchScroll', touchMove);
$this.bind('touchend.touchScroll touchcancel.touchScroll', touchEnd);
$this.bind('webkitTransitionEnd.touchScroll', transitionEnd);
// Set the position of the scroll area using transform CSS
var setPosition = this.setPosition = function(y) {
scrollY = y;
$this.css('-webkit-transform', cssTranslate(scrollY));
};
// Transform using a 3D translate if available
function cssTranslate(y) {
return 'translate' + (has3d ? '3d(0,' : '(0,') + y + 'px' + (has3d ? ',0)' : ')');
}
// Set CSS transition time
function setTransitionTime(time) {
time = time || '0';
$this.css('-webkit-transition-duration', time + 'ms');
}
// Get the actual pixel position made by transform CSS
function getPosition() {
if (hasMatrix) {
var transform = window.getComputedStyle($this[0]).webkitTransform;
if (!!transform && transform !== 'none') {
var matrix = new WebKitCSSMatrix(transform);
return matrix.f;
}
}
return scrollY;
}
// Expose getPosition API
this.getPosition = function() {
return getPosition();
};
// Bounce back to the bounds after momentum scrolling
function reboundScroll() {
if (scrollY > 0) {
scrollTo(0, o.reboundTime);
} else if (scrollY < maxHeight) {
scrollTo(maxHeight, o.reboundTime);
}
}
// Stop everything once the CSS transition in complete
function transitionEnd() {
if (bouncing) {
bouncing = false;
reboundScroll();
}
clearTimeout(timeoutID);
}
// Limit the scrolling to within the bounds
function clampScroll(poll) {
if (!hasMatrix || bouncing) {
return;
}
var oldY = pollY;
pollY = getPosition();
if (pollY > 0) {
if (o.elastic) {
// Slow down outside top bound
bouncing = true;
scrollY = 0;
momentumScroll(pollY - oldY, o.elasticDamp, 1, height, o.elasticTime);
} else {
// Stop outside top bound
setTransitionTime(0);
setPosition(0);
}
} else if (pollY < maxHeight) {
if (o.elastic) {
// Slow down outside bottom bound
bouncing = true;
scrollY = maxHeight;
momentumScroll(pollY - oldY, o.elasticDamp, 1, height, o.elasticTime);
} else {
// Stop outside bottom bound
setTransitionTime(0);
setPosition(maxHeight);
}
} else if (poll) {
// Poll the computed position to check if element is out of bounds
timeoutID = setTimeout(clampScroll, 20, true);
}
}
// Animate to a position using CSS
function scrollTo(destY, time) {
if (destY === scrollY) {
return;
}
moved = true;
setTransitionTime(time);
setPosition(destY);
}
// Perform a momentum-based scroll using CSS
function momentumScroll(d, k, minDist, maxDist, t) {
var ad = Math.abs(d),
dy = 0;
// Calculate the total distance
while (ad > 0.1) {
ad *= k;
dy += ad;
}
// Limit to within min and max distances
if (dy > maxDist) {
dy = maxDist;
}
if (dy > minDist) {
if (d < 0) {
dy = -dy;
}
dy += scrollY;
// If outside the bounds, don't go too far
if (height > 0) {
if (dy > height * 2) {
var ody = dy;
dy = height * 2;
} else if (dy < maxHeight - height * 2) {
dy = maxHeight - height * 2;
}
}
// Perform scroll
scrollTo(Math.round(dy), t);
}
clampScroll(true);
}
// Get the touch points from this event
function getTouches(e) {
if (e.originalEvent) {
if (e.originalEvent.touches && e.originalEvent.touches.length) {
return e.originalEvent.touches;
} else if (e.originalEvent.changedTouches && e.originalEvent.changedTouches.length) {
return e.originalEvent.changedTouches;
}
}
return e.touches;
}
// Dispatches a fake mouse event from a touch event
function dispatchMouseEvent(name, touch, target) {
var e = document.createEvent('MouseEvent');
e.initMouseEvent(name, true, true, touch.view, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);
target.dispatchEvent(e);
}
// Find the root node of this target
function getRootNode(target) {
while (target.nodeType !== 1) {
target = target.parentNode;
}
return target;
}
// Perform a touch start event
function touchStart(e) {
// Allow certain HTML tags to receive touch events
if ($.inArray(e.target.tagName.toLowerCase(), o.touchTags) !== -1) {
return;
}
// Stop the default touches
e.preventDefault();
e.stopPropagation();
var touch = getTouches(e)[0];
// Dispatch a fake mouse down event
dispatchMouseEvent('mousedown', touch, getRootNode(touch.target));
scrolling = true;
moved = false;
movedY = 0;
clearTimeout(timeoutID);
setTransitionTime(0);
// Check scroll position
if (o.momentum) {
var y = getPosition();
if (y !== scrollY) {
setPosition(y);
moved = true;
}
}
touchY = touch.pageY - scrollY;
}
// Perform a touch move event
function touchMove(e) {
if (!scrolling) {
return;
}
var dy = getTouches(e)[0].pageY - touchY;
// Elastic-drag or stop when moving outside of boundaries
if (dy > 0) {
if (o.elastic) {
dy /= 2;
} else {
dy = 0;
}
} else if (dy < maxHeight) {
if (o.elastic) {
dy = (dy + maxHeight) / 2;
} else {
dy = maxHeight;
}
}
movedY = dy - scrollY;
moved = true;
setPosition(dy);
}
// Perform a touch end event
function touchEnd(e) {
if (!scrolling) {
return;
}
scrolling = false;
if (moved) {
// Ease back to within boundaries
if (scrollY > 0 || scrollY < maxHeight) {
reboundScroll();
} else if (o.momentum) {
// Free scroll with momentum
momentumScroll(movedY, isiPad ? o.iPadMomentumDamp : o.momentumDamp, 40, 2000, isiPad ? o.iPadMomentumTime : o.momentumTime);
}
} else {
var touch = getTouches(e)[0],
target = getRootNode(touch.target);
// Dispatch fake mouse up and click events if this touch event did not move
dispatchMouseEvent('mouseup', touch, target);
dispatchMouseEvent('click', touch, target);
}
}
});
},
update: function() {
return this.each(function() {
this.update();
});
},
getPosition: function() {
var a = [];
this.each(function() {
a.push(-this.getPosition());
});
return a;
},
setPosition: function(y) {
return this.each(function() {
this.setPosition(-y);
});
}
};
// Public method for touchScroll
$.fn.touchScroll = function(method) {
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
} else if (typeof method === 'object' || !method) {
return methods.init.apply(this, arguments);
} else {
$.error('Method ' + method + ' does not exist on jQuery.touchScroll');
}
};
})(jQuery);
How to make this work on multiple instances with different selectors and unique options for each like this?:
$('#selector1').touchScroll({elastic: false});
$('#selector2').touchScroll({elastic: true});
Currently options set in either override the other.
This is my modified code for multiple instances
(function($) {
// Define default scroll settings
var defaults = {
y: 0,
x: 0,
scrollHeight: 0,
scrollWidth: 0,
vScroll: true,
hScroll: false,
elastic: !navigator.userAgent.match(/android/i),
momentum: true,
elasticDamp: 0.6,
elasticTime: 50,
reboundTime: 400,
momentumDamp: 0.9,
momentumTime: 300,
iPadMomentumDamp: 0.95,
iPadMomentumTime: 1200,
touchTags: ['select', 'input', 'textarea']
};
// Define methods
var methods = {
init: function(options) {
return this.each(function() {
var self = this;
self.scrollOptions = {};
$.extend(self.scrollOptions, defaults);
$.extend(self.scrollOptions, options);
// Prevent double-init, just update instead
if (!!this._init) {
return this.update();
}
this._init = true;
// Define element variables
var $this = $(this),
scrollY = -self.scrollOptions.y,
scrollX = -self.scrollOptions.x,
touchY = 0,
touchX = 0,
movedY = 0,
movedX = 0,
pollY = 0,
pollX = 0,
height = 0,
width = 0,
maxHeight = 0,
maxWidth = 0,
scrollHeight = 0,
scrollWidth = 0,
scrolling = false,
bouncing = false,
moved = false,
timeoutID,
isiPad = !!navigator.platform.match(/ipad/i),
hasMatrix = 'WebKitCSSMatrix' in window,
has3d = hasMatrix && 'm11' in new WebKitCSSMatrix();
// Keep bottom of scroll area at the bottom on resize
var update = this.update = function() {
if (self.scrollOptions.hScroll) {
self.scrollOptions.vScroll = false;
// width setup
width = $this.parent().width();
scrollWidth = $this.width();
maxWidth = width - scrollWidth;
}
if (self.scrollOptions.vScroll) {
// height setup
height = $this.parent().height();
scrollHeight = $this.height();
maxHeight = height - scrollHeight;
}
clearTimeout(timeoutID);
clampScroll(false);
};
// Set up initial variables
update();
// Set up transform CSS
$this.css({'-webkit-transition-property': '-webkit-transform',
'-webkit-transition-timing-function': 'cubic-bezier(0,0,0.2,1)',
'-webkit-transition-duration': '0',
'-webkit-transform': cssTranslate(scrollX, scrollY)});
// Listen for screen size change event
window.addEventListener('onorientationchange' in window ? 'orientationchange' : 'resize', update, false);
// Listen for touch events
$this.bind('touchstart.touchScroll', touchStart);
$this.bind('touchmove.touchScroll', touchMove);
$this.bind('touchend.touchScroll touchcancel.touchScroll', touchEnd);
$this.bind('webkitTransitionEnd.touchScroll', transitionEnd);
// Set the position of the scroll area using transform CSS
var setPosition = this.setPosition = function(x, y) {
scrollX = x;
scrollY = y;
$this.css('-webkit-transform', cssTranslate(scrollX, scrollY));
};
// Transform using a 3D translate if available
function cssTranslate(x, y) {
return 'translate' + (has3d ? '3d(' : '(') + x + 'px,' + y + 'px' + (has3d ? ',0)' : ')');
}
// Set CSS transition time
function setTransitionTime(time) {
time = time || '0';
$this.css('-webkit-transition-duration', time + 'ms');
}
// Get the actual pixel position made by transform CSS
function getPosition() {
if (hasMatrix) {
var transform = window.getComputedStyle($this[0]).webkitTransform;
if (!!transform && transform !== 'none') {
var matrix = new WebKitCSSMatrix(transform);
return { x: matrix.e, y: matrix.f };
}
}
return { x: scrollX, y: scrollY };
}
// Expose getPosition API
this.getPosition = function() {
return getPosition();
};
// Bounce back to the bounds after momentum scrolling
function reboundScroll() {
if (scrollX > 0 && scrollY > 0) {
scrollTo(0, 0, self.scrollOptions.reboundTime);
} else if (scrollX > 0) {
scrollTo(0, scrollY, self.scrollOptions.reboundTime);
} else if (scrollY > 0) {
scrollTo(scrollX, 0, self.scrollOptions.reboundTime);
} else if (scrollX < maxWidth && scrollY < maxHeight) {
scrollTo(maxWidth, maxHeight, self.scrollOptions.reboundTime);
} else if (scrollX < maxWidth) {
scrollTo(maxWidth, scrollY, self.scrollOptions.reboundTime);
} else if (scrollY < maxHeight) {
scrollTo(scrollX, maxHeight, self.scrollOptions.reboundTime);
}
}
// Stop everything once the CSS transition in complete
function transitionEnd() {
if (bouncing) {
bouncing = false;
reboundScroll();
}
clearTimeout(timeoutID);
}
// Limit the scrolling to within the bounds
function clampScroll(poll) {
if (!hasMatrix || bouncing) {
return;
}
var oldX = pollX;
pollX = getPosition().x;
var oldY = pollY;
pollY = getPosition().y;
if (pollX > 0 || pollY > 0) {
if (self.scrollOptions.elastic) {
// Slow down outside top bound
bouncing = true;
scrollX = (pollX > 0) ? 0 : pollX ;
scrollY = (pollY > 0) ? 0 : pollY ;
momentumScroll(pollX - oldX, pollY - oldY, self.scrollOptions.elasticDamp, 1, width, height, self.scrollOptions.elasticTime);
} else {
// Stop outside top bound
var x = (pollX > 0) ? 0 : pollX ;
var y = (pollY > 0) ? 0 : pollY ;
setTransitionTime(0);
setPosition(x, y);
}
} else if (pollX < maxWidth || pollY < maxHeight) {
if (self.scrollOptions.elastic) {
// Slow down outside bottom bound
bouncing = true;
scrollX = (pollX > maxHeight) ? maxHeight : pollX ;
scrollY = (pollY > maxHeight) ? maxHeight : pollY ;
momentumScroll(pollX - oldX, pollY - oldY, self.scrollOptions.elasticDamp, 1, width, height, self.scrollOptions.elasticTime);
} else {
// Stop outside bottom bound
var x = (pollX > maxWidth) ? maxWidth : pollX ;
var y = (pollY > maxHeight) ? maxHeight : pollY ;
setTransitionTime(0);
setPosition(x, y);
}
} else if (poll) {
// Poll the computed position to check if element is out of bounds
timeoutID = setTimeout(clampScroll, 20, true);
}
}
// Animate to a position using CSS
function scrollTo(destX, destY, time) {
if (destX === scrollX && destY === scrollY) {
return;
}
moved = true;
setTransitionTime(time);
setPosition(destX, destY);
}
// Perform a momentum-based scroll using CSS
function momentumScroll(dxin, dyin, k, minDist, maxDistX, maxDistY, t) {
var adx = Math.abs(dxin),
ady = Math.abs(dyin),
dx = 0,
dy = 0;
// Calculate the total distance
while (adx > 0.1) {
adx *= k;
dx += adx;
}
while (ady > 0.1) {
ady *= k;
dy += ady;
}
// Limit to within min and max distances
if (dx > maxDistX) {
dx = maxDistX;
}
if (dy > maxDistY) {
dy = maxDistY;
}
if (dx > minDist) {
if (dxin < 0) {
dx = -dx;
}
dx += scrollX;
// If outside the bounds, don't go too far
if (width > 0) {
if (dx > width * 2) {
dx = width * 2;
} else if (dx < maxWidth - width * 2) {
dx = maxWidth - width * 2;
}
}
}
if (dy > minDist) {
if (dyin < 0) {
dy = -dy;
}
dy += scrollY;
// If outside the bounds, don't go too far
if (height > 0) {
if (dy > height * 2) {
dy = height * 2;
} else if (dy < maxHeight - height * 2) {
dy = maxHeight - height * 2;
}
}
}
if (Math.abs(dx) > minDist || Math.abs(dy) > minDist) {
// Perform scroll
scrollTo(Math.round(dx), Math.round(dy), t);
}
clampScroll(true);
}
// Get the touch points from this event
function getTouches(e) {
if (e.originalEvent) {
if (e.originalEvent.touches && e.originalEvent.touches.length) {
return e.originalEvent.touches;
} else if (e.originalEvent.changedTouches && e.originalEvent.changedTouches.length) {
return e.originalEvent.changedTouches;
}
}
return e.touches;
}
// Dispatches a fake mouse event from a touch event
function dispatchMouseEvent(name, touch, target) {
var e = document.createEvent('MouseEvent');
e.initMouseEvent(name, true, true, touch.view, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);
target.dispatchEvent(e);
}
// Find the root node of this target
function getRootNode(target) {
while (target.nodeType !== 1) {
target = target.parentNode;
}
return target;
}
// Perform a touch start event
function touchStart(e) {
// Allow certain HTML tags to receive touch events
if ($.inArray(e.target.tagName.toLowerCase(), self.scrollOptions.touchTags) !== -1) {
return;
}
// Stop the default touches
e.preventDefault();
e.stopPropagation();
var touch = getTouches(e)[0];
// Dispatch a fake mouse down event
dispatchMouseEvent('mousedown', touch, getRootNode(touch.target));
scrolling = true;
moved = false;
movedX = 0;
movedY = 0;
clearTimeout(timeoutID);
setTransitionTime(0);
// Check scroll position
if (self.scrollOptions.momentum) {
var x = getPosition().x;
var y = getPosition().y;
if (x !== scrollX || y !== scrollY) {
setPosition(x, y);
moved = true;
}
}
touchX = touch.pageX - scrollX;
touchY = touch.pageY - scrollY;
}
// Perform a touch move event
function touchMove(e) {
if (!scrolling) {
return;
}
var dx = getTouches(e)[0].pageX - touchX;
var dy = getTouches(e)[0].pageY - touchY;
// Elastic-drag or stop when moving outside of boundaries
if (dx > 0) {
if (self.scrollOptions.elastic) {
dx /= 2;
} else {
dx = 0;
}
} else if (dx < maxWidth) {
if (self.scrollOptions.elastic) {
dx = (dx + maxWidth) / 2;
} else {
dx = maxWidth;
}
}
if (dy > 0) {
if (self.scrollOptions.elastic) {
dy /= 2;
} else {
dy = 0;
}
} else if (dy < maxHeight) {
if (self.scrollOptions.elastic) {
dy = (dy + maxHeight) / 2;
} else {
dy = maxHeight;
}
}
if (self.scrollOptions.hScroll) {
movedX = dx - scrollX;
} else {
dx = 0;
}
if (self.scrollOptions.vScroll) {
movedY = dy - scrollY;
} else {
dy = 0;
}
moved = true;
setPosition(dx, dy);
}
// Perform a touch end event
function touchEnd(e) {
if (!scrolling) {
return;
}
scrolling = false;
if (moved) {
// Ease back to within boundaries
if ((scrollX > 0 || scrollX < maxWidth) || (scrollY > 0 || scrollY < maxHeight)) {
reboundScroll();
} else if (self.scrollOptions.momentum) {
// Free scroll with momentum
momentumScroll(movedX, movedY, isiPad ? self.scrollOptions.iPadMomentumDamp : self.scrollOptions.momentumDamp, 40, 2000, 2000, isiPad ? self.scrollOptions.iPadMomentumTime : self.scrollOptions.momentumTime);
}
} else {
var touch = getTouches(e)[0],
target = getRootNode(touch.target);
// Dispatch fake mouse up and click events if this touch event did not move
dispatchMouseEvent('mouseup', touch, target);
dispatchMouseEvent('click', touch, target);
}
}
});
},
update: function() {
return this.each(function() {
this.update();
});
},
getPosition: function() {
var a = [];
this.each(function() {
a.push(-this.getPosition());
});
return a;
},
setPosition: function(x, y) {
return this.each(function() {
this.setPosition(-x, -y);
});
}
};
// Public method for touchScroll
$.fn.touchScroll = function(method) {
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
} else if (typeof method === 'object' || !method) {
return methods.init.apply(this, arguments);
} else {
$.error('Method ' + method + ' does not exist on jQuery.touchScroll');
}
};
})(jQuery);
来源:https://stackoverflow.com/questions/6573715/touch-scroll-jquery-plugin-how-to-init-with-different-options-for-multiple-ins