/* 简单使用时: */ import { ref, unref, watch, watchEffect, onMounted, onBeforeUnmount } from 'vue' export default function useSmoothSwipe({ scrollTargetRef, maxTranslateLength, viewportWidth = document.documentElement.clientWidth, speedFactor = 1, damping = 0.003, enableAutoMoving = false } = {}) { const isOperating = ref(false) const haveSwipedThisTime = ref(false) let haveSwipedThisTimeTimeoutId = null let lastMoveEventTimeStamp = 0 let lastCursorPos = 0 let moveSpeed = ref(0) let translateLength = ref(0) let maxTranslateLengthInner = Infinity let isAutoMoving = ref(false) let beginAutoMoveIntervalId = null let lastAnimationTimeStamp = 0 let animationFrameId = null // watch(moveSpeed, (v) => { if (!haveSwipedThisTime.value && v) { haveSwipedThisTime.value = true } }) watch(isOperating, (v) => { if (v) { clearTimeout(haveSwipedThisTimeTimeoutId) haveSwipedThisTime.value = false } else { clearTimeout(haveSwipedThisTimeTimeoutId) haveSwipedThisTimeTimeoutId = setTimeout(() => { haveSwipedThisTime.value = false }, 333) } }) // compute maxTranslateLengthInner watchEffect(() => { if (maxTranslateLength) { maxTranslateLengthInner = unref(maxTranslateLength) - unref(viewportWidth) if (maxTranslateLengthInner < 0) { maxTranslateLengthInner = 0 } } }) function computeMaxTranslateLengthInnerByScrollTarget() { maxTranslateLengthInner = scrollTargetRef.value.scrollWidth - unref(viewportWidth) } function onWheel(e) { moveSpeed.value += e.deltaY / 200 } function speedUp(v) { console.log('sdf') moveSpeed.value += v } /** * swipe begin */ function onMouseDown(e) { isOperating.value = true moveSpeed.value = 0 lastMoveEventTimeStamp = 0 lastAnimationTimeStamp = Date.now() lastCursorPos = e.clientX if (beginAutoMoveIntervalId) { clearInterval(beginAutoMoveIntervalId) beginAutoMoveIntervalId = null } if (isAutoMoving.value) { isAutoMoving.value = false } } function onTouchStart(e) { isOperating.value = true moveSpeed.value = 0 lastMoveEventTimeStamp = 0 lastAnimationTimeStamp = Date.now() lastCursorPos = e.changedTouches[0].clientX if (beginAutoMoveIntervalId) { clearInterval(beginAutoMoveIntervalId) beginAutoMoveIntervalId = null } if (isAutoMoving.value) { isAutoMoving.value = false } } /** * swiping */ function onMouseMove(e) { if (isOperating.value) { // 疯狂操作的极端情况下两个时间戳之间的时差会不合理,甚至为0 if (lastMoveEventTimeStamp && (e.timeStamp - lastMoveEventTimeStamp > 1)) { // 更新speed const currentMoveSpeed = - (e.clientX - lastCursorPos) / (e.timeStamp - lastMoveEventTimeStamp) * unref(speedFactor) moveSpeed.value = moveSpeed.value * 0.9 + currentMoveSpeed * 0.1 lastCursorPos = e.clientX } lastMoveEventTimeStamp = e.timeStamp } } function onTouchMove(e) { if (isOperating.value && e.changedTouches.length === 1) { // 疯狂操作的极端情况下两个时间戳之间的时差会不合理,甚至为0 if (lastMoveEventTimeStamp && (e.timeStamp - lastMoveEventTimeStamp > 1)) { // 更新speed const currentMoveSpeed = - (e.changedTouches[0].clientX - lastCursorPos) / (e.timeStamp - lastMoveEventTimeStamp) * unref(speedFactor) moveSpeed.value = moveSpeed.value * 0.9 + currentMoveSpeed * 0.1 lastCursorPos = e.changedTouches[0].clientX } lastMoveEventTimeStamp = e.timeStamp } } /** * swipe end */ function onMouseUp(e) { isOperating.value = false e.stopPropagation() beginAutoMoveIntervalId = setInterval(() => { if (moveSpeed.value === 0 && unref(enableAutoMoving)) { isAutoMoving.value = true } }, 2000) } function onTouchEnd(e) { isOperating.value = false beginAutoMoveIntervalId = setInterval(() => { if (moveSpeed.value === 0 && unref(enableAutoMoving)) { isAutoMoving.value = true } }, 2000) } function onMouseLeave() { isOperating.value = false beginAutoMoveIntervalId = setInterval(() => { if (moveSpeed.value === 0 && unref(enableAutoMoving)) { isAutoMoving.value = true } }, 2000) } function onTouchCancel() { isOperating.value = false beginAutoMoveIntervalId = setInterval(() => { if (moveSpeed.value === 0 && unref(enableAutoMoving)) { isAutoMoving.value = true } }, 2000) } beginAutoMoveIntervalId = setInterval(() => { if (moveSpeed.value === 0 && unref(enableAutoMoving)) { isAutoMoving.value = true } }, 2000) watch(isAutoMoving, (vNew) => { if (vNew) { moveSpeed.value = 0.03 } else { moveSpeed.value = 0 } }) onBeforeUnmount(() => { clearInterval(beginAutoMoveIntervalId) }) // 在每一帧更新镜头速度、位置 function animationFrameTask() { const timeStamp = Date.now() const timeElapsed = timeStamp - lastAnimationTimeStamp if (!isAutoMoving.value) { // 速度减慢 if (moveSpeed.value > 0) { moveSpeed.value -= unref(damping) * timeElapsed if (moveSpeed.value < 0) { moveSpeed.value = 0 } } else if (moveSpeed.value < 0) { moveSpeed.value += unref(damping) * timeElapsed if (moveSpeed.value > 0) { moveSpeed.value = 0 } } } // 根据速度更新位置 translateLength.value += moveSpeed.value * timeElapsed if (translateLength.value < 0) { translateLength.value = 0 moveSpeed.value = 0 } else if (translateLength.value > maxTranslateLengthInner) { if (isAutoMoving.value) { translateLength.value = 0 } else { translateLength.value = maxTranslateLengthInner moveSpeed.value = 0 } } lastAnimationTimeStamp = timeStamp animationFrameId = requestAnimationFrame(animationFrameTask) } onMounted(() => { animationFrameId = requestAnimationFrame(animationFrameTask) }) onBeforeUnmount(() => { cancelAnimationFrame(animationFrameId) }) if (scrollTargetRef) { watch(scrollTargetRef, (v) => { if (v) { v.addEventListener('wheel', onWheel) v.addEventListener('mousedown', onMouseDown) v.addEventListener('mouseleave', onMouseLeave) v.addEventListener('mousemove', onMouseMove) v.addEventListener('mouseup', onMouseUp) // v.addEventListener('touchstart', onTouchStart) // v.addEventListener('touchmove', onTouchMove) // v.addEventListener('touchend', onTouchEnd) // v.addEventListener('touchcancel', onTouchCancel) } }, { immediate: true, }) watch(translateLength, (v) => { scrollTargetRef.value && (scrollTargetRef.value.scrollLeft = v) }) } return { onWheel, onMouseDown, onMouseLeave, onMouseMove, onMouseUp, onTouchStart, onTouchMove, onTouchEnd, onTouchCancel, translateLength, haveSwipedThisTime, computeMaxTranslateLengthInnerByScrollTarget, speedUp } }