useSmoothSwipe.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. import { ref, unref, watch, watchEffect, onMounted, onBeforeUnmount } from 'vue'
  2. import TWEEN from '@tweenjs/tween.js'
  3. export default function useSmoothSwipe({
  4. scrollTargetRef,
  5. maxTranslateLength,
  6. viewportWidth = document.documentElement.clientWidth,
  7. anchorPosList,
  8. initialAnchorIdx = 0,
  9. } = {}) {
  10. const isOperating = ref(false)
  11. const haveSwipedThisTime = ref(false)
  12. let hasOperatedThisTimeTimeoutId = null
  13. let translateLength = ref(anchorPosList[initialAnchorIdx])
  14. let maxTranslateLengthInner = Infinity
  15. let animationFrameId = null
  16. let tween = null
  17. let isTweening = false
  18. let fingerPosXWhenTouchStart = 0
  19. let anchorPosListInner = [...anchorPosList].sort((a, b) => {
  20. return a > b
  21. })
  22. let anchorPosListInnerReverse = [...anchorPosListInner].reverse()
  23. const currentAnchorIdx = ref(initialAnchorIdx)
  24. const goingToAnchorIdx = ref(null)
  25. watch(isOperating, (v) => {
  26. if (v) {
  27. clearTimeout(hasOperatedThisTimeTimeoutId)
  28. haveSwipedThisTime.value = false
  29. } else {
  30. clearTimeout(hasOperatedThisTimeTimeoutId)
  31. hasOperatedThisTimeTimeoutId = setTimeout(() => {
  32. haveSwipedThisTime.value = false
  33. }, 333)
  34. }
  35. })
  36. // compute maxTranslateLengthInner todo: 不能用if包裹吧?
  37. if (maxTranslateLength) {
  38. watchEffect(() => {
  39. maxTranslateLengthInner = unref(maxTranslateLength) - unref(viewportWidth)
  40. if (maxTranslateLengthInner < 0) {
  41. maxTranslateLengthInner = 0
  42. }
  43. })
  44. } else if (scrollTargetRef) {
  45. watchEffect(() => {
  46. if (scrollTargetRef.value) {
  47. maxTranslateLengthInner = scrollTargetRef.value.scrollWidth - unref(viewportWidth)
  48. if (maxTranslateLengthInner < 0) {
  49. maxTranslateLengthInner = 0
  50. }
  51. } else {
  52. maxTranslateLengthInner = Infinity
  53. }
  54. })
  55. }
  56. function onTouchStart(e) {
  57. isOperating.value = true
  58. fingerPosXWhenTouchStart = e.changedTouches[0].pageX
  59. }
  60. const onTouchMove = (event) => {
  61. if (isTweening) {
  62. return
  63. }
  64. let currentX = event.changedTouches[0].pageX
  65. let dX = currentX - fingerPosXWhenTouchStart
  66. if (dX < -1) { // 左滑,要往右移动
  67. const destinationIdx = anchorPosListInner.findIndex((item) => {
  68. return item - translateLength.value > 1
  69. })
  70. if ((destinationIdx !== -1) && !isTweening) {
  71. isTweening = true
  72. tween = new TWEEN.Tween(translateLength)
  73. tween.to({
  74. value: anchorPosListInner[destinationIdx],
  75. }, 1200)
  76. tween.easing(TWEEN.Easing.Cubic.InOut)
  77. tween.start()
  78. goingToAnchorIdx.value = destinationIdx
  79. tween.onComplete(() => {
  80. isTweening = false
  81. currentAnchorIdx.value = destinationIdx
  82. goingToAnchorIdx.value = null
  83. })
  84. }
  85. } else if (dX > 1) {
  86. const destinationIdx = anchorPosListInnerReverse.findIndex((item) => {
  87. return item - translateLength.value < -1
  88. })
  89. if ((destinationIdx !== -1) && !isTweening) {
  90. isTweening = true
  91. tween = new TWEEN.Tween(translateLength)
  92. tween.to({
  93. value: anchorPosListInnerReverse[destinationIdx],
  94. }, 1200)
  95. tween.easing(TWEEN.Easing.Cubic.InOut)
  96. tween.start()
  97. goingToAnchorIdx.value = anchorPosList.length - destinationIdx - 1
  98. tween.onComplete(() => {
  99. isTweening = false
  100. currentAnchorIdx.value = anchorPosList.length - destinationIdx - 1
  101. goingToAnchorIdx.value = null
  102. })
  103. }
  104. }
  105. }
  106. function onTouchEnd(e) {
  107. isOperating.value = false
  108. }
  109. function onTouchCancel() {
  110. isOperating.value = false
  111. }
  112. // 在每一帧更新镜头速度、位置
  113. function animationFrameTask() {
  114. TWEEN.update()
  115. animationFrameId = requestAnimationFrame(animationFrameTask)
  116. }
  117. onMounted(() => {
  118. animationFrameId = requestAnimationFrame(animationFrameTask)
  119. })
  120. onBeforeUnmount(() => {
  121. cancelAnimationFrame(animationFrameId)
  122. tween && tween.stop()
  123. })
  124. if (scrollTargetRef) { // todo: 不能用if包裹吧?
  125. watch(scrollTargetRef, (v) => {
  126. if (v) {
  127. v.addEventListener('touchstart', onTouchStart)
  128. v.addEventListener('touchmove', onTouchMove)
  129. v.addEventListener('touchend', onTouchEnd)
  130. v.addEventListener('touchcancel', onTouchCancel)
  131. }
  132. }, {
  133. immediate: true,
  134. })
  135. }
  136. return {
  137. onTouchStart,
  138. onTouchMove,
  139. onTouchEnd,
  140. onTouchCancel,
  141. translateLength,
  142. haveSwipedThisTime,
  143. currentAnchorIdx,
  144. goingToAnchorIdx,
  145. }
  146. }