refactor(StudyCoursePage): 优化标签页固定和切换动画
重构了标签页的固定逻辑,使其在滚动时更流畅地固定到顶部。同时,改进了标签页切换时的动画效果,增加了过渡动画的平滑性。这些改动提升了用户体验和页面交互的流畅性。
Showing
1 changed file
with
64 additions
and
15 deletions
| ... | @@ -6,7 +6,7 @@ | ... | @@ -6,7 +6,7 @@ |
| 6 | <div class="study-course-page bg-gradient-to-b from-green-50/70 to-white/90 min-h-screen"> | 6 | <div class="study-course-page bg-gradient-to-b from-green-50/70 to-white/90 min-h-screen"> |
| 7 | <div v-if="course" class="flex flex-col h-screen"> | 7 | <div v-if="course" class="flex flex-col h-screen"> |
| 8 | <!-- 固定区域:课程封面和标签页 --> | 8 | <!-- 固定区域:课程封面和标签页 --> |
| 9 | - <div class="fixed top-0 left-0 right-0 z-10 top-wrapper bg-white"> | 9 | + <div class=" bg-white"> |
| 10 | <!-- 课程封面区域 --> | 10 | <!-- 课程封面区域 --> |
| 11 | <van-image class="w-full aspect-video object-cover" :src="course?.cover || 'https://cdn.ipadbiz.cn/mlaj/images/default_block.png'" :alt="course?.title" /> | 11 | <van-image class="w-full aspect-video object-cover" :src="course?.cover || 'https://cdn.ipadbiz.cn/mlaj/images/default_block.png'" :alt="course?.title" /> |
| 12 | <div class="p-4"> | 12 | <div class="p-4"> |
| ... | @@ -21,15 +21,26 @@ | ... | @@ -21,15 +21,26 @@ |
| 21 | <div class="h-2 bg-gray-100"></div> | 21 | <div class="h-2 bg-gray-100"></div> |
| 22 | 22 | ||
| 23 | <!-- 标签页区域 --> | 23 | <!-- 标签页区域 --> |
| 24 | - <div class="py-3 bg-white"> | 24 | + <div class="py-3 bg-white transition-all duration-300" :class="{'fixed top-0 left-0 right-0 z-10': isTabFixed}" :style="isTabFixed ? { transform: `translateY(0)` } : {}"> |
| 25 | - <van-tabs v-model:active="activeTab" sticky animated swipeable shrink @change="handleTabChange"> | 25 | + <div class="flex justify-around items-center relative"> |
| 26 | - <van-tab title="详情" name="detail"> | 26 | + <div |
| 27 | - </van-tab> | 27 | + v-for="(tab, index) in [{title: '详情', name: 'detail'}, {title: '目录', name: 'catalog'}, {title: '课程互动', name: 'interaction'}]" |
| 28 | - <van-tab title="目录" name="catalog"> | 28 | + :key="tab.name" |
| 29 | - </van-tab> | 29 | + :class="['px-4 py-2 cursor-pointer transition-colors relative', activeTab === tab.name ? 'text-green-600 font-medium' : 'text-gray-600']" |
| 30 | - <van-tab title="课程互动" name="interaction"> | 30 | + @click="handleTabChange(tab.name)" |
| 31 | - </van-tab> | 31 | + ref="tabRefs" |
| 32 | - </van-tabs> | 32 | + > |
| 33 | + {{ tab.title }} | ||
| 34 | + </div> | ||
| 35 | + <div | ||
| 36 | + class="absolute bottom-0 left-0 bg-green-600 transition-all duration-300 z-20" | ||
| 37 | + :style="{ | ||
| 38 | + width: tabRefs && tabRefs[activeTab === 'detail' ? 0 : activeTab === 'catalog' ? 1 : 2]?.offsetWidth + 'px', | ||
| 39 | + transform: `translateX(${tabRefs && tabRefs[activeTab === 'detail' ? 0 : activeTab === 'catalog' ? 1 : 2]?.offsetLeft}px)`, | ||
| 40 | + height: '2px', | ||
| 41 | + }" | ||
| 42 | + ></div> | ||
| 43 | + </div> | ||
| 33 | </div> | 44 | </div> |
| 34 | </div> | 45 | </div> |
| 35 | 46 | ||
| ... | @@ -123,12 +134,17 @@ useTitle('课程详情'); | ... | @@ -123,12 +134,17 @@ useTitle('课程详情'); |
| 123 | const activeTab = ref('detail'); | 134 | const activeTab = ref('detail'); |
| 124 | const topWrapperHeight = ref(0); | 135 | const topWrapperHeight = ref(0); |
| 125 | const resizeObserver = ref(null); | 136 | const resizeObserver = ref(null); |
| 137 | +const tabRefs = ref([]); | ||
| 126 | 138 | ||
| 127 | // 计算topWrapperHeight的函数 | 139 | // 计算topWrapperHeight的函数 |
| 128 | const updateTopWrapperHeight = () => { | 140 | const updateTopWrapperHeight = () => { |
| 129 | nextTick(() => { | 141 | nextTick(() => { |
| 130 | const topWrapper = document.querySelector('.top-wrapper'); | 142 | const topWrapper = document.querySelector('.top-wrapper'); |
| 131 | if (topWrapper) { | 143 | if (topWrapper) { |
| 144 | + // 断开之前的observer连接 | ||
| 145 | + if (resizeObserver.value) { | ||
| 146 | + resizeObserver.value.disconnect(); | ||
| 147 | + } | ||
| 132 | // 使用 ResizeObserver 监听元素尺寸变化 | 148 | // 使用 ResizeObserver 监听元素尺寸变化 |
| 133 | resizeObserver.value = new ResizeObserver(() => { | 149 | resizeObserver.value = new ResizeObserver(() => { |
| 134 | topWrapperHeight.value = `${topWrapper.offsetHeight}px`; | 150 | topWrapperHeight.value = `${topWrapper.offsetHeight}px`; |
| ... | @@ -175,21 +191,44 @@ onUnmounted(() => { | ... | @@ -175,21 +191,44 @@ onUnmounted(() => { |
| 175 | window.removeEventListener('scroll', handleScroll); | 191 | window.removeEventListener('scroll', handleScroll); |
| 176 | window.removeEventListener('resize', updateTopWrapperHeight); | 192 | window.removeEventListener('resize', updateTopWrapperHeight); |
| 177 | // 组件卸载时取消监听 | 193 | // 组件卸载时取消监听 |
| 178 | - resizeObserver.value.disconnect(); | 194 | + if (resizeObserver.value) { |
| 195 | + resizeObserver.value.disconnect(); | ||
| 196 | + resizeObserver.value = null; | ||
| 197 | + } | ||
| 179 | }); | 198 | }); |
| 180 | 199 | ||
| 181 | // 处理滚动事件 | 200 | // 处理滚动事件 |
| 201 | +// 在script setup中添加 | ||
| 202 | +const isTabFixed = ref(false); | ||
| 203 | +const tabOriginalTop = ref(0); | ||
| 204 | + | ||
| 205 | +onMounted(async () => { | ||
| 206 | + setTimeout(() => { | ||
| 207 | + nextTick(() => { | ||
| 208 | + // 记录标签页原始位置 | ||
| 209 | + const tabElement = document.querySelector('.py-3.bg-white'); | ||
| 210 | + if (tabElement) { | ||
| 211 | + tabOriginalTop.value = tabElement.getBoundingClientRect().top + window.scrollY; | ||
| 212 | + } | ||
| 213 | + }) | ||
| 214 | + }, 500); | ||
| 215 | +}); | ||
| 216 | + | ||
| 217 | +// 修改handleScroll函数 | ||
| 182 | const handleScroll = () => { | 218 | const handleScroll = () => { |
| 183 | const detailElement = document.getElementById('detail'); | 219 | const detailElement = document.getElementById('detail'); |
| 184 | const catalogElement = document.getElementById('catalog'); | 220 | const catalogElement = document.getElementById('catalog'); |
| 185 | const interactionElement = document.getElementById('interaction'); | 221 | const interactionElement = document.getElementById('interaction'); |
| 186 | - if (!detailElement || !catalogElement || !interactionElement) return; | 222 | + const tabElement = document.querySelector('.py-3.bg-white'); |
| 223 | + if (!detailElement || !catalogElement || !interactionElement || !tabElement) return; | ||
| 224 | + | ||
| 225 | + const currentScrollY = window.scrollY; | ||
| 226 | + isTabFixed.value = currentScrollY >= tabOriginalTop.value; | ||
| 187 | 227 | ||
| 188 | const scrollTop = window.scrollY; | 228 | const scrollTop = window.scrollY; |
| 189 | - const catalogOffset = catalogElement.offsetTop - parseInt(topWrapperHeight.value); | 229 | + const catalogOffset = catalogElement.offsetTop - (isTabFixed.value ? tabElement.offsetHeight : 0); |
| 190 | - const interactionOffset = interactionElement.offsetTop - parseInt(topWrapperHeight.value); | 230 | + const interactionOffset = interactionElement.offsetTop - (isTabFixed.value ? tabElement.offsetHeight : 0); |
| 191 | 231 | ||
| 192 | - // 根据滚动位置更新activeTab | ||
| 193 | if (scrollTop >= interactionOffset) { | 232 | if (scrollTop >= interactionOffset) { |
| 194 | activeTab.value = 'interaction'; | 233 | activeTab.value = 'interaction'; |
| 195 | } else if (scrollTop >= catalogOffset) { | 234 | } else if (scrollTop >= catalogOffset) { |
| ... | @@ -200,7 +239,17 @@ const handleScroll = () => { | ... | @@ -200,7 +239,17 @@ const handleScroll = () => { |
| 200 | }; | 239 | }; |
| 201 | 240 | ||
| 202 | // 处理标签页切换 | 241 | // 处理标签页切换 |
| 242 | +// 在script setup中添加 | ||
| 243 | +const previousTab = ref('detail'); | ||
| 244 | + | ||
| 245 | +const getTransitionTiming = (current, previous) => { | ||
| 246 | + const tabOrder = { detail: 0, catalog: 1, interaction: 2 }; | ||
| 247 | + return tabOrder[current] > tabOrder[previous] ? 'cubic-bezier(0.4, 0, 0.2, 1)' : 'cubic-bezier(0.4, 0, 0.2, 1)'; | ||
| 248 | +}; | ||
| 249 | + | ||
| 250 | +// 修改handleTabChange函数 | ||
| 203 | const handleTabChange = (name) => { | 251 | const handleTabChange = (name) => { |
| 252 | + previousTab.value = activeTab.value; | ||
| 204 | nextTick(() => { | 253 | nextTick(() => { |
| 205 | const element = document.getElementById(name); | 254 | const element = document.getElementById(name); |
| 206 | if (element) { | 255 | if (element) { | ... | ... |
-
Please register or login to post a comment