UNPKG

causeway-standard-theme

Version:

762 lines (651 loc) 30.2 kB
/** * jQuery CSS Customizable Scrollbar * * Copyright 2014, Yuriy Khabarov * Dual licensed under the MIT or GPL Version 2 licenses. * * If you found bug, please contact me via email <13real008@gmail.com> * * @author Yuriy Khabarov aka Gromo * @version 0.2.5 * @url https://github.com/gromo/jquery.scrollbar/ * */ ; (function($, doc, win){ 'use strict'; // init flags & variables var debug = false; var lmb = 1, px = "px"; var browser = { "data": {}, "macosx": win.navigator.platform.toLowerCase().indexOf('mac') !== -1, "mobile": /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(win.navigator.userAgent), "overlay": null, "scroll": null, "scrolls": [], "webkit": /WebKit/.test(win.navigator.userAgent), "log": debug ? function(data, toString){ var output = data; if(toString && typeof data != "string"){ output = []; $.each(data, function(i, v){ output.push('"' + i + '": ' + v); }); output = output.join(", "); } if(win.console && win.console.log){ win.console.log(output); } else { alert(output); } } : function(){ } }; var defaults = { "autoScrollSize": true, // automatically calculate scrollsize "autoUpdate": true, // update scrollbar if content/container size changed "debug": false, // debug mode "disableBodyScroll": false, // disable body scroll if mouse over container "duration": 200, // scroll animate duration in ms "ignoreMobile": true, // ignore mobile devices "ignoreOverlay": true, // ignore browsers with overlay scrollbars (mobile, MacOS) "scrollStep": 30, // scroll step for scrollbar arrows "showArrows": false, // add class to show arrows "stepScrolling": true, // when scrolling to scrollbar mousedown position "type":"simple", // [advanced|simple] scrollbar html type "scrollx": null, // horizontal scroll element "scrolly": null, // vertical scroll element "onDestroy": null, // callback function on destroy, "onInit": null, // callback function on first initialization "onScroll": null, // callback function on content scrolling "onUpdate": null // callback function on init/resize (before scrollbar size calculation) }; var customScrollbar = function(container, options){ if(!browser.scroll){ browser.log("Init jQuery Scrollbar v0.2.5"); browser.overlay = isScrollOverlaysContent(); browser.scroll = getBrowserScrollSize(); updateScrollbars(); $(win).resize(function(){ var forceUpdate = false; if(browser.scroll && (browser.scroll.height || browser.scroll.width)){ var scroll = getBrowserScrollSize(); if(scroll.height != browser.scroll.height || scroll.width != browser.scroll.width){ browser.scroll = scroll; forceUpdate = true; // handle page zoom } } updateScrollbars(forceUpdate); }); } this.container = container; this.options = $.extend({}, defaults, win.jQueryScrollbarOptions || {}); this.scrollTo = null; this.scrollx = {}; this.scrolly = {}; this.init(options); }; customScrollbar.prototype = { destroy: function(){ if(!this.wrapper){ return; } // init variables var scrollLeft = this.container.scrollLeft(); var scrollTop = this.container.scrollTop(); this.container.insertBefore(this.wrapper).css({ "height":"", "margin":"" }) .removeClass("scroll-content") .removeClass("scroll-scrollx_visible") .removeClass("scroll-scrolly_visible") .off(".scrollbar") .scrollLeft(scrollLeft) .scrollTop(scrollTop); this.scrollx.scrollbar.removeClass("scroll-scrollx_visible").find("div").andSelf().off(".scrollbar"); this.scrolly.scrollbar.removeClass("scroll-scrolly_visible").find("div").andSelf().off(".scrollbar"); this.wrapper.remove(); $(doc).add("body").off(".scrollbar"); if($.isFunction(this.options.onDestroy)) this.options.onDestroy.apply(this, [this.container]); }, getScrollbar: function(d){ var scrollbar = this.options["scroll" + d]; var html = { "advanced": '<div class="scroll-element_corner"></div>' + '<div class="scroll-arrow scroll-arrow_less"></div>' + '<div class="scroll-arrow scroll-arrow_more"></div>' + '<div class="scroll-element_outer">' + ' <div class="scroll-element_size"></div>' + // required! used for scrollbar size calculation ! ' <div class="scroll-element_inner-wrapper">' + ' <div class="scroll-element_inner scroll-element_track">' + // used for handling scrollbar click ' <div class="scroll-element_inner-bottom"></div>' + ' </div>' + ' </div>' + ' <div class="scroll-bar">' + ' <div class="scroll-bar_body">' + ' <div class="scroll-bar_body-inner"></div>' + ' </div>' + ' <div class="scroll-bar_bottom"></div>' + ' <div class="scroll-bar_center"></div>' + ' </div>' + '</div>', "simple": '<div class="scroll-element_outer">' + ' <div class="scroll-element_size"></div>' + // required! used for scrollbar size calculation ! ' <div class="scroll-element_track"></div>' + // used for handling scrollbar click ' <div class="scroll-bar"></div>' + '</div>' }; var type = html[this.options.type] ? this.options.type : "advanced"; if(scrollbar){ if(typeof(scrollbar) == "string"){ scrollbar = $(scrollbar).appendTo(this.wrapper); } else { scrollbar = $(scrollbar); } } else { scrollbar = $("<div>").addClass("scroll-element").html(html[type]).appendTo(this.wrapper); } if(this.options.showArrows){ scrollbar.addClass("scroll-element_arrows_visible"); } return scrollbar.addClass("scroll-" + d); }, init: function(options){ // init variables var S = this; var c = this.container; var cw = this.containerWrapper || c; var o = $.extend(this.options, options || {}); var s = { "x": this.scrollx, "y": this.scrolly }; var w = this.wrapper; var initScroll = { "scrollLeft": c.scrollLeft(), "scrollTop": c.scrollTop() }; // do not init if in ignorable browser if ((browser.mobile && o.ignoreMobile) || (browser.overlay && o.ignoreOverlay) || (browser.macosx && !browser.webkit) // still required to ignore nonWebKit browsers on Mac ) { return false; } // init scroll container if(!w){ this.wrapper = w = $('<div>').addClass('scroll-wrapper').addClass(c.attr('class')) .css('position', c.css('position') == 'absolute' ? 'absolute' : 'relative') .insertBefore(c).append(c); if(c.is('textarea')){ this.containerWrapper = cw = $('<div>').insertBefore(c).append(c); w.addClass('scroll-textarea'); } cw.addClass("scroll-content").css({ "height":"", "margin-bottom": browser.scroll.height * -1 + px, "margin-right": browser.scroll.width * -1 + px }); c.on("scroll.scrollbar", function(event){ if($.isFunction(o.onScroll)){ o.onScroll.call(S, { "maxScroll": s.y.maxScrollOffset, "scroll": c.scrollTop(), "size": s.y.size, "visible": s.y.visible }, { "maxScroll": s.x.maxScrollOffset, "scroll": c.scrollLeft(), "size": s.x.size, "visible": s.x.visible }); } s.x.isVisible && s.x.scroller.css("left", c.scrollLeft() * s.x.kx + px); s.y.isVisible && s.y.scroller.css("top", c.scrollTop() * s.y.kx + px); }); /* prevent native scrollbars to be visible on #anchor click */ w.on("scroll", function(){ w.scrollTop(0).scrollLeft(0); }); if(o.disableBodyScroll){ var handleMouseScroll = function(event){ isVerticalScroll(event) ? s.y.isVisible && s.y.mousewheel(event) : s.x.isVisible && s.x.mousewheel(event); }; w.on({ "MozMousePixelScroll.scrollbar": handleMouseScroll, "mousewheel.scrollbar": handleMouseScroll }); if(browser.mobile){ w.on("touchstart.scrollbar", function(event){ var touch = event.originalEvent.touches && event.originalEvent.touches[0] || event; var originalTouch = { "pageX": touch.pageX, "pageY": touch.pageY }; var originalScroll = { "left": c.scrollLeft(), "top": c.scrollTop() }; $(doc).on({ "touchmove.scrollbar": function(event){ var touch = event.originalEvent.targetTouches && event.originalEvent.targetTouches[0] || event; c.scrollLeft(originalScroll.left + originalTouch.pageX - touch.pageX); c.scrollTop(originalScroll.top + originalTouch.pageY - touch.pageY); event.preventDefault(); }, "touchend.scrollbar": function(){ $(doc).off(".scrollbar"); } }); }); } } if($.isFunction(o.onInit)) o.onInit.apply(this, [c]); } else { cw.css({ "height":"", "margin-bottom": browser.scroll.height * -1 + px, "margin-right": browser.scroll.width * -1 + px }); } // init scrollbars & recalculate sizes $.each(s, function(d, scrollx){ var scrollCallback = null; var scrollForward = 1; var scrollOffset = (d == "x") ? "scrollLeft" : "scrollTop"; var scrollStep = o.scrollStep; var scrollTo = function(){ var currentOffset = c[scrollOffset](); c[scrollOffset](currentOffset + scrollStep); if(scrollForward == 1 && (currentOffset + scrollStep) >= scrollToValue) currentOffset = c[scrollOffset](); if(scrollForward == -1 && (currentOffset + scrollStep) <= scrollToValue) currentOffset = c[scrollOffset](); if(c[scrollOffset]() == currentOffset && scrollCallback){ scrollCallback(); } } var scrollToValue = 0; if(!scrollx.scrollbar){ scrollx.scrollbar = S.getScrollbar(d); scrollx.scroller = scrollx.scrollbar.find(".scroll-bar"); scrollx.mousewheel = function(event){ if(!scrollx.isVisible || (d == 'x' && isVerticalScroll(event))){ return true; } if(d == 'y' && !isVerticalScroll(event)){ s.x.mousewheel(event); return true; } var delta = event.originalEvent.wheelDelta * -1 || event.originalEvent.detail; var maxScrollValue = scrollx.size - scrollx.visible - scrollx.offset; if(!((scrollToValue <= 0 && delta < 0) || (scrollToValue >= maxScrollValue && delta > 0))){ scrollToValue = scrollToValue + delta; if(scrollToValue < 0) scrollToValue = 0; if(scrollToValue > maxScrollValue) scrollToValue = maxScrollValue; S.scrollTo = S.scrollTo || {}; S.scrollTo[scrollOffset] = scrollToValue; setTimeout(function(){ if(S.scrollTo){ c.stop().animate(S.scrollTo, 240, 'linear', function(){ scrollToValue = c[scrollOffset](); }); S.scrollTo = null; } }, 1); } event.preventDefault(); return false; }; scrollx.scrollbar.on({ "MozMousePixelScroll.scrollbar": scrollx.mousewheel, "mousewheel.scrollbar": scrollx.mousewheel, "mouseenter.scrollbar": function(){ scrollToValue = c[scrollOffset](); } }); // handle arrows & scroll inner mousedown event scrollx.scrollbar.find(".scroll-arrow, .scroll-element_track") .on("mousedown.scrollbar", function(event){ if(event.which != lmb) return true; scrollForward = 1; var data = { "eventOffset": event[(d == "x") ? "pageX" : "pageY"], "maxScrollValue": scrollx.size - scrollx.visible - scrollx.offset, "scrollbarOffset": scrollx.scroller.offset()[(d == "x") ? "left" : "top"], "scrollbarSize": scrollx.scroller[(d == "x") ? "outerWidth" : "outerHeight"]() }; var timeout = 0, timer = 0; if($(this).hasClass('scroll-arrow')){ scrollForward = $(this).hasClass("scroll-arrow_more") ? 1 : -1; scrollStep = o.scrollStep * scrollForward; scrollToValue = scrollForward > 0 ? data.maxScrollValue : 0; } else { scrollForward = (data.eventOffset > (data.scrollbarOffset + data.scrollbarSize) ? 1 : (data.eventOffset < data.scrollbarOffset ? -1 : 0)); scrollStep = Math.round(scrollx.visible * 0.75) * scrollForward; scrollToValue = (data.eventOffset - data.scrollbarOffset - (o.stepScrolling ? (scrollForward == 1 ? data.scrollbarSize : 0) : Math.round(data.scrollbarSize / 2))); scrollToValue = c[scrollOffset]() + (scrollToValue / scrollx.kx); } S.scrollTo = S.scrollTo || {}; S.scrollTo[scrollOffset] = o.stepScrolling ? c[scrollOffset]() + scrollStep : scrollToValue; if(o.stepScrolling){ scrollCallback = function(){ scrollToValue = c[scrollOffset](); clearInterval(timer); clearTimeout(timeout); timeout = 0; timer = 0; }; timeout = setTimeout(function(){ timer = setInterval(scrollTo, 40); }, o.duration + 100); } setTimeout(function(){ if(S.scrollTo){ c.animate(S.scrollTo, o.duration); S.scrollTo = null; } }, 1); return handleMouseDown(scrollCallback, event); }); // handle scrollbar drag'n'drop scrollx.scroller.on("mousedown.scrollbar", function(event){ if(event.which != lmb) return true; var eventPosition = event[(d == "x")? "pageX" : "pageY"]; var initOffset = c[scrollOffset](); scrollx.scrollbar.addClass("scroll-draggable"); $(doc).on("mousemove.scrollbar", function(event){ var diff = parseInt((event[(d == "x")? "pageX" : "pageY"] - eventPosition) / scrollx.kx, 10); c[scrollOffset](initOffset + diff); }); return handleMouseDown(function(){ scrollx.scrollbar.removeClass("scroll-draggable"); scrollToValue = c[scrollOffset](); }, event); }); } }); // remove classes & reset applied styles $.each(s, function(d, scrollx){ var scrollClass = "scroll-scroll" + d + "_visible"; var scrolly = (d == "x") ? s.y : s.x; scrollx.scrollbar.removeClass(scrollClass); scrolly.scrollbar.removeClass(scrollClass); cw.removeClass(scrollClass); }); // calculate init sizes $.each(s, function(d, scrollx){ $.extend(scrollx, (d == "x") ? { "offset": parseInt(c.css("left"), 10) || 0, "size": c.prop("scrollWidth"), "visible": w.width() } : { "offset": parseInt(c.css("top"), 10) || 0, "size": c.prop("scrollHeight"), "visible": w.height() }); }); var updateScroll = function(d, scrollx){ var scrollClass = "scroll-scroll" + d + "_visible"; var scrolly = (d == "x") ? s.y : s.x; var offset = parseInt(c.css((d == "x") ? "left" : "top"), 10) || 0; var AreaSize = scrollx.size; var AreaVisible = scrollx.visible + offset; scrollx.isVisible = (AreaSize - AreaVisible) > 1; // bug in IE9/11 with 1px diff if(scrollx.isVisible){ scrollx.scrollbar.addClass(scrollClass); scrolly.scrollbar.addClass(scrollClass); cw.addClass(scrollClass); } else { scrollx.scrollbar.removeClass(scrollClass); scrolly.scrollbar.removeClass(scrollClass); cw.removeClass(scrollClass); } if(d == "y" && (scrollx.isVisible || scrollx.size < scrollx.visible)){ cw.css("height", (AreaVisible + browser.scroll.height) + px); } if(s.x.size != c.prop("scrollWidth") || s.y.size != c.prop("scrollHeight") || s.x.visible != w.width() || s.y.visible != w.height() || s.x.offset != (parseInt(c.css("left"), 10) || 0) || s.y.offset != (parseInt(c.css("top"), 10) || 0) ){ $.each(s, function(d, scrollx){ $.extend(scrollx, (d == "x") ? { "offset": parseInt(c.css("left"), 10) || 0, "size": c.prop("scrollWidth"), "visible": w.width() } : { "offset": parseInt(c.css("top"), 10) || 0, "size": c.prop("scrollHeight"), "visible": w.height() }); }); updateScroll(d == "x" ? "y" : "x", scrolly); } }; $.each(s, updateScroll); if($.isFunction(o.onUpdate)) o.onUpdate.apply(this, [c]); // calculate scroll size $.each(s, function(d, scrollx){ var cssOffset = (d == "x") ? "left" : "top"; var cssFullSize = (d == "x") ? "outerWidth" : "outerHeight"; var cssSize = (d == "x") ? "width" : "height"; var offset = parseInt(c.css(cssOffset), 10) || 0; var AreaSize = scrollx.size; var AreaVisible = scrollx.visible + offset; var scrollSize = scrollx.scrollbar.find(".scroll-element_size"); scrollSize = scrollSize[cssFullSize]() + (parseInt(scrollSize.css(cssOffset), 10) || 0); if(o.autoScrollSize){ scrollx.scrollbarSize = parseInt(scrollSize * AreaVisible / AreaSize, 10); scrollx.scroller.css(cssSize, scrollx.scrollbarSize + px); } scrollx.scrollbarSize = scrollx.scroller[cssFullSize](); scrollx.kx = ((scrollSize - scrollx.scrollbarSize) / (AreaSize - AreaVisible)) || 1; scrollx.maxScrollOffset = AreaSize - AreaVisible; }); c.scrollLeft(initScroll.scrollLeft).scrollTop(initScroll.scrollTop).trigger("scroll"); } }; /* * Extend jQuery as plugin * * @param {object|string} options or command to execute * @param {object|array} args additional arguments as array [] */ $.fn.scrollbar = function(options, args){ var toReturn = this; if(options === "get") toReturn = null; this.each(function() { var container = $(this); if(container.hasClass("scroll-wrapper") || container.get(0).nodeName == "body"){ return true; } var instance = container.data("scrollbar"); if(instance){ if(options === "get"){ toReturn = instance; return false; } var func = (typeof options == "string" && instance[options]) ? options : "init"; instance[func].apply(instance, $.isArray(args) ? args : []); if(options === "destroy"){ container.removeData("scrollbar"); while($.inArray(instance, browser.scrolls) >= 0) browser.scrolls.splice($.inArray(instance, browser.scrolls), 1); } } else { if(typeof options != "string"){ instance = new customScrollbar(container, options); container.data("scrollbar", instance); browser.scrolls.push(instance); } } return true; }); return toReturn; }; /** * Connect default options to global object */ $.fn.scrollbar.options = defaults; /** * Extend AngularJS as UI directive * * */ if(win.angular){ (function(angular){ var app = angular.module('jQueryScrollbar', []); app.directive('jqueryScrollbar', function(){ return { "link": function(scope, element){ element.scrollbar(scope.options).on('$destroy', function(){ element.scrollbar('destroy'); }); }, "restring": "AC", "scope": { "options": "=jqueryScrollbar" } }; }); })(win.angular); } /** * Check if scroll content/container size is changed */ var timer = 0, timerCounter = 0; var updateScrollbars = function(force){ var i, c, o, s, w, x, y; for( i = 0; i < browser.scrolls.length; i++){ s = browser.scrolls[i]; c = s.container; o = s.options; w = s.wrapper; x = s.scrollx; y = s.scrolly; if(force || (o.autoUpdate && w && w.is(":visible") && (c.prop("scrollWidth") != x.size || c.prop("scrollHeight") != y.size || w.width() != x.visible || w.height() != y.visible ))){ s.init(); if(debug){ browser.log({ "scrollHeight": c.prop("scrollHeight") + ":" + s.scrolly.size, "scrollWidth": c.prop("scrollWidth") + ":" + s.scrollx.size, "visibleHeight": w.height() + ":" + s.scrolly.visible, "visibleWidth": w.width() + ":" + s.scrollx.visible }, true); timerCounter++; } } } if(debug && timerCounter > 10){ browser.log("Scroll updates exceed 10"); updateScrollbars = function(){}; } else { clearTimeout(timer); timer = setTimeout(updateScrollbars, 300); } }; /* ADDITIONAL FUNCTIONS */ /** * Get native browser scrollbar size (height/width) * * @param {Boolean} actual size or CSS size, default - CSS size * @returns {Object} with height, width */ function getBrowserScrollSize(actualSize){ if(browser.webkit && !actualSize){ return { "height": 0, "width": 0 }; } if(!browser.data.outer){ var css = { "border": "none", "box-sizing": "content-box", "height": "200px", "margin": "0", "padding": "0", "width": "200px" }; browser.data.inner = $("<div>").css($.extend({}, css)); browser.data.outer = $("<div>").css($.extend({ "left": "-1000px", "overflow": "scroll", "position": "absolute", "top": "-1000px" }, css)).append(browser.data.inner).appendTo("body"); } browser.data.outer.scrollLeft(1000).scrollTop(1000); return { "height": Math.ceil((browser.data.outer.offset().top - browser.data.inner.offset().top) || 0), "width": Math.ceil((browser.data.outer.offset().left - browser.data.inner.offset().left) || 0) }; } function handleMouseDown(callback, event){ $(doc).on({ "blur.scrollbar": function(){ $(doc).add('body').off('.scrollbar'); callback && callback(); }, "dragstart.scrollbar": function(event){ event.preventDefault(); return false; }, "mouseup.scrollbar": function(){ $(doc).add('body').off('.scrollbar'); callback && callback(); } }); $("body").on({ "selectstart.scrollbar": function(event){ event.preventDefault(); return false; } }); event && event.preventDefault(); return false; } /** * Check if native browser scrollbars overlay content * * @returns {Boolean} */ function isScrollOverlaysContent(){ var scrollSize = getBrowserScrollSize(true); return !(scrollSize.height || scrollSize.width); } function isVerticalScroll(event){ var e = event.originalEvent; if (e.axis && e.axis === e.HORIZONTAL_AXIS) return false; if (e.wheelDeltaX) return false; return true; } })(jQuery, document, window);