diff --git a/src/perfect-scrollbar.js b/src/perfect-scrollbar.js index b233821..1ca7071 100644 --- a/src/perfect-scrollbar.js +++ b/src/perfect-scrollbar.js @@ -1,320 +1,324 @@ /* Copyright (c) 2012 HyeonJe Jun (http://github.com/noraesae) * Licensed under the MIT License */ -((function($) { +((function ($) { - // The default settings for the plugin - var defaultSettings = { - wheelSpeed: 10, - wheelPropagation: false + // The default settings for the plugin + var defaultSettings = { + wheelSpeed: 10, + wheelPropagation: false + }; + + $.fn.perfectScrollbar = function (suppliedSettings, option) { + + // Use the default settings + var settings = $.extend(true, {}, defaultSettings); + if (typeof suppliedSettings === "object") { + // But over-ride any supplied + $.extend(true, settings, suppliedSettings); + } else { + // If no settings were supplied, then the first param must be the option + option = suppliedSettings; + } + + if (option === 'update') { + if ($(this).data('perfect-scrollbar-update')) { + $(this).data('perfect-scrollbar-update')(); + } + return $(this); + } + else if (option === 'destroy') { + if ($(this).data('perfect-scrollbar-destroy')) { + $(this).data('perfect-scrollbar-destroy')(); + } + return $(this); + } + + if ($(this).data('perfect-scrollbar')) { + // if there's already perfect-scrollbar + return $(this).data('perfect-scrollbar'); + } + + var $this = $(this).addClass('ps-container'), + $content = $(this).children(), + $scrollbarX = $("
").appendTo($this), + $scrollbarY = $("
").appendTo($this), + containerWidth, + containerHeight, + contentWidth, + contentHeight, + scrollbarXWidth, + scrollbarXLeft, + scrollbarXBottom = parseInt($scrollbarX.css('bottom'), 10), + scrollbarYHeight, + scrollbarYTop, + scrollbarYRight = parseInt($scrollbarY.css('right'), 10); + + var updateContentScrollTop = function () { + var scrollTop = parseInt(scrollbarYTop * contentHeight / containerHeight, 10); + $this.scrollTop(scrollTop); + $scrollbarX.css({bottom: scrollbarXBottom - scrollTop}); }; - $.fn.perfectScrollbar = function(suppliedSettings, option) { - - // Use the default settings - var settings = $.extend( true, {}, defaultSettings ); - if (typeof suppliedSettings === "object") { - // But over-ride any supplied - $.extend( true, settings, suppliedSettings ); - } else { - // If no settings were supplied, then the first param must be the option - option = suppliedSettings; - } - - if(option === 'update') { - if($(this).data('perfect_scrollbar_update')) { - $(this).data('perfect_scrollbar_update')(); - } - return $(this); - } - else if(option === 'destroy') { - if($(this).data('perfect_scrollbar_destroy')) { - $(this).data('perfect_scrollbar_destroy')(); - } - return $(this); - } - - if($(this).data('perfect_scrollbar')) { - // if there's already perfect_scrollbar - return $(this).data('perfect_scrollbar'); - } - - var $this = $(this).addClass('ps-container'), - $content = $(this).children(), - $scrollbar_x = $("
").appendTo($this), - $scrollbar_y = $("
").appendTo($this), - container_width, - container_height, - content_width, - content_height, - scrollbar_x_width, - scrollbar_x_left, - scrollbar_x_bottom = parseInt($scrollbar_x.css('bottom'), 10), - scrollbar_y_height, - scrollbar_y_top, - scrollbar_y_right = parseInt($scrollbar_y.css('right'), 10); - - var updateContentScrollTop = function() { - var scroll_top = parseInt(scrollbar_y_top * content_height / container_height, 10); - $this.scrollTop(scroll_top); - $scrollbar_x.css({bottom: scrollbar_x_bottom - scroll_top}); - }; - - var updateContentScrollLeft = function() { - var scroll_left = parseInt(scrollbar_x_left * content_width / container_width, 10); - $this.scrollLeft(scroll_left); - $scrollbar_y.css({right: scrollbar_y_right - scroll_left}); - }; - - var updateBarSizeAndPosition = function() { - container_width = $this.width(); - container_height = $this.height(); - content_width = $content.outerWidth(false); - content_height = $content.outerHeight(false); - if(container_width < content_width) { - scrollbar_x_width = parseInt(container_width * container_width / content_width, 10); - scrollbar_x_left = parseInt($this.scrollLeft() * container_width / content_width, 10); - } - else { - scrollbar_x_width = 0; - scrollbar_x_left = 0; - $this.scrollLeft(0); - } - if(container_height < content_height) { - scrollbar_y_height = parseInt(container_height * container_height / content_height, 10); - scrollbar_y_top = parseInt($this.scrollTop() * container_height / content_height, 10); - } - else { - scrollbar_y_height = 0; - scrollbar_y_left = 0; - $this.scrollTop(0); - } - - if(scrollbar_y_top >= container_height - scrollbar_y_height) { - scrollbar_y_top = container_height - scrollbar_y_height; - } - if(scrollbar_x_left >= container_width - scrollbar_x_width) { - scrollbar_x_left = container_width - scrollbar_x_width; - } - - $scrollbar_x.css({left: scrollbar_x_left + $this.scrollLeft(), bottom: scrollbar_x_bottom - $this.scrollTop(), width: scrollbar_x_width}); - $scrollbar_y.css({top: scrollbar_y_top + $this.scrollTop(), right: scrollbar_y_right - $this.scrollLeft(), height: scrollbar_y_height}); - }; - - var moveBarX = function(current_left, delta_x) { - var new_left = current_left + delta_x, - max_left = container_width - scrollbar_x_width; - - if(new_left < 0) { - scrollbar_x_left = 0; - } - else if(new_left > max_left) { - scrollbar_x_left = max_left; - } - else { - scrollbar_x_left = new_left; - } - $scrollbar_x.css({left: scrollbar_x_left + $this.scrollLeft()}); - }; - - var moveBarY = function(current_top, delta_y) { - var new_top = current_top + delta_y, - max_top = container_height - scrollbar_y_height; - - if(new_top < 0) { - scrollbar_y_top = 0; - } - else if(new_top > max_top) { - scrollbar_y_top = max_top; - } - else { - scrollbar_y_top = new_top; - } - $scrollbar_y.css({top: scrollbar_y_top + $this.scrollTop()}); - }; - - var bindMouseScrollXHandler = function() { - var current_left, - current_page_x; - - $scrollbar_x.bind('mousedown.perfect-scroll', function(e) { - current_page_x = e.pageX; - current_left = $scrollbar_x.position().left; - $scrollbar_x.addClass('in-scrolling'); - e.stopPropagation(); - e.preventDefault(); - }); - - $(document).bind('mousemove.perfect-scroll', function(e) { - if($scrollbar_x.hasClass('in-scrolling')) { - moveBarX(current_left, e.pageX - current_page_x); - updateContentScrollLeft(); - e.stopPropagation(); - e.preventDefault(); - } - }); - - $(document).bind('mouseup.perfect-scroll', function(e) { - if($scrollbar_x.hasClass('in-scrolling')) { - $scrollbar_x.removeClass('in-scrolling'); - } - }); - }; - - var bindMouseScrollYHandler = function() { - var current_top, - current_page_y; - - $scrollbar_y.bind('mousedown.perfect-scroll', function(e) { - current_page_y = e.pageY; - current_top = $scrollbar_y.position().top; - $scrollbar_y.addClass('in-scrolling'); - e.stopPropagation(); - e.preventDefault(); - }); - - $(document).bind('mousemove.perfect-scroll', function(e) { - if($scrollbar_y.hasClass('in-scrolling')) { - moveBarY(current_top, e.pageY - current_page_y); - updateContentScrollTop(); - e.stopPropagation(); - e.preventDefault(); - } - }); - - $(document).bind('mouseup.perfect-scroll', function(e) { - if($scrollbar_y.hasClass('in-scrolling')) { - $scrollbar_y.removeClass('in-scrolling'); - } - }); - }; - - // bind handlers - var bindMouseWheelHandler = function() { - var shouldPreventDefault = function(deltaX, deltaY) { - var scrollTop = $this.scrollTop(); - if(scrollTop === 0 && deltaY > 0 && deltaX === 0) { - return !settings.wheelPropagation; - } - else if(scrollTop >= content_height - container_height && deltaY < 0 && deltaX === 0) { - return !settings.wheelPropagation; - } - - var scrollLeft = $this.scrollLeft(); - if(scrollLeft === 0 && deltaX < 0 && deltaY === 0) { - return !settings.wheelPropagation; - } - else if(scrollLeft >= content_width - container_width && deltaX > 0 && deltaY === 0) { - return !settings.wheelPropagation; - } - return true; - }; - - $this.mousewheel(function(e, delta, deltaX, deltaY) { - $this.scrollTop($this.scrollTop() - (deltaY * settings.wheelSpeed)); - $this.scrollLeft($this.scrollLeft() + (deltaX * settings.wheelSpeed)); - - // update bar position - updateBarSizeAndPosition(); - - if(shouldPreventDefault(deltaX, deltaY)) { - e.preventDefault(); - } - }); - }; - - // bind mobile touch handler - var bindMobileTouchHandler = function() { - var applyTouchMove = function(difference_x, difference_y) { - $this.scrollTop($this.scrollTop() - difference_y); - $this.scrollLeft($this.scrollLeft() - difference_x); - - // update bar position - updateBarSizeAndPosition(); - }; - - var start_coords = {}, - start_time = 0, - speed = {}, - breaking_process = null; - - $this.bind("touchstart.perfect-scroll", function(e) { - var touch = e.originalEvent.targetTouches[0]; - - start_coords.pageX = touch.pageX; - start_coords.pageY = touch.pageY; - - start_time = (new Date()).getTime(); - - if (breaking_process !== null) { - clearInterval(breaking_process); - } - }); - $this.bind("touchmove.perfect-scroll", function(e) { - var touch = e.originalEvent.targetTouches[0]; - - var current_coords = {}; - current_coords.pageX = touch.pageX; - current_coords.pageY = touch.pageY; - - var difference_x = current_coords.pageX - start_coords.pageX, - difference_y = current_coords.pageY - start_coords.pageY; - - applyTouchMove(difference_x, difference_y); - start_coords = current_coords; - - var current_time = (new Date()).getTime(); - speed.x = difference_x / (current_time - start_time); - speed.y = difference_y / (current_time - start_time); - start_time = current_time; - - e.preventDefault(); - }); - $this.bind("touchend.perfect-scroll", function(e) { - breaking_process = setInterval(function() { - if(Math.abs(speed.x) < 0.01 && Math.abs(speed.y) < 0.01) { - clearInterval(breaking_process); - return; - } - - applyTouchMove(speed.x * 30, speed.y * 30); - - speed.x *= 0.8; - speed.y *= 0.8; - }, 10); - }); - }; - - var destroy = function() { - $scrollbar_x.remove(); - $scrollbar_y.remove(); - $this.unbind('mousewheel'); - $this.unbind('touchstart.perfect-scroll'); - $this.unbind('touchmove.perfect-scroll'); - $this.unbind('touchend.perfect-scroll'); - $(window).unbind('mousemove.perfect-scroll'); - $(window).unbind('mouseup.perfect-scroll'); - $this.data('perfect_scrollbar', null); - $this.data('perfect_scrollbar_update', null); - $this.data('perfect_scrollbar_destroy', null); - }; - - var isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent); - - var initialize = function() { - updateBarSizeAndPosition(); - bindMouseScrollXHandler(); - bindMouseScrollYHandler(); - if(isMobile) bindMobileTouchHandler(); - if($this.mousewheel) bindMouseWheelHandler(); - $this.data('perfect_scrollbar', $this); - $this.data('perfect_scrollbar_update', updateBarSizeAndPosition); - $this.data('perfect_scrollbar_destroy', destroy); - }; - - // initialize - initialize(); - - return $this; + var updateContentScrollLeft = function () { + var scrollLeft = parseInt(scrollbarXLeft * contentWidth / containerWidth, 10); + $this.scrollLeft(scrollLeft); + $scrollbarY.css({right: scrollbarYRight - scrollLeft}); }; + + var updateBarSizeAndPosition = function () { + containerWidth = $this.width(); + containerHeight = $this.height(); + contentWidth = $content.outerWidth(false); + contentHeight = $content.outerHeight(false); + if (containerWidth < contentWidth) { + scrollbarXWidth = parseInt(containerWidth * containerWidth / contentWidth, 10); + scrollbarXLeft = parseInt($this.scrollLeft() * containerWidth / contentWidth, 10); + } + else { + scrollbarXWidth = 0; + scrollbarXLeft = 0; + $this.scrollLeft(0); + } + if (containerHeight < contentHeight) { + scrollbarYHeight = parseInt(containerHeight * containerHeight / contentHeight, 10); + scrollbarYTop = parseInt($this.scrollTop() * containerHeight / contentHeight, 10); + } + else { + scrollbarYHeight = 0; + scrollbarYTop = 0; + $this.scrollTop(0); + } + + if (scrollbarYTop >= containerHeight - scrollbarYHeight) { + scrollbarYTop = containerHeight - scrollbarYHeight; + } + if (scrollbarXLeft >= containerWidth - scrollbarXWidth) { + scrollbarXLeft = containerWidth - scrollbarXWidth; + } + + $scrollbarX.css({left: scrollbarXLeft + $this.scrollLeft(), bottom: scrollbarXBottom - $this.scrollTop(), width: scrollbarXWidth}); + $scrollbarY.css({top: scrollbarYTop + $this.scrollTop(), right: scrollbarYRight - $this.scrollLeft(), height: scrollbarYHeight}); + }; + + var moveBarX = function (currentLeft, deltaX) { + var newLeft = currentLeft + deltaX, + maxLeft = containerWidth - scrollbarXWidth; + + if (newLeft < 0) { + scrollbarXLeft = 0; + } + else if (newLeft > maxLeft) { + scrollbarXLeft = maxLeft; + } + else { + scrollbarXLeft = newLeft; + } + $scrollbarX.css({left: scrollbarXLeft + $this.scrollLeft()}); + }; + + var moveBarY = function (currentTop, deltaY) { + var newTop = currentTop + deltaY, + maxTop = containerHeight - scrollbarYHeight; + + if (newTop < 0) { + scrollbarYTop = 0; + } + else if (newTop > maxTop) { + scrollbarYTop = maxTop; + } + else { + scrollbarYTop = newTop; + } + $scrollbarY.css({top: scrollbarYTop + $this.scrollTop()}); + }; + + var bindMouseScrollXHandler = function () { + var currentLeft, + currentPageX; + + $scrollbarX.bind('mousedown.perfect-scroll', function (e) { + currentPageX = e.pageX; + currentLeft = $scrollbarX.position().left; + $scrollbarX.addClass('in-scrolling'); + e.stopPropagation(); + e.preventDefault(); + }); + + $(document).bind('mousemove.perfect-scroll', function (e) { + if ($scrollbarX.hasClass('in-scrolling')) { + moveBarX(currentLeft, e.pageX - currentPageX); + updateContentScrollLeft(); + e.stopPropagation(); + e.preventDefault(); + } + }); + + $(document).bind('mouseup.perfect-scroll', function (e) { + if ($scrollbarX.hasClass('in-scrolling')) { + $scrollbarX.removeClass('in-scrolling'); + } + }); + }; + + var bindMouseScrollYHandler = function () { + var currentTop, + currentPageY; + + $scrollbarY.bind('mousedown.perfect-scroll', function (e) { + currentPageY = e.pageY; + currentTop = $scrollbarY.position().top; + $scrollbarY.addClass('in-scrolling'); + e.stopPropagation(); + e.preventDefault(); + }); + + $(document).bind('mousemove.perfect-scroll', function (e) { + if ($scrollbarY.hasClass('in-scrolling')) { + moveBarY(currentTop, e.pageY - currentPageY); + updateContentScrollTop(); + e.stopPropagation(); + e.preventDefault(); + } + }); + + $(document).bind('mouseup.perfect-scroll', function (e) { + if ($scrollbarY.hasClass('in-scrolling')) { + $scrollbarY.removeClass('in-scrolling'); + } + }); + }; + + // bind handlers + var bindMouseWheelHandler = function () { + var shouldPreventDefault = function (deltaX, deltaY) { + var scrollTop = $this.scrollTop(); + if (scrollTop === 0 && deltaY > 0 && deltaX === 0) { + return !settings.wheelPropagation; + } + else if (scrollTop >= contentHeight - containerHeight && deltaY < 0 && deltaX === 0) { + return !settings.wheelPropagation; + } + + var scrollLeft = $this.scrollLeft(); + if (scrollLeft === 0 && deltaX < 0 && deltaY === 0) { + return !settings.wheelPropagation; + } + else if (scrollLeft >= contentWidth - containerWidth && deltaX > 0 && deltaY === 0) { + return !settings.wheelPropagation; + } + return true; + }; + + $this.mousewheel(function (e, delta, deltaX, deltaY) { + $this.scrollTop($this.scrollTop() - (deltaY * settings.wheelSpeed)); + $this.scrollLeft($this.scrollLeft() + (deltaX * settings.wheelSpeed)); + + // update bar position + updateBarSizeAndPosition(); + + if (shouldPreventDefault(deltaX, deltaY)) { + e.preventDefault(); + } + }); + }; + + // bind mobile touch handler + var bindMobileTouchHandler = function () { + var applyTouchMove = function (differenceX, differenceY) { + $this.scrollTop($this.scrollTop() - differenceY); + $this.scrollLeft($this.scrollLeft() - differenceX); + + // update bar position + updateBarSizeAndPosition(); + }; + + var startCoords = {}, + startTime = 0, + speed = {}, + breakingProcess = null; + + $this.bind("touchstart.perfect-scroll", function (e) { + var touch = e.originalEvent.targetTouches[0]; + + startCoords.pageX = touch.pageX; + startCoords.pageY = touch.pageY; + + startTime = (new Date()).getTime(); + + if (breakingProcess !== null) { + clearInterval(breakingProcess); + } + }); + $this.bind("touchmove.perfect-scroll", function (e) { + var touch = e.originalEvent.targetTouches[0]; + + var currentCoords = {}; + currentCoords.pageX = touch.pageX; + currentCoords.pageY = touch.pageY; + + var differenceX = currentCoords.pageX - startCoords.pageX, + differenceY = currentCoords.pageY - startCoords.pageY; + + applyTouchMove(differenceX, differenceY); + startCoords = currentCoords; + + var currentTime = (new Date()).getTime(); + speed.x = differenceX / (currentTime - startTime); + speed.y = differenceY / (currentTime - startTime); + startTime = currentTime; + + e.preventDefault(); + }); + $this.bind("touchend.perfect-scroll", function (e) { + breakingProcess = setInterval(function () { + if (Math.abs(speed.x) < 0.01 && Math.abs(speed.y) < 0.01) { + clearInterval(breakingProcess); + return; + } + + applyTouchMove(speed.x * 30, speed.y * 30); + + speed.x *= 0.8; + speed.y *= 0.8; + }, 10); + }); + }; + + var destroy = function () { + $scrollbarX.remove(); + $scrollbarY.remove(); + $this.unbind('mousewheel'); + $this.unbind('touchstart.perfect-scroll'); + $this.unbind('touchmove.perfect-scroll'); + $this.unbind('touchend.perfect-scroll'); + $(window).unbind('mousemove.perfect-scroll'); + $(window).unbind('mouseup.perfect-scroll'); + $this.data('perfect-scrollbar', null); + $this.data('perfect-scrollbar-update', null); + $this.data('perfect-scrollbar-destroy', null); + }; + + var isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent); + + var initialize = function () { + updateBarSizeAndPosition(); + bindMouseScrollXHandler(); + bindMouseScrollYHandler(); + if (isMobile) { + bindMobileTouchHandler(); + } + if ($this.mousewheel) { + bindMouseWheelHandler(); + } + $this.data('perfect-scrollbar', $this); + $this.data('perfect-scrollbar-update', updateBarSizeAndPosition); + $this.data('perfect-scrollbar-destroy', destroy); + }; + + // initialize + initialize(); + + return $this; + }; })(jQuery));