Files
cmsPRO/libraries/framework/js/utility/scroller/jquery.scroller.js
2026-02-22 21:59:33 +01:00

569 lines
14 KiB
JavaScript

/*
* Scroller v3.1.2 - 2014-12-08
* A jQuery plugin for replacing default browser scrollbars. Part of the Formstone Library.
* http://formstone.it/scroller/
*
* Copyright 2014 Ben Plum; MIT Licensed
*/
;(function ($, window) {
"use strict";
var namespace = "scroller",
$body = null,
classes = {
base: "scroller",
content: "scroller-content",
bar: "scroller-bar",
track: "scroller-track",
handle: "scroller-handle",
isHorizontal: "scroller-horizontal",
isSetup: "scroller-setup",
isActive: "scroller-active"
},
events = {
start: "touchstart." + namespace + " mousedown." + namespace,
move: "touchmove." + namespace + " mousemove." + namespace,
end: "touchend." + namespace + " mouseup." + namespace
};
/**
* @options
* @param customClass [string] <''> "Class applied to instance"
* @param duration [int] <0> "Scroll animation length"
* @param handleSize [int] <0> "Handle size; 0 to auto size"
* @param horizontal [boolean] <false> "Scroll horizontally"
* @param trackMargin [int] <0> "Margin between track and handle edge”
*/
var options = {
customClass: "",
duration: 0,
handleSize: 0,
horizontal: false,
trackMargin: 0
};
var pub = {
/**
* @method
* @name defaults
* @description Sets default plugin options
* @param opts [object] <{}> "Options object"
* @example $.scroller("defaults", opts);
*/
defaults: function(opts) {
options = $.extend(options, opts || {});
return (typeof this === 'object') ? $(this) : true;
},
/**
* @method
* @name destroy
* @description Removes instance of plugin
* @example $(".target").scroller("destroy");
*/
destroy: function() {
return $(this).each(function(i, el) {
var data = $(el).data(namespace);
if (data) {
data.$scroller.removeClass( [data.customClass, classes.base, classes.isActive].join(" ") );
data.$bar.remove();
data.$content.contents().unwrap();
data.$content.off( classify(namespace) );
data.$scroller.off( classify(namespace) )
.removeData(namespace);
}
});
},
/**
* @method
* @name scroll
* @description Scrolls instance of plugin to element or position
* @param pos [string || int] <null> "Target element selector or static position"
* @param duration [int] <null> "Optional scroll duration"
* @example $.scroller("scroll", pos, duration);
*/
scroll: function(pos, dur) {
return $(this).each(function(i) {
var data = $(this).data(namespace),
duration = dur || options.duration;
if (typeof pos !== "number") {
var $el = $(pos);
if ($el.length > 0) {
var offset = $el.position();
if (data.horizontal) {
pos = offset.left + data.$content.scrollLeft();
} else {
pos = offset.top + data.$content.scrollTop();
}
} else {
pos = data.$content.scrollTop();
}
}
var styles = data.horizontal ? { scrollLeft: pos } : { scrollTop: pos };
data.$content.stop().animate(styles, duration);
});
},
/**
* @method
* @name reset
* @description Resets layout on instance of plugin
* @example $.scroller("reset");
*/
reset: function() {
return $(this).each(function(i) {
var data = $(this).data(namespace);
if (data) {
data.$scroller.addClass(classes.isSetup);
var barStyles = {},
trackStyles = {},
handleStyles = {},
handlePosition = 0,
isActive = true;
if (data.horizontal) {
// Horizontal
data.barHeight = data.$content[0].offsetHeight - data.$content[0].clientHeight;
data.frameWidth = data.$content.outerWidth();
data.trackWidth = data.frameWidth - (data.trackMargin * 2);
data.scrollWidth = data.$content[0].scrollWidth;
data.ratio = data.trackWidth / data.scrollWidth;
data.trackRatio = data.trackWidth / data.scrollWidth;
data.handleWidth = (data.handleSize > 0) ? data.handleSize : data.trackWidth * data.trackRatio;
data.scrollRatio = (data.scrollWidth - data.frameWidth) / (data.trackWidth - data.handleWidth);
data.handleBounds = {
left: 0,
right: data.trackWidth - data.handleWidth
};
data.$content.css({
paddingBottom: data.barHeight + data.paddingBottom
});
var scrollLeft = data.$content.scrollLeft();
handlePosition = scrollLeft * data.ratio;
isActive = (data.scrollWidth <= data.frameWidth);
barStyles = {
width: data.frameWidth
};
trackStyles = {
width: data.trackWidth,
marginLeft: data.trackMargin,
marginRight: data.trackMargin
};
handleStyles = {
width: data.handleWidth
};
} else {
// Vertical
data.barWidth = data.$content[0].offsetWidth - data.$content[0].clientWidth;
data.frameHeight = data.$content.outerHeight();
data.trackHeight = data.frameHeight - (data.trackMargin * 2);
data.scrollHeight = data.$content[0].scrollHeight;
data.ratio = data.trackHeight / data.scrollHeight;
data.trackRatio = data.trackHeight / data.scrollHeight;
data.handleHeight = (data.handleSize > 0) ? data.handleSize : data.trackHeight * data.trackRatio;
data.scrollRatio = (data.scrollHeight - data.frameHeight) / (data.trackHeight - data.handleHeight);
data.handleBounds = {
top: 0,
bottom: data.trackHeight - data.handleHeight
};
var scrollTop = data.$content.scrollTop();
handlePosition = scrollTop * data.ratio;
isActive = (data.scrollHeight <= data.frameHeight);
barStyles = {
height: data.frameHeight
};
trackStyles = {
height: data.trackHeight,
marginBottom: data.trackMargin,
marginTop: data.trackMargin
};
handleStyles = {
height: data.handleHeight
};
}
// Updates
if (isActive) {
data.$scroller.removeClass(classes.isActive);
} else {
data.$scroller.addClass(classes.isActive);
}
data.$bar.css(barStyles);
data.$track.css(trackStyles);
data.$handle.css(handleStyles);
position(data, handlePosition);
data.$scroller.removeClass(classes.isSetup);
}
});
}
};
/**
* @method private
* @name init
* @description Initializes plugin
* @param opts [object] "Initialization options"
*/
function init(opts) {
// Local options
opts = $.extend({}, options, opts || {});
// Check for Body
if ($body === null) {
$body = $("body");
}
// Apply to each element
var $items = $(this);
for (var i = 0, count = $items.length; i < count; i++) {
build($items.eq(i), opts);
}
return $items;
}
/**
* @method private
* @name build
* @description Builds each instance
* @param $scroller [jQuery object] "Target jQuery object"
* @param opts [object] <{}> "Options object"
*/
function build($scroller, opts) {
if (!$scroller.hasClass(classes.base)) {
// EXTEND OPTIONS
opts = $.extend({}, opts, $scroller.data(namespace + "-options"));
var html = '';
html += '<div class="' + classes.bar + '">';
html += '<div class="' + classes.track + '">';
html += '<div class="' + classes.handle + '">';
html += '</div></div></div>';
opts.paddingRight = parseInt($scroller.css("padding-right"), 10);
opts.paddingBottom = parseInt($scroller.css("padding-bottom"), 10);
$scroller.addClass( [classes.base, opts.customClass].join(" ") )
.wrapInner('<div class="' + classes.content + '" />')
.prepend(html);
if (opts.horizontal) {
$scroller.addClass(classes.isHorizontal);
}
var data = $.extend({
$scroller: $scroller,
$content: $scroller.find( classify(classes.content) ),
$bar: $scroller.find( classify(classes.bar) ),
$track: $scroller.find( classify(classes.track) ),
$handle: $scroller.find( classify(classes.handle) )
}, opts);
data.trackMargin = parseInt(data.trackMargin, 10);
data.$content.on("scroll." + namespace, data, onScroll);
data.$scroller.on(events.start, classify(classes.track), data, onTrackDown)
.on(events.start, classify(classes.handle), data, onHandleDown)
.data(namespace, data);
pub.reset.apply($scroller);
$(window).one("load", function() {
pub.reset.apply($scroller);
});
}
}
/**
* @method private
* @name onScroll
* @description Handles scroll event
* @param e [object] "Event data"
*/
function onScroll(e) {
e.preventDefault();
e.stopPropagation();
var data = e.data,
handleStyles = {};
if (data.horizontal) {
// Horizontal
var scrollLeft = data.$content.scrollLeft();
if (scrollLeft < 0) {
scrollLeft = 0;
}
var handleLeft = scrollLeft / data.scrollRatio;
if (handleLeft > data.handleBounds.right) {
handleLeft = data.handleBounds.right;
}
handleStyles = {
left: handleLeft
};
} else {
// Vertical
var scrollTop = data.$content.scrollTop();
if (scrollTop < 0) {
scrollTop = 0;
}
var handleTop = scrollTop / data.scrollRatio;
if (handleTop > data.handleBounds.bottom) {
handleTop = data.handleBounds.bottom;
}
handleStyles = {
top: handleTop
};
}
data.$handle.css(handleStyles);
}
/**
* @method private
* @name onTrackDown
* @description Handles mousedown event on track
* @param e [object] "Event data"
*/
function onTrackDown(e) {
e.preventDefault();
e.stopPropagation();
var data = e.data,
oe = e.originalEvent,
offset = data.$track.offset(),
touch = (typeof oe.targetTouches !== "undefined") ? oe.targetTouches[0] : null,
pageX = (touch) ? touch.pageX : e.clientX,
pageY = (touch) ? touch.pageY : e.clientY;
if (data.horizontal) {
// Horizontal
data.mouseStart = pageX;
data.handleLeft = pageX - offset.left - (data.handleWidth / 2);
position(data, data.handleLeft);
} else {
// Vertical
data.mouseStart = pageY;
data.handleTop = pageY - offset.top - (data.handleHeight / 2);
position(data, data.handleTop);
}
onStart(data);
}
/**
* @method private
* @name onHandleDown
* @description Handles mousedown event on handle
* @param e [object] "Event data"
*/
function onHandleDown(e) {
e.preventDefault();
e.stopPropagation();
var data = e.data,
oe = e.originalEvent,
touch = (typeof oe.targetTouches !== "undefined") ? oe.targetTouches[0] : null,
pageX = (touch) ? touch.pageX : e.clientX,
pageY = (touch) ? touch.pageY : e.clientY;
if (data.horizontal) {
// Horizontal
data.mouseStart = pageX;
data.handleLeft = parseInt(data.$handle.css("left"), 10);
} else {
// Vertical
data.mouseStart = pageY;
data.handleTop = parseInt(data.$handle.css("top"), 10);
}
onStart(data);
}
/**
* @method private
* @name onStart
* @description Handles touch.mouse start
* @param data [object] "Instance data"
*/
function onStart(data) {
data.$content.off( classify(namespace) );
$body.on(events.move, data, onMouseMove)
.on(events.end, data, onMouseUp);
}
/**
* @method private
* @name onMouseMove
* @description Handles mousemove event
* @param e [object] "Event data"
*/
function onMouseMove(e) {
e.preventDefault();
e.stopPropagation();
var data = e.data,
oe = e.originalEvent,
pos = 0,
delta = 0,
touch = (typeof oe.targetTouches !== "undefined") ? oe.targetTouches[0] : null,
pageX = (touch) ? touch.pageX : e.clientX,
pageY = (touch) ? touch.pageY : e.clientY;
if (data.horizontal) {
// Horizontal
delta = data.mouseStart - pageX;
pos = data.handleLeft - delta;
} else {
// Vertical
delta = data.mouseStart - pageY;
pos = data.handleTop - delta;
}
position(data, pos);
}
/**
* @method private
* @name onMouseUp
* @description Handles mouseup event
* @param e [object] "Event data"
*/
function onMouseUp(e) {
e.preventDefault();
e.stopPropagation();
var data = e.data;
data.$content.on("scroll.scroller", data, onScroll);
$body.off(".scroller");
}
/**
* @method private
* @name onTouchEnd
* @description Handles mouseup event
* @param e [object] "Event data"
*/
function onTouchEnd(e) {
e.preventDefault();
e.stopPropagation();
var data = e.data;
data.$content.on("scroll.scroller", data, onScroll);
$body.off(".scroller");
}
/**
* @method private
* @name position
* @description Position handle based on scroll
* @param data [object] "Instance data"
* @param pos [int] "Scroll position"
*/
function position(data, pos) {
var handleStyles = {};
if (data.horizontal) {
// Horizontal
if (pos < data.handleBounds.left) {
pos = data.handleBounds.left;
}
if (pos > data.handleBounds.right) {
pos = data.handleBounds.right;
}
var scrollLeft = Math.round(pos * data.scrollRatio);
handleStyles = {
left: pos
};
data.$content.scrollLeft( scrollLeft );
} else {
// Vertical
if (pos < data.handleBounds.top) {
pos = data.handleBounds.top;
}
if (pos > data.handleBounds.bottom) {
pos = data.handleBounds.bottom;
}
var scrollTop = Math.round(pos * data.scrollRatio);
handleStyles = {
top: pos
};
data.$content.scrollTop( scrollTop );
}
data.$handle.css(handleStyles);
}
/**
* @method private
* @name classify
* @description Create class selector from text
* @param text [string] "Text to convert"
* @return [string] "New class name"
*/
function classify(text) {
return "." + text;
}
$.fn[namespace] = function(method) {
if (pub[method]) {
return pub[method].apply(this, Array.prototype.slice.call(arguments, 1));
} else if (typeof method === 'object' || !method) {
return init.apply(this, arguments);
}
return this;
};
$[namespace] = function(method) {
if (method === "defaults") {
pub.defaults.apply(this, Array.prototype.slice.call(arguments, 1));
}
};
})(jQuery);