LampSlider.vue 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. <template>
  2. <div class="slider-container" ref="slider-container">
  3. <div class="slider-track">
  4. <div
  5. class="slider-thumb"
  6. @touchstart="startDrag"
  7. @touchmove="onDrag"
  8. @touchend="endDrag"
  9. ref="slider-thumb"
  10. :style="{ left: `${thumbLeft}px` }"></div>
  11. </div>
  12. <!-- <div class="slider-value">{{ internalValue }}%</div> -->
  13. </div>
  14. </template>
  15. <script setup>
  16. import { computed, defineModel, onMounted, onUnmounted, ref, useTemplateRef } from 'vue'
  17. const model = defineModel({ default: 50 })
  18. const internalValue = ref(model.value)
  19. const isDragging = ref(false)
  20. const sliderContainer = useTemplateRef('slider-container')
  21. const sliderThumb = useTemplateRef('slider-thumb')
  22. const max = ref(0)
  23. const min = ref(0)
  24. const emit = defineEmits(['onEnd'])
  25. const touchstartPos = ref(0)
  26. const sliderThumbLeft = ref(0)
  27. const sliderContainerLeft = ref(0)
  28. let animationFrameId = null
  29. const thumbLeft = computed(() => {
  30. return (internalValue.value / 100) * (max.value - min.value)
  31. })
  32. // 初始化滑块位置
  33. const initSlider = () => {
  34. max.value = sliderContainer.value?.getBoundingClientRect().width
  35. min.value = sliderThumb.value?.getBoundingClientRect().width
  36. sliderContainerLeft.value = sliderContainer.value?.getBoundingClientRect().left
  37. }
  38. const startDrag = event => {
  39. const touch = event.touches[0]
  40. touchstartPos.value = touch.clientX
  41. sliderThumbLeft.value = sliderThumb.value?.getBoundingClientRect().left
  42. isDragging.value = true
  43. initSlider()
  44. onDrag(event)
  45. }
  46. const onDrag = event => {
  47. if (!isDragging.value) return
  48. const touch = event.touches[0]
  49. const sliderRect = sliderContainer.value.getBoundingClientRect()
  50. const maxRange = max.value - min.value
  51. let offsetX = touch.clientX - touchstartPos.value + sliderThumbLeft.value - sliderContainerLeft.value
  52. offsetX = Math.max(0, Math.min(offsetX, maxRange))
  53. if (animationFrameId) {
  54. cancelAnimationFrame(animationFrameId)
  55. }
  56. animationFrameId = requestAnimationFrame(() => {
  57. model.value = internalValue.value = Math.round((offsetX / (max.value - min.value)) * 100)
  58. })
  59. }
  60. const endDrag = () => {
  61. isDragging.value = false
  62. if (animationFrameId) {
  63. cancelAnimationFrame(animationFrameId)
  64. }
  65. emit('onEnd', internalValue.value)
  66. }
  67. // 监听DOM插入
  68. let visibilityObserver = null
  69. // 监听DOM可见性
  70. const observeVisibility = () => {
  71. const observer = new IntersectionObserver(entries => {
  72. entries.forEach(entry => {
  73. if (entry.isIntersecting) {
  74. initSlider()
  75. }
  76. })
  77. })
  78. if (sliderContainer.value) {
  79. observer.observe(sliderContainer.value)
  80. }
  81. return observer
  82. }
  83. onMounted(() => {
  84. // 启动观察器
  85. visibilityObserver = observeVisibility()
  86. })
  87. onUnmounted(() => {
  88. // 清理观察器
  89. visibilityObserver?.disconnect()
  90. })
  91. </script>
  92. <style scoped>
  93. .slider-container {
  94. height: 100%;
  95. width: 100%;
  96. height: 100%;
  97. width: 100%;
  98. position: relative;
  99. border-radius: 10px;
  100. background: linear-gradient(93deg, #59c5ff 22.29%, #fff 48.81%, #ffb33a 75.32%);
  101. cursor: pointer;
  102. }
  103. .slider-track {
  104. height: 100%;
  105. width: 100%;
  106. position: relative;
  107. cursor: pointer;
  108. }
  109. .slider-thumb {
  110. position: absolute;
  111. /* top: 2px; */
  112. width: 54px;
  113. height: 54px;
  114. border-radius: 9px;
  115. background: #fff;
  116. flex-shrink: 0;
  117. box-shadow: 0px 2px 7.7px 0px rgba(48, 16, 16, 0.25);
  118. /* transform: translateX(-100%); 修改这里,使滑块右边缘对齐百分比位置 */
  119. }
  120. .slider-value {
  121. margin-top: 10px;
  122. text-align: center;
  123. font-size: 14px;
  124. }
  125. </style>