/*! JRoll v2.6.1 ~ (c) 2015-2017 Author:BarZu Git:https://github.com/chjtx/JRoll Website:http://www.chjtx.com/JRoll/ */ /* global define, HTMLElement */ (function (window, document, Math) { 'use strict' var JRoll var VERSION = '2.6.1' var rAF = window.requestAnimationFrame || window.webkitRequestAnimationFrame || function (callback) { setTimeout(callback, 17) } var sty = document.createElement('div').style var jrollMap = {} // 保存所有JRoll对象 var ua = navigator.userAgent.toLowerCase() var prefix = (function () { var vendors = ['OT', 'msT', 'MozT', 'webkitT', 't'] var transform var i = vendors.length while (i--) { transform = vendors[i] + 'ransform' if (transform in sty) return vendors[i] } })() // 实用工具 var utils = { // 兼容 TSF: prefix + 'ransform', TSD: prefix + 'ransitionDuration', TFO: prefix + 'ransformOrigin', isAndroid: /android/.test(ua), isIOS: /iphone|ipad/.test(ua), isMobile: /mobile|phone|android|pad/.test(ua), // 判断浏览是否支持perspective属性,从而判断是否支持开启3D加速 translateZ: (function (pre) { var f if (pre) { f = pre + 'Perspective' in sty } else { f = 'perspective' in sty } return f ? ' translateZ(0px)' : '' })(prefix.substr(0, prefix.length - 1)), // 计算相对偏移,a相对于b的偏移 computeTranslate: function (a, b) { var x = 0 var y = 0 var s while (a) { s = window.getComputedStyle(a)[utils.TSF].replace(/matrix\(|\)/g, '').split(', ') x += parseInt(s[4]) || 0 y += parseInt(s[5]) || 0 a = a.parentElement if (a === b) { a = null } } return { x: x, y: y } }, // 计算相对位置,a相对于b的位置 computePosition: function (a, b) { var left = 0 var top = 0 while (a) { left += a.offsetLeft top += a.offsetTop a = a.offsetParent if (a === b) { a = null } } return { left: left, top: top } }, /** * 在指定时间内将指定元素从开始位置移到结束位置并执行回调方法 * el 必须是dom元素,必填 * x,y 结束位置,必填 * duration 过渡时长,单位ms,可选 * callback 回调方法,可选 * context 上下文,可选 */ moveTo: function (el, x, y, duration, callback, context) { var startX = 0 var startY = 0 var endX var endY var zoom = 1 var stepX var stepY var d var result result = /translate\(([-\d.]+)px,\s+([-\d.]+)px\)\s+(?:translateZ\(0px\)\s+)?scale\(([\d.]+)\)/.exec(el.style[utils.TSF]) if (result) { startX = Number(result[1]) startY = Number(result[2]) zoom = Number(result[3]) } d = duration || 17 stepX = (x - startX) / (d / 17) stepY = (y - startY) / (d / 17) endX = startX endY = startY function moving () { d = d - 17 if (d < 17) { endX = x endY = y } else { endX = parseInt(endX + stepX, 10) endY = parseInt(endY + stepY, 10) } el.style[utils.TSF] = 'translate(' + endX + 'px, ' + endY + 'px)' + utils.translateZ + ' scale(' + zoom + ')' // 执行用户注册的滑动事件 if (context) { context.x = endX context.y = endY context._execEvent('scroll') if (context.scrollBtnX) context._runScrollBarX() if (context.scrollBtnY) context._runScrollBarY() } if (d > 0 && !(endX === x && endY === y)) { rAF(moving) } else if (typeof callback === 'function') { callback() } } moving() }, /** * 一层一层往上查找已实例化的jroll * el 目标元素 * force 强制查找,忽略textarea */ findScroller: function (el, force) { var id // 遇到document或带垂直滚动条的textarea终止查找 if (force || !(el.tagName === 'TEXTAREA' && el.scrollHeight > el.offsetHeight)) { while (el !== document) { id = el.getAttribute('jroll-id') if (id) { return jrollMap[id] } el = el.parentNode } } return null }, // 一层一层往上查找所有已实例化的jroll findAllJRolls: function (el, force) { var jrolls = [] var id // 遇到document或带垂直滚动条的textarea终止查找 if (force || !(el.tagName === 'TEXTAREA' && (el.scrollHeight > el.clientHeight) && (el.scrollTop > 0 && el.scrollTop < el.scrollHeight - el.clientHeight))) { while (el !== document) { id = el.getAttribute('jroll-id') if (id) { jrolls.push(jrollMap[id]) } el = el.parentNode } } return jrolls } } function _touchstart (e) { var jrolls = utils.findAllJRolls(e.target) var l = jrolls.length // 非缩放且第二个手指按屏中止往后执行 if (JRoll.jrollActive && !JRoll.jrollActive.options.zoom && e.touches && e.touches.length > 1) { return } if (l) { while (l--) { if (jrolls[l].moving) { e.preventDefault() // 防止按停滑动时误触a链接 jrolls[l]._endAction() // 结束并终止惯性 } } JRoll.jrollActive = jrolls[0] JRoll.jrollActive._start(e) } else if (JRoll.jrollActive) { JRoll.jrollActive._end(e) } } function _touchmove (e) { if (JRoll.jrollActive) { var activeElement = document.activeElement if (JRoll.jrollActive.options.preventDefault) { e.preventDefault() } if (utils.isMobile && JRoll.jrollActive.options.autoBlur && (activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA')) { activeElement.blur() } JRoll.jrollActive._move(e) } } function _touchend (e) { if (JRoll.jrollActive) { JRoll.jrollActive._end(e) } } function _resize () { setTimeout(function () { for (var i in jrollMap) { jrollMap[i].refresh().scrollTo(jrollMap[i].x, jrollMap[i].y, 200) } }, 600) } function _wheel (e) { var jroll = utils.findScroller(e.target) if (jroll) { jroll._wheel(e) } } // 检测是否支持passive选项 var supportsPassiveOption = false try { var opts = Object.defineProperty({}, 'passive', { get: function () { supportsPassiveOption = true } }) window.addEventListener('test', null, opts) } catch (e) {} function addEvent (type, method) { document.addEventListener(type, method, supportsPassiveOption ? { passive: false } : false) } // 添加监听事件 addEvent(utils.isMobile ? 'touchstart' : 'mousedown', _touchstart) addEvent(utils.isMobile ? 'touchmove' : 'mousemove', _touchmove) addEvent(utils.isMobile ? 'touchend' : 'mouseup', _touchend) if (utils.isMobile) { addEvent('touchcancel', _touchend) } else { addEvent(/firefox/.test(ua) ? 'DOMMouseScroll' : 'mousewheel', _wheel) } window.addEventListener('resize', _resize) window.addEventListener('orientationchange', _resize) JRoll = function (el, options) { var me = this me.wrapper = typeof el === 'string' ? document.querySelector(el) : el me.scroller = options && options.scroller ? (typeof options.scroller === 'string' ? document.querySelector(options.scroller) : options.scroller) : me.wrapper.children[0] // 防止重复多次new JRoll if (me.scroller.jroll) { me.scroller.jroll.refresh() return me.scroller.jroll } else { me.scroller.jroll = me } this._init(el, options) } JRoll.version = VERSION JRoll.utils = utils JRoll.jrollMap = jrollMap JRoll.prototype = { // 初始化 _init: function (el, options) { var me = this // 计算wrapper相对document的位置 me.wrapperOffset = utils.computePosition(me.wrapper, document.body) // 创建ID me.id = (options && options.id) || me.scroller.getAttribute('jroll-id') || 'jroll_' + Math.random().toString().substr(2, 8) // 保存jroll对象 me.scroller.setAttribute('jroll-id', me.id) jrollMap[me.id] = me // 默认选项 me.options = { scrollX: false, scrollY: true, scrollFree: false, // 自由滑动 minX: null, // 向左滑动的边界值,默认为0 maxX: null, // 向右滑动的边界值,默认为scroller的宽*-1 minY: null, // 向下滑动的边界值,默认为0 maxY: null, // 向上滑动的边界值,默认为scroller的高*-1 zoom: false, // 使能缩放 zoomMin: 1, // 最小缩放倍数 zoomMax: 4, // 最大缩放倍数 zoomDuration: 400, // 缩放结束后回到限定位置的过渡时间 bounce: true, // 回弹 scrollBarX: false, // 开启x滚动条 scrollBarY: false, // 开启y滚动条 scrollBarFade: false, // 滚动条使用渐隐模式 preventDefault: true, // 禁止touchmove默认事件 momentum: true, // 滑动结束平滑过渡 autoStyle: true, // 自动为wrapper和scroller添加样式 autoBlur: true, // 在滑动时自动将input/textarea失焦 edgeRelease: true // 边缘释放,滑动到上下边界自动结束,解决手指滑出屏幕没触发touchEnd事件的问题 } for (var i in options) { if (i !== 'scroller') { me.options[i] = options[i] } } if (me.options.autoStyle) { // 将wrapper设为relative if (window.getComputedStyle(me.wrapper).position === 'static') { me.wrapper.style.position = 'relative' me.wrapper.style.top = '0' me.wrapper.style.left = '0' } me.wrapper.style.overflow = 'hidden' me.scroller.style.minHeight = '100%' } if (me.options.zoom) { // 该属性是为了解决缩放时与浏览器手势冲突造成缩放卡顿的问题,尤其是微信端 // 设置该属性会导致 preventDefault 选项失效 me.scroller.style.touchAction = 'none' } me.x = 0 me.y = 0 /** * 当前状态,可取值: * null * preScroll(准备滑动) * preZoom(准备缩放) * scrollX(横向) * scrollY(竖向) * scrollFree(各个方向) */ me.s = null me.scrollBarX = null // x滚动条 me.scrollBarY = null // y滚动条 me._s = { startX: 0, startY: 0, lastX: 0, lastY: 0, endX: 0, endY: 0 } me._z = { spacing: 0, // 两指间间距 scale: 1, startScale: 1 } me._event = { 'scrollStart': [], 'scroll': [], 'scrollEnd': [], 'zoomStart': [], 'zoom': [], 'zoomEnd': [], 'refresh': [], 'touchEnd': [] } me.refresh(true) }, // 开启 enable: function () { var me = this me.scroller.setAttribute('jroll-id', me.id) return me }, // 关闭 disable: function () { var me = this me.scroller.removeAttribute('jroll-id') return me }, // 销毁 destroy: function () { var me = this delete jrollMap[me.id] delete me.scroller.jroll if (me.scrollBarX) { me.wrapper.removeChild(me.scrollBarX) } if (me.scrollBarY) { me.wrapper.removeChild(me.scrollBarY) } me.disable() me.scroller.style[utils.TSF] = '' me.scroller.style[utils.TSD] = '' me.scroller.style[utils.TFO] = '' me.prototype = null for (var i in me) { if (me.hasOwnProperty(i)) { delete me[i] } } }, // 替换对象 call: function (target, e) { var me = this me.scrollTo(me.x, me.y) JRoll.jrollActive = target if (e) target._start(e) return target }, // 刷新JRoll的宽高 refresh: function (notRefreshEvent) { var me = this var wrapperStyle = window.getComputedStyle(me.wrapper) var scrollerStyle = window.getComputedStyle(me.scroller) var paddingX var paddingY var marginX var marginY var temp var size me.wrapperWidth = me.wrapper.clientWidth me.wrapperHeight = me.wrapper.clientHeight me.scrollerWidth = Math.round(me.scroller.offsetWidth * me._z.scale) me.scrollerHeight = Math.round(me.scroller.offsetHeight * me._z.scale) // 解决wrapper的padding和scroller的margin造成maxWidth/maxHeight计算错误的问题 paddingX = parseInt(wrapperStyle['padding-left']) + parseInt(wrapperStyle['padding-right']) paddingY = parseInt(wrapperStyle['padding-top']) + parseInt(wrapperStyle['padding-bottom']) marginX = parseInt(scrollerStyle['margin-left']) + parseInt(scrollerStyle['margin-right']) marginY = parseInt(scrollerStyle['margin-top']) + parseInt(scrollerStyle['margin-bottom']) // 最大/最小范围 me.minScrollX = me.options.minX === null ? 0 : me.options.minX me.maxScrollX = me.options.maxX === null ? me.wrapperWidth - me.scrollerWidth - paddingX - marginX : me.options.maxX me.minScrollY = me.options.minY === null ? 0 : me.options.minY me.maxScrollY = me.options.maxY === null ? me.wrapperHeight - me.scrollerHeight - paddingY - marginY : me.options.maxY if (me.minScrollX < 0) { me.minScrollX = 0 } if (me.minScrollY < 0) { me.minScrollY = 0 } if (me.maxScrollX > 0) { me.maxScrollX = 0 } if (me.maxScrollY > 0) { me.maxScrollY = 0 } me._s.endX = me.x me._s.endY = me.y // x滚动条 if (me.options.scrollBarX) { if (!me.scrollBarX) { temp = me._createScrollBar('jroll-xbar', 'jroll-xbtn', false) me.scrollBarX = temp[0] me.scrollBtnX = temp[1] } me.scrollBarScaleX = me.wrapper.clientWidth / me.scrollerWidth size = Math.round(me.scrollBarX.clientWidth * me.scrollBarScaleX) me.scrollBtnX.style.width = (size > 8 ? size : 8) + 'px' me._runScrollBarX() } else if (me.scrollBarX) { me.wrapper.removeChild(me.scrollBarX) me.scrollBarX = null } // y滚动条 if (me.options.scrollBarY) { if (!me.scrollBarY) { temp = me._createScrollBar('jroll-ybar', 'jroll-ybtn', true) me.scrollBarY = temp[0] me.scrollBtnY = temp[1] } me.scrollBarScaleY = me.wrapper.clientHeight / me.scrollerHeight size = Math.round(me.scrollBarY.clientHeight * me.scrollBarScaleY) me.scrollBtnY.style.height = (size > 8 ? size : 8) + 'px' me._runScrollBarY() } else if (me.scrollBarY) { me.wrapper.removeChild(me.scrollBarY) me.scrollBarY = null } if (!notRefreshEvent) { me._execEvent('refresh') } return me }, scale: function (multiple) { var me = this var z = parseFloat(multiple) if (!isNaN(z)) { me.scroller.style[utils.TFO] = '0 0' me._z.scale = z me.refresh()._scrollTo(me.x, me.y) me.scrollTo(me.x, me.y, 400) } return me }, _wheel: function (e) { var me = this var y = e.wheelDelta || -(e.detail / 3) * 120 // 兼容火狐 if (me.options.scrollY || me.options.scrollFree) { me.scrollTo(me.x, me._compute(me.y + y, me.minScrollY, me.maxScrollY)) } }, // 滑动滚动条 _runScrollBarX: function () { var me = this var x = Math.round(-1 * me.x * me.scrollBarScaleX) me._scrollTo.call({ scroller: me.scrollBtnX, _z: { scale: 1 } }, x, 0) }, _runScrollBarY: function () { var me = this var y = Math.round(-1 * me.y * me.scrollBarScaleY) me._scrollTo.call({ scroller: me.scrollBtnY, _z: { scale: 1 } }, 0, y) }, // 创建滚动条 _createScrollBar: function (a, b, isY) { var me = this var bar var btn bar = document.createElement('div') btn = document.createElement('div') bar.className = a btn.className = b if (this.options.scrollBarX === true || this.options.scrollBarY === true) { if (isY) { bar.style.cssText = 'position:absolute;top:2px;right:2px;bottom:2px;width:6px;overflow:hidden;border-radius:2px;-webkit-transform: scaleX(.5);transform: scaleX(.5);' btn.style.cssText = 'background:rgba(0,0,0,.4);position:absolute;top:0;left:0;right:0;border-radius:2px;' } else { bar.style.cssText = 'position:absolute;left:2px;bottom:2px;right:2px;height:6px;overflow:hidden;border-radius:2px;-webkit-transform: scaleY(.5);transform: scaleY(.5);' btn.style.cssText = 'background:rgba(0,0,0,.4);height:100%;position:absolute;left:0;top:0;bottom:0;border-radius:2px;' } } if (me.options.scrollBarFade) { bar.style.opacity = 0 } bar.appendChild(btn) me.wrapper.appendChild(bar) return [bar, btn] }, // 滚动条渐隐 _fade: function (bar, time) { var me = this if (me.fading && time > 0) { time = time - 25 if (time % 100 === 0) bar.style.opacity = time / 1000 } else { return } rAF(me._fade.bind(me, bar, time)) }, on: function (event, callback) { var me = this switch (event) { case 'scrollStart': me._event.scrollStart.push(callback) break case 'scroll': me._event.scroll.push(callback) break case 'scrollEnd': me._event.scrollEnd.push(callback) break case 'zoomStart': me._event.zoomStart.push(callback) break case 'zoom': me._event.zoom.push(callback) break case 'zoomEnd': me._event.zoomEnd.push(callback) break case 'refresh': me._event.refresh.push(callback) break case 'touchEnd': me._event.touchEnd.push(callback) break } return me }, _execEvent: function (event, e) { var me = this var i = me._event[event].length - 1 for (; i >= 0; i--) { me._event[event][i].call(me, e) } }, // 计算x,y的值 _compute: function (val, min, max) { var me = this if (val > min) { if (me.options.bounce && (val > (min + 10))) { return Math.round(min + ((val - min) / 4)) } else { return min } } if (val < max) { if (me.options.bounce && (val < (max - 10))) { return Math.round(max + ((val - max) / 4)) } else { return max } } return val }, _scrollTo: function (x, y) { this.scroller.style[utils.TSF] = 'translate(' + x + 'px, ' + y + 'px)' + utils.translateZ + ' scale(' + this._z.scale + ')' }, /** * 供用户调用的scrollTo方法 * x x坐标 * y y坐标 * timing 滑动时长,使用css3的transition-duration进行过渡 * allow 是否允许超出边界,默认为undefined即不允许超出边界 * system 为true时即是本程序自己调用,默认为undefined即非本程序调用 */ scrollTo: function (x, y, timing, allow, callback, system, t) { var me = this if (!allow) { // x if (x >= me.minScrollX) { me.x = me.minScrollX // 滑到最大值时手指继续滑,重置开始、结束位置,优化体验 if (t) { me._s.startX = t[0].pageX me._s.endX = me.minScrollX } } else if (x <= me.maxScrollX) { me.x = me.maxScrollX if (t) { me._s.startX = t[0].pageX me._s.endX = me.maxScrollX } } else { me.x = x } // y if (y >= me.minScrollY) { me.y = me.minScrollY if (t) { me._s.startY = t[0].pageY me._s.endY = me.minScrollY } } else if (y <= me.maxScrollY) { me.y = me.maxScrollY if (t) { me._s.startY = t[0].pageY me._s.endY = me.maxScrollY } } else { me.y = y } } else { me.x = x me.y = y } if (!system) { me._s.endX = me.x me._s.endY = me.y } if (timing) { utils.moveTo(me.scroller, me.x, me.y, timing, callback, system ? me : null) } else { me._scrollTo(me.x, me.y) if (system) { me._execEvent('scroll', t && t[0]) } if (typeof callback === 'function') { callback() } } if (me.scrollBtnX) me._runScrollBarX() if (me.scrollBtnY) me._runScrollBarY() return me }, scrollToElement: function (selector, timing, allow, callback) { var me = this var el = typeof selector === 'string' ? me.scroller.querySelector(selector) : selector if (el instanceof HTMLElement) { var p = utils.computePosition(el, me.scroller) var t = utils.computeTranslate(el, me.scroller) var x = -(p.left + t.x) var y = -(p.top + t.y) return me.scrollTo(x, y, timing, allow, callback) } }, _endAction: function () { var me = this me._s.endX = me.x me._s.endY = me.y me.moving = false if (me.options.scrollBarFade && !me.fading) { me.fading = true // 标记渐隐滚动条 if (me.scrollBarX) me._fade(me.scrollBarX, 2000) if (me.scrollBarY) me._fade(me.scrollBarY, 2000) } me._execEvent('scrollEnd') }, _stepBounce: function (time, count) { var me = this var now = Date.now() var t = now - time var s = 0 if (t > 0) { me.speed = me.speed - t * 0.008 s = Math.round(me.speed * t * count * 0.005) if (me.speed <= 0 || s <= 0 || isNaN(s)) { me.bouncing = false me.scrollTo(me.x, me.y, 200, false, function () { me._endAction() }, true) return } if (me.s === 'scrollY' || me.s === 'scrollFree') { me.y = me.y + s * me.directionY } if (me.s === 'scrollX' || me.s === 'scrollFree') { me.x = me.x + s * me.directionX } me.scrollTo(me.x, me.y, 0, true, null, true) rAF(me._stepBounce.bind(me, now, count - 1)) } }, _x: function (p) { var me = this var n = me.directionX * p if (!isNaN(n)) { me.x = me.x + n // 达到边界终止惯性,执行回弹 if (me.x >= me.minScrollX || me.x <= me.maxScrollX) { if (me.options.bounce) { me.bouncing = true // 标记回弹 } else { me.moving = false } } } }, _y: function (p) { var me = this var n = me.directionY * p if (!isNaN(n)) { me.y = me.y + n // 达到边界终止惯性,执行回弹 if (me.y >= me.minScrollY || me.y <= me.maxScrollY) { if (me.options.bounce) { me.bouncing = true // 标记回弹 } else { me.moving = false } } } }, _xy: function (p) { var me = this var x = Math.round(me.cosX * p) var y = Math.round(me.cosY * p) if (!isNaN(x) && !isNaN(y)) { me.x = me.x + x me.y = me.y + y // 达到边界终止惯性,执行回弹 if ((me.x >= me.minScrollX || me.x <= me.maxScrollX) && (me.y >= me.minScrollY || me.y <= me.maxScrollY)) { me.moving = false } } }, _step: function (time) { var me = this var now = Date.now() var t = now - time var s = 0 // fixed github issue #63 if (!me.id) { return } // 惯性滑动结束,执行回弹 if (me.bouncing) { rAF(me._stepBounce.bind(me, time, 20)) return } // 终止 if (!me.moving) { me._endAction() return } // 防止t为0滑动终止造成卡顿现象 if (t > 0) { me.speed = me.speed - t * (me.speed > 1.2 ? 0.001 : (me.speed > 0.6 ? 0.0008 : 0.0006)) s = Math.round(me.speed * t) if (me.speed <= 0 || s <= 0) { me._endAction() return } time = now // _do是可变方法,可为_x,_y或_xy,在判断方向时判断为何值,避免在次处进行过多的判断操作 me._do(s) me.scrollTo(me.x, me.y, 0, me.options.bounce && !me.options.scrollFree, null, true) } rAF(me._step.bind(me, time)) }, _doScroll: function (d, e) { var me = this var pageY me.distance = d if (me.options.bounce) { me.x = me._compute(me.x, me.minScrollX, me.maxScrollX) me.y = me._compute(me.y, me.minScrollY, me.maxScrollY) } me.scrollTo(me.x, me.y, 0, me.options.bounce, null, true, (e.touches || [e])) // 解决垂直滑动超出屏幕边界时捕捉不到touchend事件无法执行结束方法的问题 if (e && e.touches && me.options.edgeRelease) { pageY = e.touches[0].pageY if (pageY <= 10 || pageY >= window.innerHeight - 10) { me._end(e) } } }, // 判断是滑动JRoll还是滑动Textarea(垂直方向) _yTextarea: function (e) { var me = this var target = e.target if (target.tagName === 'TEXTAREA' && target.scrollHeight > target.clientHeight && // textarea滑动条在顶部,向上滑动时将滑动权交给textarea ((target.scrollTop === 0 && me.directionY === -1) || // textarea滑动条在底部,向下滑动时将滑动权交给textarea (target.scrollTop === target.scrollHeight - target.clientHeight && me.directionY === 1))) { me._end(e, true) return false } return true }, _start: function (e) { var me = this var t = e.touches || [e] // 判断缩放 if (me.options.zoom && t.length > 1) { me.s = 'preZoom' me.scroller.style[utils.TFO] = '0 0' var c1 = Math.abs(t[0].pageX - t[1].pageX) var c2 = Math.abs(t[0].pageY - t[1].pageY) me._z.spacing = Math.sqrt(c1 * c1 + c2 * c2) me._z.startScale = me._z.scale me.originX = (t[0].pageX - t[1].pageX) / 2 + t[1].pageX - (utils.computePosition(me.scroller, document.body).left + utils.computeTranslate(me.scroller, document.body).x) me.originY = (t[0].pageY - t[1].pageY) / 2 + t[1].pageY - (utils.computePosition(me.scroller, document.body).top + utils.computeTranslate(me.scroller, document.body).y) me._execEvent('zoomStart', e) return } if (me.options.scrollBarFade) { me.fading = false // 终止滑动条渐隐 if (me.scrollBarX) me.scrollBarX.style.opacity = 1 if (me.scrollBarY) me.scrollBarY.style.opacity = 1 } // 任意方向滑动 if (me.options.scrollFree) { me._do = me._xy me.s = 'scrollFree' // 允许xy两个方向滑动 } else if (me.options.scrollX && me.options.scrollY) { me.s = 'preScroll' // 只允许y } else if (!me.options.scrollX && me.options.scrollY) { me._do = me._y me.s = 'scrollY' // 只允许x } else if (me.options.scrollX && !me.options.scrollY) { me._do = me._x me.s = 'scrollX' } else { me.s = null return } me.distance = 0 me.lastMoveTime = me.startTime = Date.now() me._s.lastX = me.startPositionX = me._s.startX = t[0].pageX me._s.lastY = me.startPositionY = me._s.startY = t[0].pageY me._execEvent('scrollStart', e) }, _move: function (e) { var me = this var t = e.touches || [e] var now var x var y var dx var dy var px var py var sqrtXY var directionX = 1 var directionY = 1 // 一个很奇怪的问题,在小米5默认浏览器上同时对x,y进行赋值流畅度会降低 // 因此采取选择性赋值以保证单向运行较好的滑动体验 if (me.s === 'preScroll' || me.s === 'scrollX' || me.s === 'scrollFree') { x = t[0].pageX } if (me.s === 'preScroll' || me.s === 'scrollY' || me.s === 'scrollFree') { y = t[0].pageY } dx = x - me._s.lastX dy = y - me._s.lastY me._s.lastX = x me._s.lastY = y directionX = dx >= 0 ? 1 : -1 // 手指滑动方向,1(向右) | -1(向左) directionY = dy >= 0 ? 1 : -1 // 手指滑动方向,1(向下) | -1(向上) now = Date.now() if (now - me.lastMoveTime > 200 || me.directionX !== directionX || me.directionY !== directionY) { me.startTime = now me.startPositionX = x me.startPositionY = y me.directionX = directionX me.directionY = directionY } me.lastMoveTime = now px = x - me.startPositionX py = y - me.startPositionY // 判断滑动方向 if (me.s === 'preScroll') { // 判断为y方向,y方向滑动较常使用,因此优先判断 if (Math.abs(y - me._s.startY) >= Math.abs(x - me._s.startX)) { me._do = me._y me.s = 'scrollY' return } // 判断为x方向 if (Math.abs(y - me._s.startY) < Math.abs(x - me._s.startX)) { me._do = me._x me.s = 'scrollX' return } } // y方向滑动 if (me.s === 'scrollY') { me.y = y - me._s.startY + me._s.endY if (me._yTextarea(e)) { me._doScroll(py, e) } return } // x方向滑动 if (me.s === 'scrollX') { me.x = x - me._s.startX + me._s.endX me._doScroll(px, e) return } // 任意方向滑动 if (me.s === 'scrollFree') { me.x = x - me._s.startX + me._s.endX me.y = y - me._s.startY + me._s.endY sqrtXY = Math.sqrt(px * px + py * py) me.cosX = px / sqrtXY me.cosY = py / sqrtXY me._doScroll(Math.sqrt(px * px + py * py), e) return } // 缩放 if (me.s === 'preZoom') { var c1 = Math.abs(t[0].pageX - t[1].pageX) var c2 = Math.abs(t[0].pageY - t[1].pageY) var spacing = Math.sqrt(c1 * c1 + c2 * c2) var scale = spacing / me._z.spacing * me._z.startScale var lastScale if (scale < me.options.zoomMin) { scale = me.options.zoomMin } else if (scale > me.options.zoomMax) { scale = me.options.zoomMax } lastScale = scale / me._z.startScale me.x = Math.round(me.originX - me.originX * lastScale + me._s.endX) me.y = Math.round(me.originY - me.originY * lastScale + me._s.endY) me._z.scale = scale me._scrollTo(me.x, me.y) me._execEvent('zoom', e) return } }, _end: function (e, manual) { var me = this var ex1 var ex2 var now = Date.now() var s1 = me.s === 'scrollY' var s2 = me.s === 'scrollX' var s3 = me.s === 'scrollFree' // 滑动结束 if (s1 || s2 || s3) { // 禁止第二个手指滑动,只有一个手指时touchend事件的touches.length为0 // manual参数用于判断是否手动执行_end方法,用于处理带滚动条的texearea if (e.touches && e.touches.length && !manual) { return } me._execEvent('touchEnd') JRoll.jrollActive = null me.duration = now - me.startTime ex1 = me.y > me.minScrollY || me.y < me.maxScrollY ex2 = me.x > me.minScrollX || me.x < me.maxScrollX // 超出边界回弹 if ((s1 && ex1) || (s2 && ex2) || (s3 && (ex1 || ex2))) { me.scrollTo(me.x, me.y, 300)._endAction() // 惯性滑动 } else if (me.options.momentum && me.duration < 200 && me.distance) { me.speed = Math.abs(me.distance / me.duration) me.speed = me.speed > 2 ? 2 : me.speed me.moving = true rAF(me._step.bind(me, now)) } else { me._endAction() } return } // 缩放结束 if (me.s === 'preZoom') { me._execEvent('touchEnd') JRoll.jrollActive = null if (me._z.scale > me.options.zoomMax) { me._z.scale = me.options.zoomMax } else if (me._z.scale < me.options.zoomMin) { me._z.scale = me.options.zoomMin } me.refresh() me.scrollTo(me.x, me.y, me.options.zoomDuration) me._execEvent('zoomEnd') return } } } if (typeof module !== 'undefined' && module.exports) { module.exports = JRoll } if (typeof define === 'function') { define(function () { return JRoll }) } window.JRoll = JRoll })(window, document, Math)
完整内容地址:https://community.apicloud.com/bbs/thread-87977-1-1.html