diff --git a/src/js/lib/helper.js b/src/js/lib/helper.js index 2e12338..8226db4 100644 --- a/src/js/lib/helper.js +++ b/src/js/lib/helper.js @@ -61,6 +61,26 @@ exports.outerWidth = function (element) { this.toInt(d.css(element, 'borderRightWidth')); }; +exports.startScrolling = function (element, axis) { + cls.add(element, 'ps-in-scrolling'); + if (typeof axis !== 'undefined') { + cls.add(element, 'ps-' + axis); + } else { + cls.add(element, 'ps-x'); + cls.add(element, 'ps-y'); + } +}; + +exports.stopScrolling = function (element, axis) { + cls.remove(element, 'ps-in-scrolling'); + if (typeof axis !== 'undefined') { + cls.remove(element, 'ps-' + axis); + } else { + cls.remove(element, 'ps-x'); + cls.remove(element, 'ps-y'); + } +}; + exports.env = { isWebKit: 'WebkitAppearance' in document.documentElement.style, supportsTouch: (('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch), diff --git a/src/js/plugin/handler/click-rail.js b/src/js/plugin/handler/click-rail.js new file mode 100644 index 0000000..a1a85e1 --- /dev/null +++ b/src/js/plugin/handler/click-rail.js @@ -0,0 +1,52 @@ +/* Copyright (c) 2015 Hyunje Alex Jun and other contributors + * Licensed under the MIT License + */ +'use strict'; + +var h = require('../../lib/helper') + , instances = require('../instances'); + +function bindClickRailHandler(element, i) { + function pageOffset(el) { + return el.getBoundingClientRect(); + } + var stopPropagation = window.Event.prototype.stopPropagation.bind; + + i.event.bind(i.scrollbarY, 'click', stopPropagation); + i.event.bind(i.scrollbarYRail, 'click', function (e) { + var halfOfScrollbarLength = h.toInt(i.scrollbarYHeight / 2); + var positionTop = e.pageY - pageOffset(i.scrollbarYRail).top - halfOfScrollbarLength; + var maxPositionTop = i.containerHeight - i.scrollbarYHeight; + var positionRatio = positionTop / maxPositionTop; + + if (positionRatio < 0) { + positionRatio = 0; + } else if (positionRatio > 1) { + positionRatio = 1; + } + + element.scrollTop = (i.contentHeight - i.containerHeight) * positionRatio; + }); + + i.event.bind(i.scrollbarX, 'click', stopPropagation); + i.event.bind(i.scrollbarXRail, 'click', function (e) { + var halfOfScrollbarLength = h.toInt(i.scrollbarXWidth / 2); + var positionLeft = e.pageX - pageOffset(i.scrollbarXRail).left - halfOfScrollbarLength; + console.log(e.pageX, i.scrollbarXRail.offsetLeft); + var maxPositionLeft = i.containerWidth - i.scrollbarXWidth; + var positionRatio = positionLeft / maxPositionLeft; + + if (positionRatio < 0) { + positionRatio = 0; + } else if (positionRatio > 1) { + positionRatio = 1; + } + + element.scrollLeft = (i.contentWidth - i.containerWidth) * positionRatio; + }); +} + +module.exports = function (element) { + var i = instances.get(element); + bindClickRailHandler(element, i); +}; diff --git a/src/js/plugin/handler/drag-scrollbar.js b/src/js/plugin/handler/drag-scrollbar.js new file mode 100644 index 0000000..972fda0 --- /dev/null +++ b/src/js/plugin/handler/drag-scrollbar.js @@ -0,0 +1,105 @@ +/* Copyright (c) 2015 Hyunje Alex Jun and other contributors + * Licensed under the MIT License + */ +'use strict'; + +var d = require('../../lib/dom') + , h = require('../../lib/helper') + , instances = require('../instances') + , updateGeometry = require('../update'); + +function bindMouseScrollXHandler(element, i) { + var currentLeft = null; + var currentPageX = null; + + function updateScrollLeft(deltaX) { + var newLeft = currentLeft + deltaX; + var maxLeft = i.containerWidth - i.scrollbarXWidth; + + if (newLeft < 0) { + i.scrollbarXLeft = 0; + } else if (newLeft > maxLeft) { + i.scrollbarXLeft = maxLeft; + } else { + i.scrollbarXLeft = newLeft; + } + + var scrollLeft = h.toInt(i.scrollbarXLeft * (i.contentWidth - i.containerWidth) / (i.containerWidth - i.scrollbarXWidth)); + element.scrollLeft = scrollLeft; + } + + var mouseMoveHandler = function (e) { + updateScrollLeft(e.pageX - currentPageX); + updateGeometry(element); + e.stopPropagation(); + e.preventDefault(); + }; + + var mouseUpHandler = function () { + h.stopScrolling(element, 'x'); + i.event.unbind(i.ownerDocument, 'mousemove', mouseMoveHandler); + }; + + i.event.bind(i.scrollbarX, 'mousedown', function (e) { + currentPageX = e.pageX; + currentLeft = h.toInt(d.css(i.scrollbarX, 'left')); + h.startScrolling(element, 'x'); + + i.event.bind(i.ownerDocument, 'mousemove', mouseMoveHandler); + i.event.once(i.ownerDocument, 'mouseup', mouseUpHandler); + + e.stopPropagation(); + e.preventDefault(); + }); +} + +function bindMouseScrollYHandler(element, i) { + var currentTop = null; + var currentPageY = null; + + function updateScrollTop(deltaY) { + var newTop = currentTop + deltaY; + var maxTop = i.containerHeight - i.scrollbarYHeight; + + if (newTop < 0) { + i.scrollbarYTop = 0; + } else if (newTop > maxTop) { + i.scrollbarYTop = maxTop; + } else { + i.scrollbarYTop = newTop; + } + + var scrollTop = h.toInt(i.scrollbarYTop * (i.contentHeight - i.containerHeight) / (i.containerHeight - i.scrollbarYHeight)); + element.scrollTop = scrollTop; + } + + var mouseMoveHandler = function (e) { + updateScrollTop(e.pageY - currentPageY); + updateGeometry(element); + e.stopPropagation(); + e.preventDefault(); + }; + + var mouseUpHandler = function () { + h.stopScrolling(element, 'y'); + i.event.unbind(i.ownerDocument, 'mousemove', mouseMoveHandler); + }; + + i.event.bind(i.scrollbarY, 'mousedown', function (e) { + currentPageY = e.pageY; + currentTop = h.toInt(d.css(i.scrollbarY, 'top')); + h.startScrolling(element, 'y'); + + i.event.bind(i.ownerDocument, 'mousemove', mouseMoveHandler); + i.event.once(i.ownerDocument, 'mouseup', mouseUpHandler); + + e.stopPropagation(); + e.preventDefault(); + }); +} + +module.exports = function (element) { + var i = instances.get(element); + bindMouseScrollXHandler(element, i); + bindMouseScrollYHandler(element, i); +}; diff --git a/src/js/plugin/handler/keyboard.js b/src/js/plugin/handler/keyboard.js new file mode 100644 index 0000000..256fca2 --- /dev/null +++ b/src/js/plugin/handler/keyboard.js @@ -0,0 +1,114 @@ +/* Copyright (c) 2015 Hyunje Alex Jun and other contributors + * Licensed under the MIT License + */ +'use strict'; + +var h = require('../../lib/helper') + , instances = require('../instances'); + +function bindKeyboardHandler(element, i) { + var hovered = false; + i.event.bind(element, 'mouseenter', function () { + hovered = true; + }); + i.event.bind(element, 'mouseleave', function () { + hovered = false; + }); + + var shouldPrevent = false; + function shouldPreventDefault(deltaX, deltaY) { + var scrollTop = element.scrollTop; + if (deltaX === 0) { + if (!i.scrollbarYActive) { + return false; + } + if ((scrollTop === 0 && deltaY > 0) || (scrollTop >= i.contentHeight - i.containerHeight && deltaY < 0)) { + return !i.settings.wheelPropagation; + } + } + + var scrollLeft = element.scrollLeft; + if (deltaY === 0) { + if (!i.scrollbarXActive) { + return false; + } + if ((scrollLeft === 0 && deltaX < 0) || (scrollLeft >= i.contentWidth - i.containerWidth && deltaX > 0)) { + return !i.settings.wheelPropagation; + } + } + return true; + } + + i.event.bind(i.ownerDocument, 'keydown', function (e) { + if (e.isDefaultPrevented && e.isDefaultPrevented()) { + return; + } + + if (!hovered) { + return; + } + + var activeElement = document.activeElement ? document.activeElement : i.ownerDocument.activeElement; + // go deeper if element is a webcomponent + while (activeElement.shadowRoot) { + activeElement = activeElement.shadowRoot.activeElement; + } + if (h.isEditable(activeElement)) { + return; + } + + var deltaX = 0; + var deltaY = 0; + + switch (e.which) { + case 37: // left + deltaX = -30; + break; + case 38: // up + deltaY = 30; + break; + case 39: // right + deltaX = 30; + break; + case 40: // down + deltaY = -30; + break; + case 33: // page up + deltaY = 90; + break; + case 32: // space bar + case 34: // page down + deltaY = -90; + break; + case 35: // end + if (e.ctrlKey) { + deltaY = -i.contentHeight; + } else { + deltaY = -i.containerHeight; + } + break; + case 36: // home + if (e.ctrlKey) { + deltaY = element.scrollTop; + } else { + deltaY = i.containerHeight; + } + break; + default: + return; + } + + element.scrollTop = element.scrollTop - deltaY; + element.scrollLeft = element.scrollLeft + deltaX; + + shouldPrevent = shouldPreventDefault(deltaX, deltaY); + if (shouldPrevent) { + e.preventDefault(); + } + }); +} + +module.exports = function (element) { + var i = instances.get(element); + bindKeyboardHandler(element, i); +}; diff --git a/src/js/plugin/handler/mouse-wheel.js b/src/js/plugin/handler/mouse-wheel.js new file mode 100644 index 0000000..24f0b3c --- /dev/null +++ b/src/js/plugin/handler/mouse-wheel.js @@ -0,0 +1,119 @@ +/* Copyright (c) 2015 Hyunje Alex Jun and other contributors + * Licensed under the MIT License + */ +'use strict'; + +var h = require('../../lib/helper') + , instances = require('../instances') + , updateGeometry = require('../update'); + +function bindMouseWheelHandler(element, i) { + var shouldPrevent = false; + + function shouldPreventDefault(deltaX, deltaY) { + var scrollTop = element.scrollTop; + if (deltaX === 0) { + if (!i.scrollbarYActive) { + return false; + } + if ((scrollTop === 0 && deltaY > 0) || (scrollTop >= i.contentHeight - i.containerHeight && deltaY < 0)) { + return !i.settings.wheelPropagation; + } + } + + var scrollLeft = element.scrollLeft; + if (deltaY === 0) { + if (!i.scrollbarXActive) { + return false; + } + if ((scrollLeft === 0 && deltaX < 0) || (scrollLeft >= i.contentWidth - i.containerWidth && deltaX > 0)) { + return !i.settings.wheelPropagation; + } + } + return true; + } + + function getDeltaFromEvent(e) { + var deltaX = e.deltaX; + var deltaY = -1 * e.deltaY; + + if (typeof deltaX === "undefined" || typeof deltaY === "undefined") { + // OS X Safari + deltaX = -1 * e.wheelDeltaX / 6; + deltaY = e.wheelDeltaY / 6; + } + + if (e.deltaMode && e.deltaMode === 1) { + // Firefox in deltaMode 1: Line scrolling + deltaX *= 10; + deltaY *= 10; + } + + if (deltaX !== deltaX && deltaY !== deltaY/* NaN checks */) { + // IE in some mouse drivers + deltaX = 0; + deltaY = e.wheelDelta; + } + + return [deltaX, deltaY]; + } + + function mousewheelHandler(e) { + // FIXME: this is a quick fix for the select problem in FF and IE. + // If there comes an effective way to deal with the problem, + // this lines should be removed. + if (!h.env.isWebKit && element.querySelector('select:focus')) { + return; + } + + var delta = getDeltaFromEvent(e); + + var deltaX = delta[0]; + var deltaY = delta[1]; + + shouldPrevent = false; + if (!i.settings.useBothWheelAxes) { + // deltaX will only be used for horizontal scrolling and deltaY will + // only be used for vertical scrolling - this is the default + element.scrollTop = element.scrollTop - (deltaY * i.settings.wheelSpeed); + element.scrollLeft = element.scrollLeft + (deltaX * i.settings.wheelSpeed); + } else if (i.scrollbarYActive && !i.scrollbarXActive) { + // only vertical scrollbar is active and useBothWheelAxes option is + // active, so let's scroll vertical bar using both mouse wheel axes + if (deltaY) { + element.scrollTop = element.scrollTop - (deltaY * i.settings.wheelSpeed); + } else { + element.scrollTop = element.scrollTop + (deltaX * i.settings.wheelSpeed); + } + shouldPrevent = true; + } else if (i.scrollbarXActive && !i.scrollbarYActive) { + // useBothWheelAxes and only horizontal bar is active, so use both + // wheel axes for horizontal bar + if (deltaX) { + element.scrollLeft = element.scrollLeft + (deltaX * i.settings.wheelSpeed); + } else { + element.scrollLeft = element.scrollLeft - (deltaY * i.settings.wheelSpeed); + } + shouldPrevent = true; + } + + updateGeometry(element); + + shouldPrevent = (shouldPrevent || shouldPreventDefault(deltaX, deltaY)); + if (shouldPrevent) { + e.stopPropagation(); + e.preventDefault(); + } + } + + if (typeof window.onwheel !== "undefined") { + i.event.bind(element, 'wheel', mousewheelHandler); + } else if (typeof window.onmousewheel !== "undefined") { + i.event.bind(element, 'mousewheel', mousewheelHandler); + } +} + +module.exports = function (element) { + var i = instances.get(element); + bindMouseWheelHandler(element, i); +}; diff --git a/src/js/plugin/handler/native-scroll.js b/src/js/plugin/handler/native-scroll.js new file mode 100644 index 0000000..3acd429 --- /dev/null +++ b/src/js/plugin/handler/native-scroll.js @@ -0,0 +1,18 @@ +/* Copyright (c) 2015 Hyunje Alex Jun and other contributors + * Licensed under the MIT License + */ +'use strict'; + +var instances = require('../instances') + , updateGeometry = require('../update'); + +function bindNativeScrollHandler(element, i) { + i.event.bind(element, 'scroll', function () { + updateGeometry(element); + }); +} + +module.exports = function (element) { + var i = instances.get(element); + bindNativeScrollHandler(element, i); +}; diff --git a/src/js/plugin/handler/selection.js b/src/js/plugin/handler/selection.js new file mode 100644 index 0000000..840b7f4 --- /dev/null +++ b/src/js/plugin/handler/selection.js @@ -0,0 +1,111 @@ +/* Copyright (c) 2015 Hyunje Alex Jun and other contributors + * Licensed under the MIT License + */ +'use strict'; + +var h = require('../../lib/helper') + , instances = require('../instances') + , updateGeometry = require('../update'); + +function bindSelectionHandler(element, i) { + function getRangeNode() { + var selection = window.getSelection ? window.getSelection() : + document.getSlection ? document.getSlection() : {rangeCount: 0}; + if (selection.rangeCount === 0) { + return null; + } else { + return selection.getRangeAt(0).commonAncestorContainer; + } + } + + var scrollingLoop = null; + var scrollDiff = {top: 0, left: 0}; + function startScrolling() { + if (!scrollingLoop) { + scrollingLoop = setInterval(function () { + if (!instances.get(element)) { + clearInterval(scrollingLoop); + return; + } + + element.scrollTop = element.scrollTop + scrollDiff.top; + element.scrollLeft = element.scrollLeft + scrollDiff.left; + updateGeometry(element); + }, 50); // every .1 sec + } + } + function stopScrolling() { + if (scrollingLoop) { + clearInterval(scrollingLoop); + scrollingLoop = null; + } + h.stopScrolling(element); + } + + var isSelected = false; + i.event.bind(i.ownerDocument, 'selectionchange', function () { + if (element.contains(getRangeNode())) { + isSelected = true; + } else { + isSelected = false; + stopScrolling(); + } + }); + i.event.bind(window, 'mouseup', function () { + if (isSelected) { + isSelected = false; + stopScrolling(); + } + }); + + i.event.bind(window, 'mousemove', function (e) { + if (isSelected) { + var mousePosition = {x: e.pageX, y: e.pageY}; + var containerGeometry = { + left: element.offsetLeft, + right: element.offsetLeft + element.offsetWidth, + top: element.offsetTop, + bottom: element.offsetTop + element.offsetHeight + }; + + if (mousePosition.x < containerGeometry.left + 3) { + scrollDiff.left = -5; + h.startScrolling(element, 'x'); + } else if (mousePosition.x > containerGeometry.right - 3) { + scrollDiff.left = 5; + h.startScrolling(element, 'x'); + } else { + scrollDiff.left = 0; + } + + if (mousePosition.y < containerGeometry.top + 3) { + if (containerGeometry.top + 3 - mousePosition.y < 5) { + scrollDiff.top = -5; + } else { + scrollDiff.top = -20; + } + h.startScrolling(element, 'y'); + } else if (mousePosition.y > containerGeometry.bottom - 3) { + if (mousePosition.y - containerGeometry.bottom + 3 < 5) { + scrollDiff.top = 5; + } else { + scrollDiff.top = 20; + } + h.startScrolling(element, 'y'); + } else { + scrollDiff.top = 0; + } + + if (scrollDiff.top === 0 && scrollDiff.left === 0) { + stopScrolling(); + } else { + startScrolling(); + } + } + }); +} + +module.exports = function (element) { + var i = instances.get(element); + bindSelectionHandler(element, i); +}; diff --git a/src/js/plugin/handler/touch.js b/src/js/plugin/handler/touch.js new file mode 100644 index 0000000..8678d41 --- /dev/null +++ b/src/js/plugin/handler/touch.js @@ -0,0 +1,170 @@ +/* Copyright (c) 2015 Hyunje Alex Jun and other contributors + * Licensed under the MIT License + */ +'use strict'; + +var instances = require('../instances') + , updateGeometry = require('../update'); + +function bindTouchHandler(element, i, supportsTouch, supportsIePointer) { + function shouldPreventDefault(deltaX, deltaY) { + var scrollTop = element.scrollTop; + var scrollLeft = element.scrollLeft; + var magnitudeX = Math.abs(deltaX); + var magnitudeY = Math.abs(deltaY); + + if (magnitudeY > magnitudeX) { + // user is perhaps trying to swipe up/down the page + + if (((deltaY < 0) && (scrollTop === i.contentHeight - i.containerHeight)) || + ((deltaY > 0) && (scrollTop === 0))) { + return !i.settings.swipePropagation; + } + } else if (magnitudeX > magnitudeY) { + // user is perhaps trying to swipe left/right across the page + + if (((deltaX < 0) && (scrollLeft === i.contentWidth - i.containerWidth)) || + ((deltaX > 0) && (scrollLeft === 0))) { + return !i.settings.swipePropagation; + } + } + + return true; + } + + function applyTouchMove(differenceX, differenceY) { + element.scrollTop = element.scrollTop - differenceY; + element.scrollLeft = element.scrollLeft() - differenceX; + + updateGeometry(element); + } + + var startOffset = {}; + var startTime = 0; + var speed = {}; + var easingLoop = null; + var inGlobalTouch = false; + var inLocalTouch = false; + + function globalTouchStart() { + inGlobalTouch = true; + } + function globalTouchEnd() { + inGlobalTouch = false; + } + + function getTouch(e) { + if (e.targetTouches) { + return e.targetTouches[0]; + } else { + // Maybe IE pointer + return e; + } + } + function shouldHandle(e) { + if (e.targetTouches && e.targetTouches.length === 1) { + return true; + } + if (e.pointerType && e.pointerType !== 'mouse' && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) { + return true; + } + return false; + } + function touchStart(e) { + if (shouldHandle(e)) { + inLocalTouch = true; + + var touch = getTouch(e); + + startOffset.pageX = touch.pageX; + startOffset.pageY = touch.pageY; + + startTime = (new Date()).getTime(); + + if (easingLoop !== null) { + clearInterval(easingLoop); + } + + e.stopPropagation(); + } + } + function touchMove(e) { + if (!inGlobalTouch && inLocalTouch && shouldHandle(e)) { + var touch = getTouch(e); + + var currentOffset = {pageX: touch.pageX, pageY: touch.pageY}; + + var differenceX = currentOffset.pageX - startOffset.pageX; + var differenceY = currentOffset.pageY - startOffset.pageY; + + applyTouchMove(differenceX, differenceY); + startOffset = currentOffset; + + var currentTime = (new Date()).getTime(); + + var timeGap = currentTime - startTime; + if (timeGap > 0) { + speed.x = differenceX / timeGap; + speed.y = differenceY / timeGap; + startTime = currentTime; + } + + if (shouldPreventDefault(differenceX, differenceY)) { + e.stopPropagation(); + e.preventDefault(); + } + } + } + function touchEnd() { + if (!inGlobalTouch && inLocalTouch) { + inLocalTouch = false; + + clearInterval(easingLoop); + easingLoop = setInterval(function () { + if (!instances.get(element)) { + clearInterval(easingLoop); + return; + } + + if (Math.abs(speed.x) < 0.01 && Math.abs(speed.y) < 0.01) { + clearInterval(easingLoop); + return; + } + + applyTouchMove(speed.x * 30, speed.y * 30); + + speed.x *= 0.8; + speed.y *= 0.8; + }, 10); + } + } + + if (supportsTouch) { + i.event.bind(window, 'touchstart', globalTouchStart); + i.event.bind(window, 'touchend', globalTouchEnd); + i.event.bind(element, 'touchstart', touchStart); + i.event.bind(element, 'touchmove', touchMove); + i.event.bind(element, 'touchend', touchEnd); + } + + if (supportsIePointer) { + if (window.PointerEvent) { + i.event.bind(window, 'pointerdown', globalTouchStart); + i.event.bind(window, 'pointerup', globalTouchEnd); + i.event.bind(element, 'pointerdown', touchStart); + i.event.bind(element, 'pointermove', touchMove); + i.event.bind(element, 'pointerup', touchEnd); + } else if (window.MSPointerEvent) { + i.event.bind(window, 'MSPointerDown', globalTouchStart); + i.event.bind(window, 'MSPointerUp', globalTouchEnd); + i.event.bind(element, 'MSPointerDown', touchStart); + i.event.bind(element, 'MSPointerMove', touchMove); + i.event.bind(element, 'MSPointerUp', touchEnd); + } + } +} + +module.exports = function (element, supportsTouch, supportsIePointer) { + var i = instances.get(element); + bindTouchHandler(element, i, supportsTouch, supportsIePointer); +}; diff --git a/src/js/plugin/initialize.js b/src/js/plugin/initialize.js index 3000994..e621878 100644 --- a/src/js/plugin/initialize.js +++ b/src/js/plugin/initialize.js @@ -4,11 +4,19 @@ 'use strict'; var cls = require('../lib/class') - , d = require('../lib/dom') , h = require('../lib/helper') , instances = require('./instances') , updateGeometry = require('./update'); +// Handlers +var clickRailHandler = require('./handler/click-rail') + , dragScrollbarHandler = require('./handler/drag-scrollbar') + , keyboardHandler = require('./handler/keyboard') + , mouseWheelHandler = require('./handler/mouse-wheel') + , nativeScrollHandler = require('./handler/native-scroll') + , selectionHandler = require('./handler/selection') + , touchHandler = require('./handler/touch'); + module.exports = function (element, userSettings) { userSettings = typeof userSettings === 'object' ? userSettings : {}; @@ -19,606 +27,18 @@ module.exports = function (element, userSettings) { i.settings = h.extend(i.settings, userSettings); - function updateScrollTop(currentTop, deltaY) { - var newTop = currentTop + deltaY; - var maxTop = i.containerHeight - i.scrollbarYHeight; + clickRailHandler(element); + dragScrollbarHandler(element); + mouseWheelHandler(element); + nativeScrollHandler(element); + selectionHandler(element); - if (newTop < 0) { - i.scrollbarYTop = 0; - } else if (newTop > maxTop) { - i.scrollbarYTop = maxTop; - } else { - i.scrollbarYTop = newTop; - } - - var scrollTop = h.toInt(i.scrollbarYTop * (i.contentHeight - i.containerHeight) / (i.containerHeight - i.scrollbarYHeight)); - element.scrollTop = scrollTop; + if (h.env.supportsTouch || h.env.supportsIePointer) { + touchHandler(element, h.env.supportsTouch, h.env.supportsIePointer); + } + if (i.settings.useKeyboard) { + keyboardHandler(element); } - function updateScrollLeft(currentLeft, deltaX) { - var newLeft = currentLeft + deltaX; - var maxLeft = i.containerWidth - i.scrollbarXWidth; - - if (newLeft < 0) { - i.scrollbarXLeft = 0; - } else if (newLeft > maxLeft) { - i.scrollbarXLeft = maxLeft; - } else { - i.scrollbarXLeft = newLeft; - } - - var scrollLeft = h.toInt(i.scrollbarXLeft * (i.contentWidth - i.containerWidth) / (i.containerWidth - i.scrollbarXWidth)); - element.scrollLeft = scrollLeft; - } - - function bindMouseScrollXHandler() { - var currentLeft; - var currentPageX; - - var mouseMoveHandler = function (e) { - updateScrollLeft(currentLeft, e.pageX - currentPageX); - updateGeometry(element); - e.stopPropagation(); - e.preventDefault(); - }; - - var mouseUpHandler = function () { - cls.remove(element, 'ps-x'); - cls.remove(element, 'ps-in-scrolling'); - i.event.unbind(i.ownerDocument, 'mousemove', mouseMoveHandler); - }; - - i.event.bind(i.scrollbarX, 'mousedown', function (e) { - currentPageX = e.pageX; - currentLeft = h.toInt(d.css(i.scrollbarX, 'left')); - cls.add(element, 'ps-in-scrolling'); - cls.add(element, 'ps-x'); - - i.event.bind(i.ownerDocument, 'mousemove', mouseMoveHandler); - i.event.once(i.ownerDocument, 'mouseup', mouseUpHandler); - - e.stopPropagation(); - e.preventDefault(); - }); - - currentLeft = - currentPageX = null; - } - - function bindMouseScrollYHandler() { - var currentTop; - var currentPageY; - - var mouseMoveHandler = function (e) { - updateScrollTop(currentTop, e.pageY - currentPageY); - updateGeometry(element); - e.stopPropagation(); - e.preventDefault(); - }; - - var mouseUpHandler = function () { - cls.remove(element, 'ps-y'); - cls.remove(element, 'ps-in-scrolling'); - i.event.unbind(i.ownerDocument, 'mousemove', mouseMoveHandler); - }; - - i.event.bind(i.scrollbarY, 'mousedown', function (e) { - currentPageY = e.pageY; - currentTop = h.toInt(d.css(i.scrollbarY, 'top')); - cls.add(element, 'ps-in-scrolling'); - cls.add(element, 'ps-y'); - - i.event.bind(i.ownerDocument, 'mousemove', mouseMoveHandler); - i.event.once(i.ownerDocument, 'mouseup', mouseUpHandler); - - e.stopPropagation(); - e.preventDefault(); - }); - - currentTop = - currentPageY = null; - } - - function shouldPreventWheel(deltaX, deltaY) { - var scrollTop = element.scrollTop; - if (deltaX === 0) { - if (!i.scrollbarYActive) { - return false; - } - if ((scrollTop === 0 && deltaY > 0) || (scrollTop >= i.contentHeight - i.containerHeight && deltaY < 0)) { - return !i.settings.wheelPropagation; - } - } - - var scrollLeft = element.scrollLeft; - if (deltaY === 0) { - if (!i.scrollbarXActive) { - return false; - } - if ((scrollLeft === 0 && deltaX < 0) || (scrollLeft >= i.contentWidth - i.containerWidth && deltaX > 0)) { - return !i.settings.wheelPropagation; - } - } - return true; - } - - function shouldPreventSwipe(deltaX, deltaY) { - var scrollTop = element.scrollTop; - var scrollLeft = element.scrollLeft; - var magnitudeX = Math.abs(deltaX); - var magnitudeY = Math.abs(deltaY); - - if (magnitudeY > magnitudeX) { - // user is perhaps trying to swipe up/down the page - - if (((deltaY < 0) && (scrollTop === i.contentHeight - i.containerHeight)) || - ((deltaY > 0) && (scrollTop === 0))) { - return !i.settings.swipePropagation; - } - } else if (magnitudeX > magnitudeY) { - // user is perhaps trying to swipe left/right across the page - - if (((deltaX < 0) && (scrollLeft === i.contentWidth - i.containerWidth)) || - ((deltaX > 0) && (scrollLeft === 0))) { - return !i.settings.swipePropagation; - } - } - - return true; - } - - function bindMouseWheelHandler() { - var shouldPrevent = false; - - function getDeltaFromEvent(e) { - var deltaX = e.deltaX; - var deltaY = -1 * e.deltaY; - - if (typeof deltaX === "undefined" || typeof deltaY === "undefined") { - // OS X Safari - deltaX = -1 * e.wheelDeltaX / 6; - deltaY = e.wheelDeltaY / 6; - } - - if (e.deltaMode && e.deltaMode === 1) { - // Firefox in deltaMode 1: Line scrolling - deltaX *= 10; - deltaY *= 10; - } - - if (deltaX !== deltaX && deltaY !== deltaY/* NaN checks */) { - // IE in some mouse drivers - deltaX = 0; - deltaY = e.wheelDelta; - } - - return [deltaX, deltaY]; - } - - function mousewheelHandler(e) { - // FIXME: this is a quick fix for the select problem in FF and IE. - // If there comes an effective way to deal with the problem, - // this lines should be removed. - if (!h.env.isWebKit && element.querySelector('select:focus')) { - return; - } - - var delta = getDeltaFromEvent(e); - - var deltaX = delta[0]; - var deltaY = delta[1]; - - shouldPrevent = false; - if (!i.settings.useBothWheelAxes) { - // deltaX will only be used for horizontal scrolling and deltaY will - // only be used for vertical scrolling - this is the default - element.scrollTop = element.scrollTop - (deltaY * i.settings.wheelSpeed); - element.scrollLeft = element.scrollLeft + (deltaX * i.settings.wheelSpeed); - } else if (i.scrollbarYActive && !i.scrollbarXActive) { - // only vertical scrollbar is active and useBothWheelAxes option is - // active, so let's scroll vertical bar using both mouse wheel axes - if (deltaY) { - element.scrollTop = element.scrollTop - (deltaY * i.settings.wheelSpeed); - } else { - element.scrollTop = element.scrollTop + (deltaX * i.settings.wheelSpeed); - } - shouldPrevent = true; - } else if (i.scrollbarXActive && !i.scrollbarYActive) { - // useBothWheelAxes and only horizontal bar is active, so use both - // wheel axes for horizontal bar - if (deltaX) { - element.scrollLeft = element.scrollLeft + (deltaX * i.settings.wheelSpeed); - } else { - element.scrollLeft = element.scrollLeft - (deltaY * i.settings.wheelSpeed); - } - shouldPrevent = true; - } - - updateGeometry(element); - - shouldPrevent = (shouldPrevent || shouldPreventWheel(deltaX, deltaY)); - if (shouldPrevent) { - e.stopPropagation(); - e.preventDefault(); - } - } - - if (typeof window.onwheel !== "undefined") { - i.event.bind(element, 'wheel', mousewheelHandler); - } else if (typeof window.onmousewheel !== "undefined") { - i.event.bind(element, 'mousewheel', mousewheelHandler); - } - } - - function bindKeyboardHandler() { - var hovered = false; - i.event.bind(element, 'mouseenter', function () { - hovered = true; - }); - i.event.bind(element, 'mouseleave', function () { - hovered = false; - }); - - var shouldPrevent = false; - i.event.bind(i.ownerDocument, 'keydown', function (e) { - if (e.isDefaultPrevented && e.isDefaultPrevented()) { - return; - } - - if (!hovered) { - return; - } - - var activeElement = document.activeElement ? document.activeElement : i.ownerDocument.activeElement; - // go deeper if element is a webcomponent - while (activeElement.shadowRoot) { - activeElement = activeElement.shadowRoot.activeElement; - } - if (h.isEditable(activeElement)) { - return; - } - - var deltaX = 0; - var deltaY = 0; - - switch (e.which) { - case 37: // left - deltaX = -30; - break; - case 38: // up - deltaY = 30; - break; - case 39: // right - deltaX = 30; - break; - case 40: // down - deltaY = -30; - break; - case 33: // page up - deltaY = 90; - break; - case 32: // space bar - case 34: // page down - deltaY = -90; - break; - case 35: // end - if (e.ctrlKey) { - deltaY = -i.contentHeight; - } else { - deltaY = -i.containerHeight; - } - break; - case 36: // home - if (e.ctrlKey) { - deltaY = element.scrollTop; - } else { - deltaY = i.containerHeight; - } - break; - default: - return; - } - - element.scrollTop = element.scrollTop - deltaY; - element.scrollLeft = element.scrollLeft + deltaX; - - shouldPrevent = shouldPreventWheel(deltaX, deltaY); - if (shouldPrevent) { - e.preventDefault(); - } - }); - } - - function bindRailClickHandler() { - var stopPropagation = window.Event.prototype.stopPropagation.bind; - - i.event.bind(i.scrollbarY, 'click', stopPropagation); - i.event.bind(i.scrollbarYRail, 'click', function (e) { - var halfOfScrollbarLength = h.toInt(i.scrollbarYHeight / 2); - var positionTop = e.pageY - i.scrollbarYRail.offsetTop - halfOfScrollbarLength; - var maxPositionTop = i.containerHeight - i.scrollbarYHeight; - var positionRatio = positionTop / maxPositionTop; - - if (positionRatio < 0) { - positionRatio = 0; - } else if (positionRatio > 1) { - positionRatio = 1; - } - - element.scrollTop = (i.contentHeight - i.containerHeight) * positionRatio; - }); - - i.event.bind(i.scrollbarX, 'click', stopPropagation); - i.event.bind(i.scrollbarXRail, 'click', function (e) { - var halfOfScrollbarLength = h.toInt(i.scrollbarXWidth / 2); - var positionLeft = e.pageX - i.scrollbarXRail.offsetLeft - halfOfScrollbarLength; - var maxPositionLeft = i.containerWidth - i.scrollbarXWidth; - var positionRatio = positionLeft / maxPositionLeft; - - if (positionRatio < 0) { - positionRatio = 0; - } else if (positionRatio > 1) { - positionRatio = 1; - } - - element.scrollLeft = (i.contentWidth - i.containerWidth) * positionRatio; - }); - } - - function bindSelectionHandler() { - function getRangeNode() { - var selection = window.getSelection ? window.getSelection() : - document.getSlection ? document.getSlection() : {rangeCount: 0}; - if (selection.rangeCount === 0) { - return null; - } else { - return selection.getRangeAt(0).commonAncestorContainer; - } - } - - var scrollingLoop = null; - var scrollDiff = {top: 0, left: 0}; - function startScrolling() { - if (!scrollingLoop) { - scrollingLoop = setInterval(function () { - if (!instances.get(element)) { - clearInterval(scrollingLoop); - return; - } - - element.scrollTop = element.scrollTop + scrollDiff.top; - element.scrollLeft = element.scrollLeft + scrollDiff.left; - updateGeometry(element); - }, 50); // every .1 sec - } - } - function stopScrolling() { - if (scrollingLoop) { - clearInterval(scrollingLoop); - scrollingLoop = null; - } - cls.remove(element, 'ps-in-scrolling'); - cls.remove(element, 'ps-in-scrolling'); - } - - var isSelected = false; - i.event.bind(i.ownerDocument, 'selectionchange', function () { - if (element.contains(getRangeNode())) { - isSelected = true; - } else { - isSelected = false; - stopScrolling(); - } - }); - i.event.bind(window, 'mouseup', function () { - if (isSelected) { - isSelected = false; - stopScrolling(); - } - }); - - i.event.bind(window, 'mousemove', function (e) { - if (isSelected) { - var mousePosition = {x: e.pageX, y: e.pageY}; - var containerGeometry = { - left: element.offsetLeft, - right: element.offsetLeft + element.offsetWidth, - top: element.offsetTop, - bottom: element.offsetTop + element.offsetHeight - }; - - if (mousePosition.x < containerGeometry.left + 3) { - scrollDiff.left = -5; - cls.add(element, 'ps-in-scrolling'); - } else if (mousePosition.x > containerGeometry.right - 3) { - scrollDiff.left = 5; - cls.add(element, 'ps-in-scrolling'); - } else { - scrollDiff.left = 0; - } - - if (mousePosition.y < containerGeometry.top + 3) { - if (containerGeometry.top + 3 - mousePosition.y < 5) { - scrollDiff.top = -5; - } else { - scrollDiff.top = -20; - } - cls.add(element, 'ps-in-scrolling'); - } else if (mousePosition.y > containerGeometry.bottom - 3) { - if (mousePosition.y - containerGeometry.bottom + 3 < 5) { - scrollDiff.top = 5; - } else { - scrollDiff.top = 20; - } - cls.add(element, 'ps-in-scrolling'); - } else { - scrollDiff.top = 0; - } - - if (scrollDiff.top === 0 && scrollDiff.left === 0) { - stopScrolling(); - } else { - startScrolling(); - } - } - }); - } - - function bindTouchHandler(supportsTouch, supportsIePointer) { - function applyTouchMove(differenceX, differenceY) { - element.scrollTop = element.scrollTop - differenceY; - element.scrollLeft = element.scrollLeft() - differenceX; - - updateGeometry(element); - } - - var startOffset = {}; - var startTime = 0; - var speed = {}; - var easingLoop = null; - var inGlobalTouch = false; - var inLocalTouch = false; - - function globalTouchStart() { - inGlobalTouch = true; - } - function globalTouchEnd() { - inGlobalTouch = false; - } - - function getTouch(e) { - if (e.targetTouches) { - return e.targetTouches[0]; - } else { - // Maybe IE pointer - return e; - } - } - function shouldHandle(e) { - if (e.targetTouches && e.targetTouches.length === 1) { - return true; - } - if (e.pointerType && e.pointerType !== 'mouse' && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) { - return true; - } - return false; - } - function touchStart(e) { - if (shouldHandle(e)) { - inLocalTouch = true; - - var touch = getTouch(e); - - startOffset.pageX = touch.pageX; - startOffset.pageY = touch.pageY; - - startTime = (new Date()).getTime(); - - if (easingLoop !== null) { - clearInterval(easingLoop); - } - - e.stopPropagation(); - } - } - function touchMove(e) { - if (!inGlobalTouch && inLocalTouch && shouldHandle(e)) { - var touch = getTouch(e); - - var currentOffset = {pageX: touch.pageX, pageY: touch.pageY}; - - var differenceX = currentOffset.pageX - startOffset.pageX; - var differenceY = currentOffset.pageY - startOffset.pageY; - - applyTouchMove(differenceX, differenceY); - startOffset = currentOffset; - - var currentTime = (new Date()).getTime(); - - var timeGap = currentTime - startTime; - if (timeGap > 0) { - speed.x = differenceX / timeGap; - speed.y = differenceY / timeGap; - startTime = currentTime; - } - - if (shouldPreventSwipe(differenceX, differenceY)) { - e.stopPropagation(); - e.preventDefault(); - } - } - } - function touchEnd() { - if (!inGlobalTouch && inLocalTouch) { - inLocalTouch = false; - - clearInterval(easingLoop); - easingLoop = setInterval(function () { - if (!instances.get(element)) { - clearInterval(easingLoop); - return; - } - - if (Math.abs(speed.x) < 0.01 && Math.abs(speed.y) < 0.01) { - clearInterval(easingLoop); - return; - } - - applyTouchMove(speed.x * 30, speed.y * 30); - - speed.x *= 0.8; - speed.y *= 0.8; - }, 10); - } - } - - if (supportsTouch) { - i.event.bind(window, 'touchstart', globalTouchStart); - i.event.bind(window, 'touchend', globalTouchEnd); - i.event.bind(element, 'touchstart', touchStart); - i.event.bind(element, 'touchmove', touchMove); - i.event.bind(element, 'touchend', touchEnd); - } - - if (supportsIePointer) { - if (window.PointerEvent) { - i.event.bind(window, 'pointerdown', globalTouchStart); - i.event.bind(window, 'pointerup', globalTouchEnd); - i.event.bind(element, 'pointerdown', touchStart); - i.event.bind(element, 'pointermove', touchMove); - i.event.bind(element, 'pointerup', touchEnd); - } else if (window.MSPointerEvent) { - i.event.bind(window, 'MSPointerDown', globalTouchStart); - i.event.bind(window, 'MSPointerUp', globalTouchEnd); - i.event.bind(element, 'MSPointerDown', touchStart); - i.event.bind(element, 'MSPointerMove', touchMove); - i.event.bind(element, 'MSPointerUp', touchEnd); - } - } - } - - function bindScrollHandler() { - i.event.bind(element, 'scroll', function () { - updateGeometry(element); - }); - } - - function initialize() { - updateGeometry(element); - bindScrollHandler(); - bindMouseScrollXHandler(); - bindMouseScrollYHandler(); - bindRailClickHandler(); - bindSelectionHandler(); - bindMouseWheelHandler(); - - if (h.env.supportsTouch || h.env.supportsIePointer) { - bindTouchHandler(h.env.supportsTouch, h.env.supportsIePointer); - } - if (i.settings.useKeyboard) { - bindKeyboardHandler(); - } - } - - initialize(); + updateGeometry(element); };