/**
* 封装鼠标触摸事件。
* @example
* const el = document.querySelector('#container')
* new Slide(el, 200, 200)
* el.addEventListener('slidemove', (e) => {
* console.log(e.detail)
* // {
* // // 滑动开始的点
* // startx: ,
* // starty: ,
* // // 滑动过程的点
* // endx: ,
* // endy: ,
* // // 滑动过程中与上一个点的距离
* // dx: ,
* // dy: ,
* // // 滑动过程中与开始点的距离
* // offsetx: ,
* // offsety:
* // }
* })
*/
export class Slide {
// dom节点
el = null
// 手指移动过程中上一个坐标
prePoint = {
x: 0,
y: 0
}
// 自定义用户数据
customData = {
// 滑动开始的点
startx: 0,
starty: 0,
// 滑动过程的点
endx: 0,
endy: 0,
// 滑动过程中与上一个点的距离
dx: 0,
dy: 0,
// 滑动过程中与开始点的距离
offsetx: 0,
offsety: 0
}
/**
* @param {HTMLElement} el - html 节点。
* @param {Number} maxSlideDx - x 方向最大移动距离。
* @param {Number} maxSlideDy - y 方向最大移动距离。
* @param {Boolean} [limitArea=false] - 是否限制区域。
* 如果为true,当滑动超过 maxSlideDx 限定的区域内,
* 获取到的 dx 为 0,offsetx 为 ±maxSlideDx。
* 如果为false,当滑动超过 maxSlideDx 限定的区域内,
* 获取到的 dx 为 正常值,offsetx 为 ±maxSlideDx。
*/
constructor (
el,
options = {
limitArea: false
}
) {
this.el = el
// 单次的最大横向滑动距离
this.maxSlideDx = options.maxSlideDx
this.maxSlideDy = options.maxSlideDy
this.limitArea = options.limitArea
this._init()
}
_init () {
// 注册滑动开始、过程、结束事件
this.slidestart = new CustomEvent('slidestart', {
detail: this.customData,
bubbles: true,
cancelable: true
})
this.slidemove = new CustomEvent('slidemove', {
detail: this.customData,
bubbles: true,
cancelable: true
})
this.slideend = new CustomEvent('slideend', {
detail: this.customData,
bubbles: true,
cancelable: true
})
// 监听原生触摸事件
this.setSupportsPassive()
if ('ontouchstart' in window) {
this._on(this.el, 'touchstart', this._start)
this._on(this.el, 'touchmove', this._move)
this._on(this.el, 'touchend', this._end)
} else {
this._on(window, 'mousedown', this._start)
this._on(window, 'mousemove', this._move)
this._on(window, 'mouseup', this._end)
}
}
_on (el, event, fn) {
el.addEventListener(
event,
fn,
this.supportsPassive
? {
capture: false,
passive: false
}
: false
)
}
_off (el, event, fn) {
el.removeEventListener(
event,
fn,
this.supportsPassive
? {
capture: false,
passive: false
}
: false
)
}
setSupportsPassive () {
try {
const opts = Object.defineProperty({}, 'passive', {
get: () => {
this.supportsPassive = true
return true
}
})
window.addEventListener('testPassive', null, opts)
window.removeEventListener('testPassive', null, opts)
} catch (e) {
console.error(e)
}
}
checkNode (node) {
if (!node) {
return false
}
if (node === this.el) {
return true
}
return this.checkNode(node.parentNode)
}
_start = (e) => {
e.preventDefault()
let startx = 0
let starty = 0
if (e.type === 'mousedown') {
if (e.button !== 0) {
return
}
if (this.checkNode(e.target)) {
this.canSlide = true
} else {
this.canSlide = false
return
}
startx = e.pageX
starty = e.pageY
} else {
e.preventDefault()
startx = e.targetTouches[0].pageX
starty = e.targetTouches[0].pageY
}
// 初始化data
Object.assign(this.customData, {
startx,
starty,
endx: startx,
endy: starty,
dx: 0,
dy: 0,
offsetx: 0,
offsety: 0
})
this.prePoint = {
x: startx,
y: starty
}
this.el.dispatchEvent(this.slidestart)
}
_move = (e) => {
let endx = 0
let endy = 0
if (e.type === 'mousemove') {
if (!this.canSlide) {
return
}
endx = e.pageX
endy = e.pageY
} else {
endx = e.targetTouches[0].pageX
endy = e.targetTouches[0].pageY
}
// 相较于上一次touchmove点的距离
let dx = endx - this.prePoint.x
let dy = endy - this.prePoint.y
let offsetx = 0
let offsety = 0
// 单次滑动过程不能超过设定maxSlideDx值
if (this.maxSlideDx) {
if (this.customData.offsetx + dx >= this.maxSlideDx) {
dx = this.maxSlideDx - this.customData.offsetx
offsetx = this.maxSlideDx
} else if (this.customData.offsetx + dx <= -this.maxSlideDx) {
dx = -this.maxSlideDx - this.customData.offsetx
offsetx = -this.maxSlideDx
} else {
if (!this.limitArea) {
offsetx = this.customData.offsetx + dx
} else {
const x = endx - this.customData.startx
// 鼠标在界限范内
if (x > -this.maxSlideDx && x < this.maxSlideDx) {
// 处理边界问题
// 如果是上次鼠标位置在左边界外面,然后移动到里面
// 修正 dx 的值
if (this.prePoint.x - this.customData.startx < -this.maxSlideDx) {
dx = x - -this.maxSlideDx
} else if (
this.prePoint.x - this.customData.startx >
this.maxSlideDx
) {
dx = x - this.maxSlideDx
}
offsetx = x
} else {
offsetx = this.customData.offsetx
dx = 0
}
}
}
} else {
offsetx = endx - this.customData.startx
}
// 单次滑动过程不能超过设定maxSlideDy值
if (this.customData.offsety + dy > this.maxSlideDy) {
dy = this.maxSlideDy - this.customData.offsety
offsety = this.maxSlideDy
} else if (this.customData.offsety + dy < -this.maxSlideDy) {
dy = -this.maxSlideDy - this.customData.offsety
offsety = -this.maxSlideDy
} else {
offsety = endy - this.customData.starty
}
Object.assign(this.customData, {
endx,
endy,
dx,
dy,
offsetx,
offsety
})
this.prePoint = {
x: endx,
y: endy
}
this.el.dispatchEvent(this.slidemove)
}
_end = (e) => {
let endx = 0
let endy = 0
if (e.type === 'mouseup') {
if (!this.canSlide) {
return
}
endx = e.pageX
endy = e.pageY
this.canSlide = false
} else {
endx = e.changedTouches[0].pageX
endy = e.changedTouches[0].pageY
}
Object.assign(this.customData, {
endx,
endy
})
this.el.dispatchEvent(this.slideend)
}
setMaxSlideDx (ds) {
this.maxSlideDx = ds
}
setMaxSlideDy (ds) {
this.maxSlideDy = ds
}
// 销毁自定义事件
destroy () {
this._off(this.el, 'touchstart', this._start)
this._off(this.el, 'touchmove', this._move)
this._off(this.el, 'touchend', this._end)
this._off(window, 'mousedown', this._start)
this._off(window, 'mousemove', this._move)
this._off(window, 'mouseup', this._end)
}
}