UNPKG

iscroll

Version:

Smooth scrolling for the web

444 lines (367 loc) 12.6 kB
function createDefaultScrollbar (direction, interactive, type) { var scrollbar = document.createElement('div'), indicator = document.createElement('div'); if ( type === true ) { scrollbar.style.cssText = 'position:absolute;z-index:9999'; indicator.style.cssText = '-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;position:absolute;background:rgba(0,0,0,0.5);border:1px solid rgba(255,255,255,0.9);border-radius:3px'; } indicator.className = 'iScrollIndicator'; if ( direction == 'h' ) { if ( type === true ) { scrollbar.style.cssText += ';height:7px;left:2px;right:2px;bottom:0'; indicator.style.height = '100%'; } scrollbar.className = 'iScrollHorizontalScrollbar'; } else { if ( type === true ) { scrollbar.style.cssText += ';width:7px;bottom:2px;top:2px;right:1px'; indicator.style.width = '100%'; } scrollbar.className = 'iScrollVerticalScrollbar'; } scrollbar.style.cssText += ';overflow:hidden'; if ( !interactive ) { scrollbar.style.pointerEvents = 'none'; } scrollbar.appendChild(indicator); return scrollbar; } function Indicator (scroller, options) { this.wrapper = typeof options.el == 'string' ? document.querySelector(options.el) : options.el; this.wrapperStyle = this.wrapper.style; this.indicator = this.wrapper.children[0]; this.indicatorStyle = this.indicator.style; this.scroller = scroller; this.options = { listenX: true, listenY: true, interactive: false, resize: true, defaultScrollbars: false, shrink: false, fade: false, speedRatioX: 0, speedRatioY: 0 }; for ( var i in options ) { this.options[i] = options[i]; } this.sizeRatioX = 1; this.sizeRatioY = 1; this.maxPosX = 0; this.maxPosY = 0; if ( this.options.interactive ) { if ( !this.options.disableTouch ) { utils.addEvent(this.indicator, 'touchstart', this); utils.addEvent(window, 'touchend', this); } if ( !this.options.disablePointer ) { utils.addEvent(this.indicator, utils.prefixPointerEvent('pointerdown'), this); utils.addEvent(window, utils.prefixPointerEvent('pointerup'), this); } if ( !this.options.disableMouse ) { utils.addEvent(this.indicator, 'mousedown', this); utils.addEvent(window, 'mouseup', this); } } if ( this.options.fade ) { this.wrapperStyle[utils.style.transform] = this.scroller.translateZ; var durationProp = utils.style.transitionDuration; this.wrapperStyle[durationProp] = utils.isBadAndroid ? '0.0001ms' : '0ms'; // remove 0.0001ms var self = this; if(utils.isBadAndroid) { rAF(function() { if(self.wrapperStyle[durationProp] === '0.0001ms') { self.wrapperStyle[durationProp] = '0s'; } }); } this.wrapperStyle.opacity = '0'; } } Indicator.prototype = { handleEvent: function (e) { switch ( e.type ) { case 'touchstart': case 'pointerdown': case 'MSPointerDown': case 'mousedown': this._start(e); break; case 'touchmove': case 'pointermove': case 'MSPointerMove': case 'mousemove': this._move(e); break; case 'touchend': case 'pointerup': case 'MSPointerUp': case 'mouseup': case 'touchcancel': case 'pointercancel': case 'MSPointerCancel': case 'mousecancel': this._end(e); break; } }, destroy: function () { if ( this.options.fadeScrollbars ) { clearTimeout(this.fadeTimeout); this.fadeTimeout = null; } if ( this.options.interactive ) { utils.removeEvent(this.indicator, 'touchstart', this); utils.removeEvent(this.indicator, utils.prefixPointerEvent('pointerdown'), this); utils.removeEvent(this.indicator, 'mousedown', this); utils.removeEvent(window, 'touchmove', this); utils.removeEvent(window, utils.prefixPointerEvent('pointermove'), this); utils.removeEvent(window, 'mousemove', this); utils.removeEvent(window, 'touchend', this); utils.removeEvent(window, utils.prefixPointerEvent('pointerup'), this); utils.removeEvent(window, 'mouseup', this); } if ( this.options.defaultScrollbars ) { this.wrapper.parentNode.removeChild(this.wrapper); } }, _start: function (e) { var point = e.touches ? e.touches[0] : e; e.preventDefault(); e.stopPropagation(); this.transitionTime(); this.initiated = true; this.moved = false; this.lastPointX = point.pageX; this.lastPointY = point.pageY; this.startTime = utils.getTime(); if ( !this.options.disableTouch ) { utils.addEvent(window, 'touchmove', this); } if ( !this.options.disablePointer ) { utils.addEvent(window, utils.prefixPointerEvent('pointermove'), this); } if ( !this.options.disableMouse ) { utils.addEvent(window, 'mousemove', this); } this.scroller._execEvent('beforeScrollStart'); }, _move: function (e) { var point = e.touches ? e.touches[0] : e, deltaX, deltaY, newX, newY, timestamp = utils.getTime(); if ( !this.moved ) { this.scroller._execEvent('scrollStart'); } this.moved = true; deltaX = point.pageX - this.lastPointX; this.lastPointX = point.pageX; deltaY = point.pageY - this.lastPointY; this.lastPointY = point.pageY; newX = this.x + deltaX; newY = this.y + deltaY; this._pos(newX, newY); // INSERT POINT: indicator._move e.preventDefault(); e.stopPropagation(); }, _end: function (e) { if ( !this.initiated ) { return; } this.initiated = false; e.preventDefault(); e.stopPropagation(); utils.removeEvent(window, 'touchmove', this); utils.removeEvent(window, utils.prefixPointerEvent('pointermove'), this); utils.removeEvent(window, 'mousemove', this); if ( this.scroller.options.snap ) { var snap = this.scroller._nearestSnap(this.scroller.x, this.scroller.y); var time = this.options.snapSpeed || Math.max( Math.max( Math.min(Math.abs(this.scroller.x - snap.x), 1000), Math.min(Math.abs(this.scroller.y - snap.y), 1000) ), 300); if ( this.scroller.x != snap.x || this.scroller.y != snap.y ) { this.scroller.directionX = 0; this.scroller.directionY = 0; this.scroller.currentPage = snap; this.scroller.scrollTo(snap.x, snap.y, time, this.scroller.options.bounceEasing); } } if ( this.moved ) { this.scroller._execEvent('scrollEnd'); } }, transitionTime: function (time) { time = time || 0; var durationProp = utils.style.transitionDuration; this.indicatorStyle[durationProp] = time + 'ms'; if ( !time && utils.isBadAndroid ) { this.indicatorStyle[durationProp] = '0.0001ms'; // remove 0.0001ms var self = this; rAF(function() { if(self.indicatorStyle[durationProp] === '0.0001ms') { self.indicatorStyle[durationProp] = '0s'; } }); } }, transitionTimingFunction: function (easing) { this.indicatorStyle[utils.style.transitionTimingFunction] = easing; }, refresh: function () { this.transitionTime(); if ( this.options.listenX && !this.options.listenY ) { this.indicatorStyle.display = this.scroller.hasHorizontalScroll ? 'block' : 'none'; } else if ( this.options.listenY && !this.options.listenX ) { this.indicatorStyle.display = this.scroller.hasVerticalScroll ? 'block' : 'none'; } else { this.indicatorStyle.display = this.scroller.hasHorizontalScroll || this.scroller.hasVerticalScroll ? 'block' : 'none'; } if ( this.scroller.hasHorizontalScroll && this.scroller.hasVerticalScroll ) { utils.addClass(this.wrapper, 'iScrollBothScrollbars'); utils.removeClass(this.wrapper, 'iScrollLoneScrollbar'); if ( this.options.defaultScrollbars && this.options.customStyle ) { if ( this.options.listenX ) { this.wrapper.style.right = '8px'; } else { this.wrapper.style.bottom = '8px'; } } } else { utils.removeClass(this.wrapper, 'iScrollBothScrollbars'); utils.addClass(this.wrapper, 'iScrollLoneScrollbar'); if ( this.options.defaultScrollbars && this.options.customStyle ) { if ( this.options.listenX ) { this.wrapper.style.right = '2px'; } else { this.wrapper.style.bottom = '2px'; } } } var r = this.wrapper.offsetHeight; // force refresh if ( this.options.listenX ) { this.wrapperWidth = this.wrapper.clientWidth; if ( this.options.resize ) { this.indicatorWidth = Math.max(Math.round(this.wrapperWidth * this.wrapperWidth / (this.scroller.scrollerWidth || this.wrapperWidth || 1)), 8); this.indicatorStyle.width = this.indicatorWidth + 'px'; } else { this.indicatorWidth = this.indicator.clientWidth; } this.maxPosX = this.wrapperWidth - this.indicatorWidth; if ( this.options.shrink == 'clip' ) { this.minBoundaryX = -this.indicatorWidth + 8; this.maxBoundaryX = this.wrapperWidth - 8; } else { this.minBoundaryX = 0; this.maxBoundaryX = this.maxPosX; } this.sizeRatioX = this.options.speedRatioX || (this.scroller.maxScrollX && (this.maxPosX / this.scroller.maxScrollX)); } if ( this.options.listenY ) { this.wrapperHeight = this.wrapper.clientHeight; if ( this.options.resize ) { this.indicatorHeight = Math.max(Math.round(this.wrapperHeight * this.wrapperHeight / (this.scroller.scrollerHeight || this.wrapperHeight || 1)), 8); this.indicatorStyle.height = this.indicatorHeight + 'px'; } else { this.indicatorHeight = this.indicator.clientHeight; } this.maxPosY = this.wrapperHeight - this.indicatorHeight; if ( this.options.shrink == 'clip' ) { this.minBoundaryY = -this.indicatorHeight + 8; this.maxBoundaryY = this.wrapperHeight - 8; } else { this.minBoundaryY = 0; this.maxBoundaryY = this.maxPosY; } this.maxPosY = this.wrapperHeight - this.indicatorHeight; this.sizeRatioY = this.options.speedRatioY || (this.scroller.maxScrollY && (this.maxPosY / this.scroller.maxScrollY)); } this.updatePosition(); }, updatePosition: function () { var x = this.options.listenX && Math.round(this.sizeRatioX * this.scroller.x) || 0, y = this.options.listenY && Math.round(this.sizeRatioY * this.scroller.y) || 0; if ( !this.options.ignoreBoundaries ) { if ( x < this.minBoundaryX ) { if ( this.options.shrink == 'scale' ) { this.width = Math.max(this.indicatorWidth + x, 8); this.indicatorStyle.width = this.width + 'px'; } x = this.minBoundaryX; } else if ( x > this.maxBoundaryX ) { if ( this.options.shrink == 'scale' ) { this.width = Math.max(this.indicatorWidth - (x - this.maxPosX), 8); this.indicatorStyle.width = this.width + 'px'; x = this.maxPosX + this.indicatorWidth - this.width; } else { x = this.maxBoundaryX; } } else if ( this.options.shrink == 'scale' && this.width != this.indicatorWidth ) { this.width = this.indicatorWidth; this.indicatorStyle.width = this.width + 'px'; } if ( y < this.minBoundaryY ) { if ( this.options.shrink == 'scale' ) { this.height = Math.max(this.indicatorHeight + y * 3, 8); this.indicatorStyle.height = this.height + 'px'; } y = this.minBoundaryY; } else if ( y > this.maxBoundaryY ) { if ( this.options.shrink == 'scale' ) { this.height = Math.max(this.indicatorHeight - (y - this.maxPosY) * 3, 8); this.indicatorStyle.height = this.height + 'px'; y = this.maxPosY + this.indicatorHeight - this.height; } else { y = this.maxBoundaryY; } } else if ( this.options.shrink == 'scale' && this.height != this.indicatorHeight ) { this.height = this.indicatorHeight; this.indicatorStyle.height = this.height + 'px'; } } this.x = x; this.y = y; if ( this.scroller.options.useTransform ) { this.indicatorStyle[utils.style.transform] = 'translate(' + x + 'px,' + y + 'px)' + this.scroller.translateZ; } else { this.indicatorStyle.left = x + 'px'; this.indicatorStyle.top = y + 'px'; } }, _pos: function (x, y) { if ( x < 0 ) { x = 0; } else if ( x > this.maxPosX ) { x = this.maxPosX; } if ( y < 0 ) { y = 0; } else if ( y > this.maxPosY ) { y = this.maxPosY; } x = this.options.listenX ? Math.round(x / this.sizeRatioX) : this.scroller.x; y = this.options.listenY ? Math.round(y / this.sizeRatioY) : this.scroller.y; this.scroller.scrollTo(x, y); }, fade: function (val, hold) { if ( hold && !this.visible ) { return; } clearTimeout(this.fadeTimeout); this.fadeTimeout = null; var time = val ? 250 : 500, delay = val ? 0 : 300; val = val ? '1' : '0'; this.wrapperStyle[utils.style.transitionDuration] = time + 'ms'; this.fadeTimeout = setTimeout((function (val) { this.wrapperStyle.opacity = val; this.visible = +val; }).bind(this, val), delay); } };