import { ref, unref, watch, watchEffect, onMounted, onBeforeUnmount } from 'vue' import TWEEN from '@tweenjs/tween.js' export default function useSmoothSwipe({ scrollTargetRef, maxTranslateLength, viewportWidth = document.documentElement.clientWidth, anchorPosList, initialAnchorIdx = 0, } = {}) { const isOperating = ref(false) const haveSwipedThisTime = ref(false) let hasOperatedThisTimeTimeoutId = null let translateLength = ref(anchorPosList[initialAnchorIdx]) let maxTranslateLengthInner = Infinity let animationFrameId = null let tween = null let isTweening = false let fingerPosXWhenTouchStart = 0 let anchorPosListInner = [...anchorPosList].sort((a, b) => { return a > b }) let anchorPosListInnerReverse = [...anchorPosListInner].reverse() const currentAnchorIdx = ref(initialAnchorIdx) const goingToAnchorIdx = ref(null) watch(isOperating, (v) => { if (v) { clearTimeout(hasOperatedThisTimeTimeoutId) haveSwipedThisTime.value = false } else { clearTimeout(hasOperatedThisTimeTimeoutId) hasOperatedThisTimeTimeoutId = setTimeout(() => { haveSwipedThisTime.value = false }, 333) } }) // compute maxTranslateLengthInner todo: 不能用if包裹吧? if (maxTranslateLength) { watchEffect(() => { maxTranslateLengthInner = unref(maxTranslateLength) - unref(viewportWidth) if (maxTranslateLengthInner < 0) { maxTranslateLengthInner = 0 } }) } else if (scrollTargetRef) { watchEffect(() => { if (scrollTargetRef.value) { maxTranslateLengthInner = scrollTargetRef.value.scrollWidth - unref(viewportWidth) if (maxTranslateLengthInner < 0) { maxTranslateLengthInner = 0 } } else { maxTranslateLengthInner = Infinity } }) } function onTouchStart(e) { isOperating.value = true fingerPosXWhenTouchStart = e.changedTouches[0].pageX } const onTouchMove = (event) => { if (isTweening) { return } let currentX = event.changedTouches[0].pageX let dX = currentX - fingerPosXWhenTouchStart if (dX < -1) { // 左滑,要往右移动 const destinationIdx = anchorPosListInner.findIndex((item) => { return item - translateLength.value > 1 }) if ((destinationIdx !== -1) && !isTweening) { isTweening = true tween = new TWEEN.Tween(translateLength) tween.to({ value: anchorPosListInner[destinationIdx], }, 1200) tween.easing(TWEEN.Easing.Cubic.InOut) tween.start() goingToAnchorIdx.value = destinationIdx tween.onComplete(() => { isTweening = false currentAnchorIdx.value = destinationIdx goingToAnchorIdx.value = null }) } } else if (dX > 1) { const destinationIdx = anchorPosListInnerReverse.findIndex((item) => { return item - translateLength.value < -1 }) if ((destinationIdx !== -1) && !isTweening) { isTweening = true tween = new TWEEN.Tween(translateLength) tween.to({ value: anchorPosListInnerReverse[destinationIdx], }, 1200) tween.easing(TWEEN.Easing.Cubic.InOut) tween.start() goingToAnchorIdx.value = anchorPosList.length - destinationIdx - 1 tween.onComplete(() => { isTweening = false currentAnchorIdx.value = anchorPosList.length - destinationIdx - 1 goingToAnchorIdx.value = null }) } } } function onTouchEnd(e) { isOperating.value = false } function onTouchCancel() { isOperating.value = false } // 在每一帧更新镜头速度、位置 function animationFrameTask() { TWEEN.update() animationFrameId = requestAnimationFrame(animationFrameTask) } onMounted(() => { animationFrameId = requestAnimationFrame(animationFrameTask) }) onBeforeUnmount(() => { cancelAnimationFrame(animationFrameId) tween && tween.stop() }) if (scrollTargetRef) { // todo: 不能用if包裹吧? watch(scrollTargetRef, (v) => { if (v) { v.addEventListener('touchstart', onTouchStart) v.addEventListener('touchmove', onTouchMove) v.addEventListener('touchend', onTouchEnd) v.addEventListener('touchcancel', onTouchCancel) } }, { immediate: true, }) } return { onTouchStart, onTouchMove, onTouchEnd, onTouchCancel, translateLength, haveSwipedThisTime, currentAnchorIdx, goingToAnchorIdx, } }