import touchtrack from './touchtrack'
|
|
import {
|
Decline,
|
Friction,
|
STD
|
} from './utils'
|
|
import {
|
emitter
|
} from '../../mixins'
|
|
let requesting = false
|
|
function requestAnimationFrame (callback) {
|
return setTimeout(callback, 16)
|
}
|
|
function cancelAnimationFrame (id) {
|
clearTimeout(id)
|
}
|
|
function _requestAnimationFrame (e) {
|
if (!requesting) {
|
requesting = true
|
requestAnimationFrame(function () {
|
e()
|
requesting = false
|
})
|
}
|
}
|
|
function v (a, b) {
|
return +((1000 * a - 1000 * b) / 1000).toFixed(1)
|
}
|
|
function g (e, t, n) {
|
const i = function (e) {
|
if (e && e.id) {
|
cancelAnimationFrame(e.id)
|
}
|
if (e) {
|
e.cancelled = true
|
}
|
}
|
const r = {
|
id: 0,
|
cancelled: false
|
}
|
function fn (n, i, r, o) {
|
if (!n || !n.cancelled) {
|
r(i)
|
const a = e.done()
|
if (!a) {
|
if (!n.cancelled) {
|
(n.id = requestAnimationFrame(fn.bind(null, n, i, r, o)))
|
}
|
}
|
if (a && o) {
|
o(i)
|
}
|
}
|
}
|
fn(r, e, t, n)
|
return {
|
cancel: i.bind(null, r),
|
model: e
|
}
|
}
|
|
function getMovableView (weex) {
|
const dom = weex.requireModule('dom')
|
const animation = weex.requireModule('animation')
|
|
return {
|
name: 'MovableView',
|
mixins: [touchtrack, emitter],
|
props: {
|
direction: {
|
type: String,
|
default: 'none'
|
},
|
inertia: {
|
type: [Boolean, String],
|
default: false
|
},
|
outOfBounds: {
|
type: [Boolean, String],
|
default: false
|
},
|
x: {
|
type: [Number, String],
|
default: 0
|
},
|
y: {
|
type: [Number, String],
|
default: 0
|
},
|
damping: {
|
type: [Number, String],
|
default: 20
|
},
|
friction: {
|
type: [Number, String],
|
default: 2
|
},
|
disabled: {
|
type: [Boolean, String],
|
default: false
|
},
|
scale: {
|
type: [Boolean, String],
|
default: false
|
},
|
scaleMin: {
|
type: [Number, String],
|
default: 0.5
|
},
|
scaleMax: {
|
type: [Number, String],
|
default: 10
|
},
|
scaleValue: {
|
type: [Number, String],
|
default: 1
|
},
|
animation: {
|
type: [Boolean, String],
|
default: true
|
}
|
},
|
data () {
|
return {
|
xSync: this._getPx(this.x),
|
ySync: this._getPx(this.y),
|
scaleValueSync: this._getScaleNumber(this.scaleValue),
|
width: 0,
|
height: 0,
|
minX: 0,
|
minY: 0,
|
maxX: 0,
|
maxY: 0
|
}
|
},
|
computed: {
|
dampingNumber () {
|
const val = Number(this.damping)
|
return isNaN(val) ? 20 : val
|
},
|
frictionNumber () {
|
const val = Number(this.friction)
|
return isNaN(val) || val <= 0 ? 2 : val
|
},
|
scaleMinNumber () {
|
const val = Number(this.scaleMin)
|
return isNaN(val) ? 0.5 : val
|
},
|
scaleMaxNumber () {
|
const val = Number(this.scaleMax)
|
return isNaN(val) ? 10 : val
|
},
|
xMove () {
|
return this.direction === 'all' || this.direction === 'horizontal'
|
},
|
yMove () {
|
return this.direction === 'all' || this.direction === 'vertical'
|
}
|
},
|
watch: {
|
x (val) {
|
this.xSync = this._getPx(val)
|
},
|
xSync (val) {
|
this._setX(val)
|
},
|
y (val) {
|
this.ySync = this._getPx(val)
|
},
|
ySync (val) {
|
this._setY(val)
|
},
|
scaleValue (val) {
|
this._setScaleValue(this._getScaleNumber(val))
|
},
|
scaleValueSync (val) {
|
this._setScaleValue(val)
|
},
|
scaleMinNumber () {
|
this._setScaleMinOrMax()
|
},
|
scaleMaxNumber () {
|
this._setScaleMinOrMax()
|
}
|
},
|
created: function () {
|
this._offset = {
|
x: 0,
|
y: 0
|
}
|
this._scaleOffset = {
|
x: 0,
|
y: 0
|
}
|
this._translateX = 0
|
this._translateY = 0
|
this._scale = 1
|
this._oldScale = 1
|
|
this._STD = new STD(1, 9 * Math.pow(this.dampingNumber, 2) / 40, this.dampingNumber)
|
this._friction = new Friction(1, this.frictionNumber)
|
this._declineX = new Decline()
|
this._declineY = new Decline()
|
this.__touchInfo = {
|
historyX: [0, 0],
|
historyY: [0, 0],
|
historyT: [0, 0]
|
}
|
this._rect = {
|
top: 0,
|
left: 0,
|
width: 0,
|
height: 0
|
}
|
},
|
mounted: function () {
|
this.touchtrack('_onTrack')
|
setTimeout(() => {
|
this._updateRect().then(() => {
|
this.setParent()
|
})
|
}, 100)
|
this._friction.reconfigure(1, this.frictionNumber)
|
this._STD.reconfigure(1, 9 * Math.pow(this.dampingNumber, 2) / 40, this.dampingNumber)
|
},
|
methods: {
|
_getPx (val) {
|
// if (/\d+[ur]px$/i.test(val)) {
|
// return uni.upx2px(parseFloat(val))
|
// }
|
return Number(val) || 0
|
},
|
_getScaleNumber (val) {
|
val = Number(val)
|
return isNaN(val) ? 1 : val
|
},
|
_setX: function (val) {
|
if (this.xMove) {
|
if (val + this._scaleOffset.x === this._translateX) {
|
return this._translateX
|
}
|
else {
|
if (this._SFA) {
|
this._SFA.cancel()
|
}
|
this._animationTo(val + this._scaleOffset.x, this.ySync + this._scaleOffset.y, this._scale)
|
}
|
}
|
return val
|
},
|
_setY: function (val) {
|
if (this.yMove) {
|
if (val + this._scaleOffset.y === this._translateY) {
|
return this._translateY
|
}
|
else {
|
if (this._SFA) {
|
this._SFA.cancel()
|
}
|
this._animationTo(this.xSync + this._scaleOffset.x, val + this._scaleOffset.y, this._scale)
|
}
|
}
|
return val
|
},
|
_setScaleMinOrMax: function () {
|
if (!this.scale) {
|
return false
|
}
|
this._updateScale(this._scale, true)
|
this._updateOldScale(this._scale)
|
},
|
_setScaleValue: function (scale) {
|
if (!this.scale) {
|
return false
|
}
|
scale = this._adjustScale(scale)
|
this._updateScale(scale, true)
|
this._updateOldScale(scale)
|
return scale
|
},
|
__handleTouchStart: function () {
|
if (!this._isScaling) {
|
if (!this.disabled) {
|
if (this._FA) {
|
this._FA.cancel()
|
}
|
if (this._SFA) {
|
this._SFA.cancel()
|
}
|
this.__touchInfo.historyX = [0, 0]
|
this.__touchInfo.historyY = [0, 0]
|
this.__touchInfo.historyT = [0, 0]
|
if (this.xMove) {
|
this.__baseX = this._translateX
|
}
|
if (this.yMove) {
|
this.__baseY = this._translateY
|
}
|
this._checkCanMove = null
|
this._firstMoveDirection = null
|
this._isTouching = true
|
}
|
}
|
},
|
__handleTouchMove: function (event) {
|
const self = this
|
if (!this._isScaling && !this.disabled && this._isTouching) {
|
let x = this._translateX
|
let y = this._translateY
|
if (this._firstMoveDirection === null) {
|
this._firstMoveDirection = Math.abs(event.detail.dx / event.detail.dy) > 1 ? 'htouchmove' : 'vtouchmove'
|
}
|
if (this.xMove) {
|
x = event.detail.dx + this.__baseX
|
this.__touchInfo.historyX.shift()
|
this.__touchInfo.historyX.push(x)
|
if (!this.yMove) {
|
if (!null !== this._checkCanMove) {
|
if (Math.abs(event.detail.dx / event.detail.dy) > 1) {
|
this._checkCanMove = false
|
}
|
else {
|
this._checkCanMove = true
|
}
|
}
|
}
|
}
|
if (this.yMove) {
|
y = event.detail.dy + this.__baseY
|
this.__touchInfo.historyY.shift()
|
this.__touchInfo.historyY.push(y)
|
if (!this.xMove) {
|
if (!null !== this._checkCanMove) {
|
if (Math.abs(event.detail.dy / event.detail.dx) > 1) {
|
this._checkCanMove = false
|
}
|
else {
|
this._checkCanMove = true
|
}
|
}
|
}
|
}
|
this.__touchInfo.historyT.shift()
|
this.__touchInfo.historyT.push(event.detail.timeStamp)
|
|
if (!this._checkCanMove) {
|
// event.preventDefault()
|
let source = 'touch'
|
if (x < this.minX) {
|
if (this.outOfBounds) {
|
source = 'touch-out-of-bounds'
|
x = this.minX - this._declineX.x(this.minX - x)
|
}
|
else {
|
x = this.minX
|
}
|
}
|
else if (x > this.maxX) {
|
if (this.outOfBounds) {
|
source = 'touch-out-of-bounds'
|
x = this.maxX + this._declineX.x(x - this.maxX)
|
}
|
else {
|
x = this.maxX
|
}
|
}
|
if (y < this.minY) {
|
if (this.outOfBounds) {
|
source = 'touch-out-of-bounds'
|
y = this.minY - this._declineY.x(this.minY - y)
|
}
|
else {
|
y = this.minY
|
}
|
}
|
else {
|
if (y > this.maxY) {
|
if (this.outOfBounds) {
|
source = 'touch-out-of-bounds'
|
y = this.maxY + this._declineY.x(y - this.maxY)
|
}
|
else {
|
y = this.maxY
|
}
|
}
|
}
|
_requestAnimationFrame(function () {
|
self._setTransform(x, y, self._scale, source)
|
})
|
}
|
}
|
},
|
__handleTouchEnd: function () {
|
const self = this
|
if (!this._isScaling && !this.disabled && this._isTouching) {
|
this._isTouching = false
|
if (!this._checkCanMove && !this._revise('out-of-bounds') && this.inertia) {
|
const xv = 1000 * (this.__touchInfo.historyX[1] - this.__touchInfo.historyX[0]) / (this.__touchInfo.historyT[1] - this.__touchInfo.historyT[0])
|
const yv = 1000 * (this.__touchInfo.historyY[1] - this.__touchInfo.historyY[0]) / (this.__touchInfo.historyT[1] - this.__touchInfo.historyT[0])
|
this._friction.setV(xv, yv)
|
this._friction.setS(this._translateX, this._translateY)
|
const x0 = this._friction.delta().x
|
const y0 = this._friction.delta().y
|
let x = x0 + this._translateX
|
let y = y0 + this._translateY
|
if (x < this.minX) {
|
x = this.minX
|
y = this._translateY + (this.minX - this._translateX) * y0 / x0
|
}
|
else {
|
if (x > this.maxX) {
|
x = this.maxX
|
y = this._translateY + (this.maxX - this._translateX) * y0 / x0
|
}
|
}
|
if (y < this.minY) {
|
y = this.minY
|
x = this._translateX + (this.minY - this._translateY) * x0 / y0
|
}
|
else {
|
if (y > this.maxY) {
|
y = this.maxY
|
x = this._translateX + (this.maxY - this._translateY) * x0 / y0
|
}
|
}
|
this._friction.setEnd(x, y)
|
this._FA = g(this._friction, function () {
|
const t = self._friction.s()
|
const x = t.x
|
const y = t.y
|
self._setTransform(x, y, self._scale, 'friction')
|
}, function () {
|
self._FA.cancel()
|
})
|
}
|
}
|
},
|
_onTrack: function (event) {
|
switch (event.detail.state) {
|
case 'start':
|
this.__handleTouchStart()
|
break
|
case 'move':
|
this.__handleTouchMove(event)
|
break
|
case 'end':
|
this.__handleTouchEnd()
|
}
|
},
|
_getLimitXY: function (x, y) {
|
let outOfBounds = false
|
if (x > this.maxX) {
|
x = this.maxX
|
outOfBounds = true
|
}
|
else {
|
if (x < this.minX) {
|
x = this.minX
|
outOfBounds = true
|
}
|
}
|
if (y > this.maxY) {
|
y = this.maxY
|
outOfBounds = true
|
}
|
else {
|
if (y < this.minY) {
|
y = this.minY
|
outOfBounds = true
|
}
|
}
|
return {
|
x,
|
y,
|
outOfBounds
|
}
|
},
|
setParent: function () {
|
if (!this.$parent.__isMounted) {
|
return
|
}
|
if (this._FA) {
|
this._FA.cancel()
|
}
|
if (this._SFA) {
|
this._SFA.cancel()
|
}
|
const scale = this.scale ? this.scaleValueSync : 1
|
this._updateOffset()
|
this._updateWH(scale)
|
this._updateBoundary()
|
this._translateX = this.xSync + this._scaleOffset.x
|
this._translateY = this.ySync + this._scaleOffset.y
|
const limitXY = this._getLimitXY(this._translateX, this._translateY)
|
const x = limitXY.x
|
const y = limitXY.y
|
this._setTransform(x, y, scale, '', true)
|
this._updateOldScale(scale)
|
},
|
_updateOffset: function () {
|
this._offset.x = this._rect.left - this.$parent.left
|
this._offset.y = this._rect.top - this.$parent.top
|
},
|
_updateWH: function (scale) {
|
scale = scale || this._scale
|
scale = this._adjustScale(scale)
|
const rect = this._rect
|
this.height = rect.height / this._scale
|
this.width = rect.width / this._scale
|
const height = this.height * scale
|
const width = this.width * scale
|
this._scaleOffset.x = (width - this.width) / 2
|
this._scaleOffset.y = (height - this.height) / 2
|
},
|
_updateBoundary: function () {
|
const x = 0 - this._offset.x + this._scaleOffset.x
|
const width = this.$parent.width - this.width - this._offset.x - this._scaleOffset.x
|
this.minX = Math.min(x, width)
|
this.maxX = Math.max(x, width)
|
const y = 0 - this._offset.y + this._scaleOffset.y
|
const height = this.$parent.height - this.height - this._offset.y - this._scaleOffset.y
|
this.minY = Math.min(y, height)
|
this.maxY = Math.max(y, height)
|
},
|
_beginScale: function () {
|
this._isScaling = true
|
},
|
_endScale: function () {
|
this._isScaling = false
|
this._updateOldScale(this._scale)
|
},
|
_setScale: function (scale) {
|
if (this.scale) {
|
scale = this._adjustScale(scale)
|
scale = this._oldScale * scale
|
this._beginScale()
|
this._updateScale(scale)
|
}
|
},
|
_updateScale: function (scale, animat) {
|
const self = this
|
if (this.scale) {
|
scale = this._adjustScale(scale)
|
this._updateWH(scale)
|
this._updateBoundary()
|
const limitXY = this._getLimitXY(this._translateX, this._translateY)
|
const x = limitXY.x
|
const y = limitXY.y
|
if (animat) {
|
this._animationTo(x, y, scale, '', true, true)
|
}
|
else {
|
_requestAnimationFrame(function () {
|
self._setTransform(x, y, scale, '', true, true)
|
})
|
}
|
}
|
},
|
_updateOldScale: function (scale) {
|
this._oldScale = scale
|
},
|
_adjustScale: function (scale) {
|
scale = Math.max(0.5, this.scaleMinNumber, scale)
|
scale = Math.min(10, this.scaleMaxNumber, scale)
|
return scale
|
},
|
_animationTo: function (x, y, scale, source, r, o) {
|
const self = this
|
if (this._FA) {
|
this._FA.cancel()
|
}
|
if (this._SFA) {
|
this._SFA.cancel()
|
}
|
if (!this.xMove) {
|
x = this._translateX
|
}
|
if (!this.yMove) {
|
y = this._translateY
|
}
|
if (!this.scale) {
|
scale = this._scale
|
}
|
const limitXY = this._getLimitXY(x, y)
|
x = limitXY.x
|
y = limitXY.y
|
if (!this.animation) {
|
this._setTransform(x, y, scale, source, r, o)
|
return
|
}
|
this._STD._springX._solution = null
|
this._STD._springY._solution = null
|
this._STD._springScale._solution = null
|
this._STD._springX._endPosition = this._translateX
|
this._STD._springY._endPosition = this._translateY
|
this._STD._springScale._endPosition = this._scale
|
this._STD.setEnd(x, y, scale, 1)
|
this._SFA = g(this._STD, function () {
|
const data = self._STD.x()
|
const x = data.x
|
const y = data.y
|
const scale = data.scale
|
self._setTransform(x, y, scale, source, r, o)
|
}, function () {
|
self._SFA.cancel()
|
})
|
},
|
_revise: function (source) {
|
const limitXY = this._getLimitXY(this._translateX, this._translateY)
|
const x = limitXY.x
|
const y = limitXY.y
|
const outOfBounds = limitXY.outOfBounds
|
if (outOfBounds) {
|
this._animationTo(x, y, this._scale, source)
|
}
|
return outOfBounds
|
},
|
_setTransform: function (x, y, scale, source = '', r, o) {
|
if (!(x !== null && x.toString() !== 'NaN' && typeof x === 'number')) {
|
x = this._translateX || 0
|
}
|
if (!(y !== null && y.toString() !== 'NaN' && typeof y === 'number')) {
|
y = this._translateY || 0
|
}
|
x = Number(x.toFixed(1))
|
y = Number(y.toFixed(1))
|
scale = Number(scale.toFixed(1))
|
if (!(this._translateX === x && this._translateY === y)) {
|
if (!r) {
|
this.$trigger('change', {
|
x: v(x, this._scaleOffset.x),
|
y: v(y, this._scaleOffset.y),
|
source: source
|
})
|
}
|
}
|
if (!this.scale) {
|
scale = this._scale
|
}
|
scale = this._adjustScale(scale)
|
scale = +scale.toFixed(3)
|
if (o && scale !== this._scale) {
|
this.$trigger('scale', {
|
x: x,
|
y: y,
|
scale: scale
|
})
|
}
|
const transform = `translate(${x}px, ${y}px) scale(${scale})`
|
animation.transition(this.$refs.el, {
|
styles: {
|
transform
|
},
|
duration: 0,
|
delay: 0
|
})
|
this._translateX = x
|
this._translateY = y
|
this._scale = scale
|
},
|
_touchstart () {
|
this.$parent.touchItem = this
|
},
|
_getComponentSize (el) {
|
return new Promise((resolve) => {
|
dom.getComponentRect(el, ({ size }) => {
|
resolve(size)
|
})
|
})
|
},
|
_updateRect () {
|
return this._getComponentSize(this.$refs.el).then(rect => {
|
this._rect = rect
|
})
|
}
|
},
|
render (createElement) {
|
const events = {
|
touchstart: this._touchstart
|
// touchend: this.touchend,
|
// touchend: this.touchend
|
}
|
return createElement('div', this._g({
|
ref: 'el',
|
on: events,
|
staticClass: ['uni-movable-view'],
|
staticStyle: {
|
transformOrigin: 'center'
|
}
|
}, this.$listeners), this.$slots.default, 2)
|
},
|
style: {
|
'uni-movable-view': {
|
position: 'absolute',
|
top: '0px',
|
left: '0px',
|
width: '10px',
|
height: '10px'
|
}
|
}
|
}
|
}
|
|
export default function init (Vue, weex) {
|
Vue.component('movable-view', getMovableView(weex))
|
}
|