hookehuyr

refactor(StudyCoursePage): 优化标签页固定和切换动画

重构了标签页的固定逻辑,使其在滚动时更流畅地固定到顶部。同时,改进了标签页切换时的动画效果,增加了过渡动画的平滑性。这些改动提升了用户体验和页面交互的流畅性。
...@@ -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) {
......