/*! iScroll v5.1.2 ~ (c) 2008-2014 Matteo Spinelli ~ http://cubiq.org/license */ (function (window, document, Math) { //請求動畫幀 var rAF = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) { window.setTimeout(callback, 1000 / 60); }; //顯示器16.7ms刷新間隔以前發生了其餘繪製請求(setTimeout),致使全部幀丟失,setTimeout的定時器值推薦最小使用16.7ms的緣由(16.7 = 1000 / 60, 即每秒60幀) //requestAnimationFrame與setTimeout的不一樣在於,前者跟着瀏覽器重繪時間走,不存在後者丟幀的問題 var utils = (function () { var me = {}; var _elementStyle = document.createElement('div').style; //獲取瀏覽器特性前綴 var _vendor = (function () { var vendors = ['t', 'webkitT', 'MozT', 'msT', 'OT'], transform, i = 0, l = vendors.length; for ( ; i < l; i++ ) { transform = vendors[i] + 'ransform'; if ( transform in _elementStyle ) return vendors[i].substr(0, vendors[i].length-1); } return false; })(); //拼裝樣式屬性名 function _prefixStyle (style) { if ( _vendor === false ) return false; if ( _vendor === '' ) return style; return _vendor + style.charAt(0).toUpperCase() + style.substr(1); } //獲取當前時間 me.getTime = Date.now || function getTime () { return new Date().getTime(); }; //對象複製 me.extend = function (target, obj) { for ( var i in obj ) { target[i] = obj[i]; } }; //事件 me.addEvent = function (el, type, fn, capture) { el.addEventListener(type, fn, !!capture); }; me.removeEvent = function (el, type, fn, capture) { el.removeEventListener(type, fn, !!capture); }; //MSPointerEvent 指針事件是一些事件和相關接口,用於處理來自鼠標、手寫筆或觸摸屏等設備的硬件不可知的指針輸入,IE10引入,IE11去掉前綴,其餘瀏覽器都不支持指針事件。 me.prefixPointerEvent = function (pointerEvent) { return window.MSPointerEvent ? 'MSPointer' + pointerEvent.charAt(9).toUpperCase() + pointerEvent.substr(10): pointerEvent; }; //deceleration減速 /** * 根據咱們的拖動返回運動的長度與耗時,用於慣性拖動判斷 * @param current 當前鼠標位置 * @param start touchStart時候記錄的Y(多是X)的開始位置,可是在touchmove時候可能被重寫 * @param time touchstart到手指離開時候經歷的時間,一樣可能被touchmove重寫 * @param lowerMargin y可移動的最大距離,這個通常爲計算得出 this.wrapperHeight - this.scrollerHeight * @param wrapperSize 若是有邊界距離的話就是可拖動,否則碰到0的時候便中止 * @param deceleration 勻減速 * @returns {{destination: number, duration: number}} */ me.momentum = function (current, start, time, lowerMargin, wrapperSize, deceleration) { var distance = current - start, speed = Math.abs(distance) / time, destination, duration; //減速變量 deceleration = deceleration === undefined ? 0.0006 : deceleration; //減速路程 destination = current + ( speed * speed ) / ( 2 * deceleration ) * ( distance < 0 ? -1 : 1 ); //持續時間 duration = speed / deceleration; if ( destination < lowerMargin ) { destination = wrapperSize ? lowerMargin - ( wrapperSize / 2.5 * ( speed / 8 ) ) : lowerMargin; distance = Math.abs(destination - current); duration = distance / speed; } else if ( destination > 0 ) { destination = wrapperSize ? wrapperSize / 2.5 * ( speed / 8 ) : 0; distance = Math.abs(current) + destination; duration = distance / speed; } return { destination: Math.round(destination), duration: duration }; }; var _transform = _prefixStyle('transform'); me.extend(me, { hasTransform: _transform !== false, hasPerspective: _prefixStyle('perspective') in _elementStyle, //透視 hasTouch: 'ontouchstart' in window, hasPointer: window.PointerEvent || window.MSPointerEvent, // IE10 is prefixed hasTransition: _prefixStyle('transition') in _elementStyle }); // This should find all Android browsers lower than build 535.19 (both stock browser and webview) 低版本安卓機 me.isBadAndroid = /Android /.test(window.navigator.appVersion) && !(/Chrome\/\d/.test(window.navigator.appVersion)); me.extend(me.style = {}, { transform: _transform, transitionTimingFunction: _prefixStyle('transitionTimingFunction'), transitionDuration: _prefixStyle('transitionDuration'), transitionDelay: _prefixStyle('transitionDelay'), transformOrigin: _prefixStyle('transformOrigin') }); me.hasClass = function (e, c) { var re = new RegExp("(^|\\s)" + c + "(\\s|$)"); return re.test(e.className); }; me.addClass = function (e, c) { if ( me.hasClass(e, c) ) { return; } var newclass = e.className.split(' '); newclass.push(c); e.className = newclass.join(' '); }; me.removeClass = function (e, c) { if ( !me.hasClass(e, c) ) { return; } var re = new RegExp("(^|\\s)" + c + "(\\s|$)", 'g'); e.className = e.className.replace(re, ' '); }; me.offset = function (el) { var left = -el.offsetLeft, top = -el.offsetTop; // jshint -W084 while (el = el.offsetParent) { left -= el.offsetLeft; top -= el.offsetTop; } // jshint +W084 return { left: left, top: top }; }; // 配合config裏面的preventDefaultException屬性,不對匹配到的element使用e.preventDefault()。 me.preventDefaultException = function (el, exceptions) { for ( var i in exceptions ) { if ( exceptions[i].test(el[i]) ) { return true; } } return false; }; // 事件類型 me.extend(me.eventType = {}, { touchstart: 1, touchmove: 1, touchend: 1, mousedown: 2, mousemove: 2, mouseup: 2, pointerdown: 3, pointermove: 3, pointerup: 3, MSPointerDown: 3, MSPointerMove: 3, MSPointerUp: 3 }); // 緩動函數 me.extend(me.ease = {}, { quadratic: { style: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)', fn: function (k) { return k * ( 2 - k ); } }, circular: { style: 'cubic-bezier(0.1, 0.57, 0.1, 1)', // Not properly "circular" but this looks better, it should be (0.075, 0.82, 0.165, 1) fn: function (k) { return Math.sqrt( 1 - ( --k * k ) ); } }, back: { style: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)', fn: function (k) { var b = 4; return ( k = k - 1 ) * k * ( ( b + 1 ) * k + b ) + 1; } }, bounce: { style: '', fn: function (k) { if ( ( k /= 1 ) < ( 1 / 2.75 ) ) { return 7.5625 * k * k; } else if ( k < ( 2 / 2.75 ) ) { return 7.5625 * ( k -= ( 1.5 / 2.75 ) ) * k + 0.75; } else if ( k < ( 2.5 / 2.75 ) ) { return 7.5625 * ( k -= ( 2.25 / 2.75 ) ) * k + 0.9375; } else { return 7.5625 * ( k -= ( 2.625 / 2.75 ) ) * k + 0.984375; } } }, elastic: { style: '', fn: function (k) { var f = 0.22, e = 0.4; if ( k === 0 ) { return 0; } if ( k == 1 ) { return 1; } return ( e * Math.pow( 2, - 10 * k ) * Math.sin( ( k - f / 4 ) * ( 2 * Math.PI ) / f ) + 1 ); } } }); me.tap = function (e, eventName) { var ev = document.createEvent('Event'); /** 只有在新建立的 Event 對象被 Document 對象或 Element 對象的 dispatchEvent() 方法分派以前,才能調用 Event.initEvent() 方法 初始化新事件對象的屬性 */ ev.initEvent(eventName, true, true); ev.pageX = e.pageX; ev.pageY = e.pageY; //事件觸發器就是用來觸發某個元素下的某個事件 e.target.dispatchEvent(ev); }; me.click = function (e) { var target = e.target, ev; //不是select/input/textarea就建立事件 if ( !(/(SELECT|INPUT|TEXTAREA)/i).test(target.tagName) ) { ev = document.createEvent('MouseEvents'); ev.initMouseEvent('click', true, true, e.view, 1, target.screenX, target.screenY, target.clientX, target.clientY, e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, 0, null); ev._constructed = true; target.dispatchEvent(ev); } }; return me; })(); function IScroll (el, options) { //傳入的元素 必需要有子元素 this.wrapper = typeof el == 'string' ? document.querySelector(el) : el; //子元素 實際滑動元素 this.scroller = this.wrapper.children[0]; this.scrollerStyle = this.scroller.style; // cache style for better performance this.options = { // INSERT POINT: OPTIONS startX: 0, startY: 0, //默認是Y軸上下滾動 scrollY: true, //方向鎖定閾值,好比用戶點擊屏幕後,x與y之間差距大於5px,判斷用戶的拖動意圖,是x方向拖動仍是y方向 directionLockThreshold: 5, //是否有慣性緩衝動畫 momentum: true, //超出邊界時候是否還能拖動 bounce: true, //超出邊界還原時間點 bounceTime: 600, //超出邊界返回的動畫 bounceEasing: '', //是否阻止默認滾動事件 preventDefault: true, //當遇到表單元素則不阻止冒泡,而是彈出系統自帶相應的輸入控件 preventDefaultException: { tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT)$/ }, //硬件合成 經過GPU作圖形渲染 HWCompositing: true, //使用transition useTransition: true, //使用transform useTransform: true }; for ( var i in options ) { this.options[i] = options[i]; } // Normalize options this.translateZ = this.options.HWCompositing && utils.hasPerspective ? ' translateZ(0)' : ''; this.options.useTransition = utils.hasTransition && this.options.useTransition; this.options.useTransform = utils.hasTransform && this.options.useTransform; // 默認false,當你想保留原生垂直滾動但可以添加水平iscroll時候,設置爲true,iscroll區域水平滑動時候的touch垂直移動,不會觸發原生的垂直滑動 // 一般設置爲:{eventPassthrough:true,scrollX:true,scrollY:false} // this.options.eventPassthrough = this.options.eventPassthrough === true ? 'vertical' : this.options.eventPassthrough; //當設置了eventPassthrough時候,不阻止默認的滾動事件 this.options.preventDefault = !this.options.eventPassthrough && this.options.preventDefault; // If you want eventPassthrough I have to lock one of the axes // eventPassthrough能夠傳入具體名字(vertical/horizontal),這裏更好說明了eventPassthrough的做用,對於傳入的值來阻止相應軸的滑動事件 // eventPassthrough -> true -> vertical -> scrollY : false 垂直滾動忽略 this.options.scrollY = this.options.eventPassthrough == 'vertical' ? false : this.options.scrollY; this.options.scrollX = this.options.eventPassthrough == 'horizontal' ? false : this.options.scrollX; // With eventPassthrough we also need lockDirection mechanism // freeScroll只能與scrollX一塊兒使用 {scrollX:true,freeScroll:true} this.options.freeScroll = this.options.freeScroll && !this.options.eventPassthrough; this.options.directionLockThreshold = this.options.eventPassthrough ? 0 : this.options.directionLockThreshold; // 緩動函數 this.options.bounceEasing = typeof this.options.bounceEasing == 'string' ? utils.ease[this.options.bounceEasing] || utils.ease.circular : this.options.bounceEasing; //window resize時從新獲取位置 this.options.resizePolling = this.options.resizePolling === undefined ? 60 : this.options.resizePolling; if ( this.options.tap === true ) { this.options.tap = 'tap'; } // INSERT POINT: NORMALIZATION // Some defaults this.x = 0; this.y = 0; this.directionX = 0; this.directionY = 0; this._events = {}; // INSERT POINT: DEFAULTS // IScroll初始化 this._init(); // this.refresh(); // 定位到最頂最左 this.scrollTo(this.options.startX, this.options.startY); // 表明「啓用」的一個標誌 this.enable(); } IScroll.prototype = { version: '5.1.2', _init: function () { this._initEvents(); // INSERT POINT: _init }, destroy: function () { this._initEvents(true); this._execEvent('destroy'); }, _transitionEnd: function (e) { if ( e.target != this.scroller || !this.isInTransition ) { return; } this._transitionTime(); if ( !this.resetPosition(this.options.bounceTime) ) { this.isInTransition = false; this._execEvent('scrollEnd'); } }, _start: function (e) { // React to left mouse button only // 不是已經註冊的事件,並且按下的不是左鍵,就返回不處理。 if ( utils.eventType[e.type] != 1 ) { if ( e.button !== 0 ) { return; } } // 沒有啓用,返回不處理。 if ( !this.enabled || (this.initiated && utils.eventType[e.type] !== this.initiated) ) { return; } // 若是 preventDefault === true 且 不是落後的安卓版本 且 不是須要過濾的target 就 阻止默認的行爲 if ( this.options.preventDefault && !utils.isBadAndroid && !utils.preventDefaultException(e.target, this.options.preventDefaultException) ) { e.preventDefault(); } //e.touches表示的在屏幕上全部的觸摸點,但事實上,絕大數手機瀏覽器並不支持多點觸摸,全部用e.touchees[0]捕獲一個觸點就滿足吧, //不要再奢望獲取e.touches[>0]了,這個觸點的位置能夠有e.touches[0].pageX獲取頁面x座標(像素); var point = e.touches ? e.touches[0] : e, pos; // 事件類型 this.initiated = utils.eventType[e.type]; this.moved = false; //是否移動的標誌 this.distX = 0; // this.distY = 0; // this.directionX = 0; //x方向移動數 this.directionY = 0; //y方向移動數 this.directionLocked = 0; //方向鎖 // 設置運動時間 this._transitionTime(); // 開始時間 this.startTime = utils.getTime(); // 若是還在運動中 if ( this.options.useTransition && this.isInTransition ) { this.isInTransition = false; // 獲取當前位置 pos = this.getComputedPosition(); // 滑動到當前位置 至關於中止於此處 this._translate(Math.round(pos.x), Math.round(pos.y)); // 觸發滑動結束回調函數 this._execEvent('scrollEnd'); } else if ( !this.options.useTransition && this.isAnimating ) { this.isAnimating = false; this._execEvent('scrollEnd'); } this.startX = this.x; // scroller開始位置x this.startY = this.y; // scroller開始位置y this.absStartX = this.x; // this.absStartY = this.y; // this.pointX = point.pageX; // 觸點x this.pointY = point.pageY; // 觸點y // 觸發滑動前回調函數 this._execEvent('beforeScrollStart'); }, _move: function (e) { // 禁止 or 不存在此eventType 則返回 if ( !this.enabled || utils.eventType[e.type] !== this.initiated ) { return; } if ( this.options.preventDefault ) { // increases performance on Android? TODO: check! e.preventDefault(); } // point 觸點 var point = e.touches ? e.touches[0] : e, deltaX = point.pageX - this.pointX, // 當前觸點的pagex - 開始時的pagex = 觸點檔次增量x deltaY = point.pageY - this.pointY, // 觸點增量y timestamp = utils.getTime(), newX, newY, absDistX, absDistY; // 最近上一次的觸點位置 this.pointX = point.pageX; this.pointY = point.pageY; // 觸點移動的距離 this.distX += deltaX; this.distY += deltaY; absDistX = Math.abs(this.distX); absDistY = Math.abs(this.distY); // We need to move at least 10 pixels for the scrolling to initiate // 觸點至少移動10px纔會觸發scroll的move 而且 移動大於300ms if ( timestamp - this.endTime > 300 && (absDistX < 10 && absDistY < 10) ) { return; } // If you are scrolling in one direction lock the other // 除非設置了freeScroll,不然將只容許同一時間一個方向滑動 if ( !this.directionLocked && !this.options.freeScroll ) { if ( absDistX > absDistY + this.options.directionLockThreshold ) { this.directionLocked = 'h'; // lock horizontally } else if ( absDistY >= absDistX + this.options.directionLockThreshold ) { this.directionLocked = 'v'; // lock vertically } else { this.directionLocked = 'n'; // no lock } } if ( this.directionLocked == 'h' ) { if ( this.options.eventPassthrough == 'vertical' ) { e.preventDefault(); } else if ( this.options.eventPassthrough == 'horizontal' ) { this.initiated = false; return; } deltaY = 0; } else if ( this.directionLocked == 'v' ) { if ( this.options.eventPassthrough == 'horizontal' ) { e.preventDefault(); } else if ( this.options.eventPassthrough == 'vertical' ) { this.initiated = false; return; } deltaX = 0; } deltaX = this.hasHorizontalScroll ? deltaX : 0; deltaY = this.hasVerticalScroll ? deltaY : 0; // this.x this.y 是最近上一次的scroller位置 newX = this.x + deltaX; newY = this.y + deltaY; // Slow down if outside of the boundaries if ( newX > 0 || newX < this.maxScrollX ) { newX = this.options.bounce ? this.x + deltaX / 3 : newX > 0 ? 0 : this.maxScrollX; } if ( newY > 0 || newY < this.maxScrollY ) { newY = this.options.bounce ? this.y + deltaY / 3 : newY > 0 ? 0 : this.maxScrollY; } this.directionX = deltaX > 0 ? -1 : deltaX < 0 ? 1 : 0; // -1 手勢向左 1 手勢向右 this.directionY = deltaY > 0 ? -1 : deltaY < 0 ? 1 : 0; // -1 手勢向上 1 手勢向下 if ( !this.moved ) { this._execEvent('scrollStart'); } this.moved = true; this._translate(newX, newY); /* REPLACE START: _move */ if ( timestamp - this.startTime > 300 ) { //300ms更新一次 this.startTime = timestamp; this.startX = this.x; this.startY = this.y; } /* REPLACE END: _move */ }, // touchEnd時候處理緩動 _end: function (e) { if ( !this.enabled || utils.eventType[e.type] !== this.initiated ) { return; } if ( this.options.preventDefault && !utils.preventDefaultException(e.target, this.options.preventDefaultException) ) { e.preventDefault(); } var point = e.changedTouches ? e.changedTouches[0] : e, momentumX, momentumY, duration = utils.getTime() - this.startTime, newX = Math.round(this.x), newY = Math.round(this.y), distanceX = Math.abs(newX - this.startX), //單次移動過程的x軸移動距離 distanceY = Math.abs(newY - this.startY), time = 0, easing = ''; this.isInTransition = 0; this.initiated = 0; this.endTime = utils.getTime(); // reset if we are outside of the boundaries // 超過了邊界就從新回到邊界位 if ( this.resetPosition(this.options.bounceTime) ) { return; } // 堅持滑動到 this.scrollTo(newX, newY); // ensures that the last position is rounded // we scrolled less than 10 pixels if ( !this.moved ) { if ( this.options.tap ) { utils.tap(e, this.options.tap); } if ( this.options.click ) { utils.click(e); } this._execEvent('scrollCancel'); return; } if ( this._events.flick && duration < 200 && distanceX < 100 && distanceY < 100 ) { this._execEvent('flick'); return; } // start momentum animation if needed // 獲取緩動的距離 和 時間 { destination , duration } if ( this.options.momentum && duration < 300 ) { momentumX = this.hasHorizontalScroll ? utils.momentum(this.x, this.startX, duration, this.maxScrollX, this.options.bounce ? this.wrapperWidth : 0, this.options.deceleration) : { destination: newX, duration: 0 }; momentumY = this.hasVerticalScroll ? utils.momentum(this.y, this.startY, duration, this.maxScrollY, this.options.bounce ? this.wrapperHeight : 0, this.options.deceleration) : { destination: newY, duration: 0 }; newX = momentumX.destination; newY = momentumY.destination; time = Math.max(momentumX.duration, momentumY.duration); this.isInTransition = 1; } // INSERT POINT: _end if ( newX != this.x || newY != this.y ) { // change easing function when scroller goes out of the boundaries if ( newX > 0 || newX < this.maxScrollX || newY > 0 || newY < this.maxScrollY ) { easing = utils.ease.quadratic; } this.scrollTo(newX, newY, time, easing); return; } this._execEvent('scrollEnd'); }, _resize: function () { var that = this; clearTimeout(this.resizeTimeout); this.resizeTimeout = setTimeout(function () { that.refresh(); }, this.options.resizePolling); }, /** * 從新定位 */ resetPosition: function (time) { var x = this.x, y = this.y; time = time || 0; /** * 若是禁止水平滑動或者x大於0,x=0。若是能夠滑動的狀況,x應該是小於0的。 * 不然(可水平滑動),x爲maxScrollX即最左 */ if ( !this.hasHorizontalScroll || this.x > 0 ) { x = 0; } else if ( this.x < this.maxScrollX ) { x = this.maxScrollX; } /** * 若是禁止垂直滑動或者y大於0,y=0。若是能夠滑動的狀況,y應該是小於0的。 * 不然(可垂直滑動),y爲maxScrollY即最上 */ if ( !this.hasVerticalScroll || this.y > 0 ) { y = 0; } else if ( this.y < this.maxScrollY ) { y = this.maxScrollY; } // 初始化時候x y 都是0 返回。 if ( x == this.x && y == this.y ) { return false; } this.scrollTo(x, y, time, this.options.bounceEasing); return true; }, /** * scroll關閉的標誌 */ disable: function () { this.enabled = false; }, /** * scroll啓用的標誌 */ enable: function () { this.enabled = true; }, /** * 從新獲取位置等參數信息 */ refresh: function () { var rf = this.wrapper.offsetHeight; // Force reflow // 獲取包含塊的寬度/高度 this.wrapperWidth = this.wrapper.clientWidth; this.wrapperHeight = this.wrapper.clientHeight; /* REPLACE START: refresh */ // 滑動區的高度/寬度 this.scrollerWidth = this.scroller.offsetWidth; this.scrollerHeight = this.scroller.offsetHeight; // 最多容許滑動的x/y this.maxScrollX = this.wrapperWidth - this.scrollerWidth; this.maxScrollY = this.wrapperHeight - this.scrollerHeight; /* REPLACE END: refresh */ // 是否能夠垂直/水平滑動 this.hasHorizontalScroll = this.options.scrollX && this.maxScrollX < 0; this.hasVerticalScroll = this.options.scrollY && this.maxScrollY < 0; if ( !this.hasHorizontalScroll ) { this.maxScrollX = 0; this.scrollerWidth = this.wrapperWidth; } if ( !this.hasVerticalScroll ) { this.maxScrollY = 0; this.scrollerHeight = this.wrapperHeight; } this.endTime = 0; this.directionX = 0; this.directionY = 0; // 獲取包含塊的offsetLeft offsetTop this.wrapperOffset = utils.offset(this.wrapper); this._execEvent('refresh'); this.resetPosition(); // INSERT POINT: _refresh }, /** * 自定義事件三件套,不解釋。 */ on: function (type, fn) { if ( !this._events[type] ) { this._events[type] = []; } this._events[type].push(fn); }, off: function (type, fn) { if ( !this._events[type] ) { return; } var index = this._events[type].indexOf(fn); if ( index > -1 ) { this._events[type].splice(index, 1); } }, _execEvent: function (type) { if ( !this._events[type] ) { return; } var i = 0, l = this._events[type].length; if ( !l ) { return; } for ( ; i < l; i++ ) { this._events[type][i].apply(this, [].slice.call(arguments, 1)); } }, scrollBy: function (x, y, time, easing) { x = this.x + x; y = this.y + y; time = time || 0; this.scrollTo(x, y, time, easing); }, /** * * @param x 爲移動的x軸座標 * @param y 爲移動的y軸座標 * @param time 爲移動時間 * @param easing 爲移動的動畫效果 */ scrollTo: function (x, y, time, easing) { easing = easing || utils.ease.circular; this.isInTransition = this.options.useTransition && time > 0; if ( !time || (this.options.useTransition && easing.style) ) { this._transitionTimingFunction(easing.style); this._transitionTime(time); this._translate(x, y); } else { this._animate(x, y, time, easing.fn); } }, /** * 這個方法其實是對scrollTo的進一步封裝,滾動到相應的元素區域。 * @param el 爲須要滾動到的元素引用 * @param time 爲滾動時間 * @param offsetX 爲X軸偏移量 * @param offsetY 爲Y軸偏移量 * @param easing 動畫效果 */ scrollToElement: function (el, time, offsetX, offsetY, easing) { el = el.nodeType ? el : this.scroller.querySelector(el); if ( !el ) { return; } var pos = utils.offset(el); pos.left -= this.wrapperOffset.left; pos.top -= this.wrapperOffset.top; // if offsetX/Y are true we center the element to the screen if ( offsetX === true ) { offsetX = Math.round(el.offsetWidth / 2 - this.wrapper.offsetWidth / 2); } if ( offsetY === true ) { offsetY = Math.round(el.offsetHeight / 2 - this.wrapper.offsetHeight / 2); } pos.left -= offsetX || 0; pos.top -= offsetY || 0; pos.left = pos.left > 0 ? 0 : pos.left < this.maxScrollX ? this.maxScrollX : pos.left; pos.top = pos.top > 0 ? 0 : pos.top < this.maxScrollY ? this.maxScrollY : pos.top; time = time === undefined || time === null || time === 'auto' ? Math.max(Math.abs(this.x-pos.left), Math.abs(this.y-pos.top)) : time; this.scrollTo(pos.left, pos.top, time, easing); }, // 動畫運動時間 _transitionTime: function (time) { time = time || 0; this.scrollerStyle[utils.style.transitionDuration] = time + 'ms'; // 落後的安卓機運動時間須要把0轉0.001s if ( !time && utils.isBadAndroid ) { this.scrollerStyle[utils.style.transitionDuration] = '0.001s'; } // INSERT POINT: _transitionTime }, _transitionTimingFunction: function (easing) { this.scrollerStyle[utils.style.transitionTimingFunction] = easing; // INSERT POINT: _transitionTimingFunction }, /** * @param x 爲移動的x軸座標 * @param y 爲移動的y軸座標 */ _translate: function (x, y) { if ( this.options.useTransform ) { /* REPLACE START: _translate */ this.scrollerStyle[utils.style.transform] = 'translate(' + x + 'px,' + y + 'px)' + this.translateZ; /* REPLACE END: _translate */ } else { x = Math.round(x); y = Math.round(y); this.scrollerStyle.left = x + 'px'; this.scrollerStyle.top = y + 'px'; } this.x = x; this.y = y; // INSERT POINT: _translate }, /** * @param remove 是否解綁事件 */ _initEvents: function (remove) { var eventType = remove ? utils.removeEvent : utils.addEvent, // 是否綁定到wrapper元素上 target = this.options.bindToWrapper ? this.wrapper : window; //旋轉屏幕事件 eventType(window, 'orientationchange', this); eventType(window, 'resize', this); if ( this.options.click ) { eventType(this.wrapper, 'click', this, true); } // 若是沒禁用鼠標,綁定鼠標事件,針對PC。 if ( !this.options.disableMouse ) { eventType(this.wrapper, 'mousedown', this); eventType(target, 'mousemove', this); eventType(target, 'mousecancel', this); eventType(target, 'mouseup', this); } // 針對win phone的touch事件 if ( utils.hasPointer && !this.options.disablePointer ) { eventType(this.wrapper, utils.prefixPointerEvent('pointerdown'), this); eventType(target, utils.prefixPointerEvent('pointermove'), this); eventType(target, utils.prefixPointerEvent('pointercancel'), this); eventType(target, utils.prefixPointerEvent('pointerup'), this); } //針對ios & Android的touch事件 if ( utils.hasTouch && !this.options.disableTouch ) { eventType(this.wrapper, 'touchstart', this); eventType(target, 'touchmove', this); eventType(target, 'touchcancel', this); eventType(target, 'touchend', this); } // css3動畫結束後的回調事件 eventType(this.scroller, 'transitionend', this); eventType(this.scroller, 'webkitTransitionEnd', this); eventType(this.scroller, 'oTransitionEnd', this); eventType(this.scroller, 'MSTransitionEnd', this); }, getComputedPosition: function () { //獲取scroller的樣式 var matrix = window.getComputedStyle(this.scroller, null), x, y; //兩種方式 transform 和 top/left ( transform在ios下,文本輸入的光標fixed ) if ( this.options.useTransform ) { matrix = matrix[utils.style.transform].split(')')[0].split(', '); x = +(matrix[12] || matrix[4]); y = +(matrix[13] || matrix[5]); } else { x = +matrix.left.replace(/[^-\d.]/g, ''); y = +matrix.top.replace(/[^-\d.]/g, ''); } //返回 (x ,y) or (top ,left) return { x: x, y: y }; }, /** * @param destx 爲移動目的地的x軸座標 * @param desty 爲移動目的地的y軸座標 * @param duration 爲移動的時間 * @param easingFn 爲移動緩動函數 */ _animate: function (destX, destY, duration, easingFn) { var that = this, startX = this.x, startY = this.y, startTime = utils.getTime(), destTime = startTime + duration; function step () { var now = utils.getTime(), newX, newY, easing; if ( now >= destTime ) { that.isAnimating = false; that._translate(destX, destY); if ( !that.resetPosition(that.options.bounceTime) ) { that._execEvent('scrollEnd'); } return; } now = ( now - startTime ) / duration; easing = easingFn(now); newX = ( destX - startX ) * easing + startX; newY = ( destY - startY ) * easing + startY; that._translate(newX, newY); if ( that.isAnimating ) { rAF(step); } } this.isAnimating = true; step(); }, 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; case 'orientationchange': case 'resize': this._resize(); break; case 'transitionend': case 'webkitTransitionEnd': case 'oTransitionEnd': case 'MSTransitionEnd': this._transitionEnd(e); break; case 'wheel': case 'DOMMouseScroll': case 'mousewheel': this._wheel(e); break; case 'keydown': this._key(e); break; case 'click': if ( !e._constructed ) { e.preventDefault(); e.stopPropagation(); } break; } } }; IScroll.utils = utils; if ( typeof module != 'undefined' && module.exports ) { module.exports = IScroll; } else { window.IScroll = IScroll; } })(window, document, Math);