| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142 |
- <template>
- <div class="slider-container" ref="slider-container">
- <div class="slider-track">
- <div
- class="slider-thumb"
- @touchstart="startDrag"
- @touchmove="onDrag"
- @touchend="endDrag"
- ref="slider-thumb"
- :style="{ left: `${thumbLeft}px` }"></div>
- </div>
- <!-- <div class="slider-value">{{ internalValue }}%</div> -->
- </div>
- </template>
- <script setup>
- import { computed, defineModel, onMounted, onUnmounted, ref, useTemplateRef } from 'vue'
- const model = defineModel({ default: 50 })
- const internalValue = ref(model.value)
- const isDragging = ref(false)
- const sliderContainer = useTemplateRef('slider-container')
- const sliderThumb = useTemplateRef('slider-thumb')
- const max = ref(0)
- const min = ref(0)
- const emit = defineEmits(['onEnd'])
- const touchstartPos = ref(0)
- const sliderThumbLeft = ref(0)
- const sliderContainerLeft = ref(0)
- let animationFrameId = null
- const thumbLeft = computed(() => {
- return (internalValue.value / 100) * (max.value - min.value)
- })
- // 初始化滑块位置
- const initSlider = () => {
- max.value = sliderContainer.value?.getBoundingClientRect().width
- min.value = sliderThumb.value?.getBoundingClientRect().width
- sliderContainerLeft.value = sliderContainer.value?.getBoundingClientRect().left
- }
- const startDrag = event => {
- const touch = event.touches[0]
- touchstartPos.value = touch.clientX
- sliderThumbLeft.value = sliderThumb.value?.getBoundingClientRect().left
- isDragging.value = true
- initSlider()
- onDrag(event)
- }
- const onDrag = event => {
- if (!isDragging.value) return
- const touch = event.touches[0]
- const sliderRect = sliderContainer.value.getBoundingClientRect()
- const maxRange = max.value - min.value
- let offsetX = touch.clientX - touchstartPos.value + sliderThumbLeft.value - sliderContainerLeft.value
- offsetX = Math.max(0, Math.min(offsetX, maxRange))
- if (animationFrameId) {
- cancelAnimationFrame(animationFrameId)
- }
- animationFrameId = requestAnimationFrame(() => {
- model.value = internalValue.value = Math.round((offsetX / (max.value - min.value)) * 100)
- })
- }
- const endDrag = () => {
- isDragging.value = false
- if (animationFrameId) {
- cancelAnimationFrame(animationFrameId)
- }
- emit('onEnd', internalValue.value)
- }
- // 监听DOM插入
- let visibilityObserver = null
- // 监听DOM可见性
- const observeVisibility = () => {
- const observer = new IntersectionObserver(entries => {
- entries.forEach(entry => {
- if (entry.isIntersecting) {
- initSlider()
- }
- })
- })
- if (sliderContainer.value) {
- observer.observe(sliderContainer.value)
- }
- return observer
- }
- onMounted(() => {
- // 启动观察器
- visibilityObserver = observeVisibility()
- })
- onUnmounted(() => {
- // 清理观察器
- visibilityObserver?.disconnect()
- })
- </script>
- <style scoped>
- .slider-container {
- height: 100%;
- width: 100%;
- height: 100%;
- width: 100%;
- position: relative;
- border-radius: 10px;
- background: linear-gradient(93deg, #59c5ff 22.29%, #fff 48.81%, #ffb33a 75.32%);
- cursor: pointer;
- }
- .slider-track {
- height: 100%;
- width: 100%;
- position: relative;
- cursor: pointer;
- }
- .slider-thumb {
- position: absolute;
- /* top: 2px; */
- width: 54px;
- height: 54px;
- border-radius: 9px;
- background: #fff;
- flex-shrink: 0;
- box-shadow: 0px 2px 7.7px 0px rgba(48, 16, 16, 0.25);
- /* transform: translateX(-100%); 修改这里,使滑块右边缘对齐百分比位置 */
- }
- .slider-value {
- margin-top: 10px;
- text-align: center;
- font-size: 14px;
- }
- </style>
|