完整地址:https://community.apicloud.co...
`/! 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)
`