refactor(轮播组件): 重构新闻轮播逻辑实现无缝滚动
移除克隆项动态增减逻辑,改用固定克隆首尾项的方式 简化轮播位置更新和动画控制逻辑
Showing
1 changed file
with
45 additions
and
65 deletions
| ... | @@ -118,7 +118,7 @@ | ... | @@ -118,7 +118,7 @@ |
| 118 | <!-- 轮播容器 --> | 118 | <!-- 轮播容器 --> |
| 119 | <div class="news-carousel-container"> | 119 | <div class="news-carousel-container"> |
| 120 | <div class="news-carousel" ref="newsCarousel"> | 120 | <div class="news-carousel" ref="newsCarousel"> |
| 121 | - <div class="news-item" v-for="(item, index) in newsItems" :key="index" @click="handleNewsClick(item)"> | 121 | + <div class="news-item" v-for="(item, index) in displayNewsItems" :key="index" @click="handleNewsClick(item)"> |
| 122 | <div class="news-image"> | 122 | <div class="news-image"> |
| 123 | <img :src="item.image" :alt="item.title"> | 123 | <img :src="item.image" :alt="item.title"> |
| 124 | </div> | 124 | </div> |
| ... | @@ -136,14 +136,12 @@ | ... | @@ -136,14 +136,12 @@ |
| 136 | src="https://cdn.ipadbiz.cn/stdj/images/home/%E4%B8%8A@2x.png" | 136 | src="https://cdn.ipadbiz.cn/stdj/images/home/%E4%B8%8A@2x.png" |
| 137 | alt="上一张" | 137 | alt="上一张" |
| 138 | @click="prevSlide" | 138 | @click="prevSlide" |
| 139 | - :class="{ disabled: currentSlide === 0 }" | ||
| 140 | > | 139 | > |
| 141 | <img | 140 | <img |
| 142 | class="nav-btn next-btn" | 141 | class="nav-btn next-btn" |
| 143 | src="https://cdn.ipadbiz.cn/stdj/images/home/%E4%B8%8B@2x.png" | 142 | src="https://cdn.ipadbiz.cn/stdj/images/home/%E4%B8%8B@2x.png" |
| 144 | alt="下一张" | 143 | alt="下一张" |
| 145 | @click="nextSlide" | 144 | @click="nextSlide" |
| 146 | - :class="{ disabled: isTransitioning }" | ||
| 147 | > | 145 | > |
| 148 | </div> | 146 | </div> |
| 149 | </div> | 147 | </div> |
| ... | @@ -276,6 +274,11 @@ onMounted(async () => { | ... | @@ -276,6 +274,11 @@ onMounted(async () => { |
| 276 | image: item.photo || '', | 274 | image: item.photo || '', |
| 277 | post_link: item.post_link || '', | 275 | post_link: item.post_link || '', |
| 278 | })) || []; | 276 | })) || []; |
| 277 | + | ||
| 278 | + // 初始化轮播位置 | ||
| 279 | + nextTick(() => { | ||
| 280 | + updateCarouselPosition(false) | ||
| 281 | + }) | ||
| 279 | } | 282 | } |
| 280 | }) | 283 | }) |
| 281 | 284 | ||
| ... | @@ -317,59 +320,61 @@ const viewMore = (type, item) => { | ... | @@ -317,59 +320,61 @@ const viewMore = (type, item) => { |
| 317 | 320 | ||
| 318 | // 新闻轮播相关逻辑 | 321 | // 新闻轮播相关逻辑 |
| 319 | const newsCarousel = ref(null) | 322 | const newsCarousel = ref(null) |
| 320 | -const currentSlide = ref(0) | 323 | +const currentSlide = ref(1) // 从1开始,因为0是克隆的最后一项 |
| 321 | const isTransitioning = ref(false) | 324 | const isTransitioning = ref(false) |
| 322 | -const hasTailClone = ref(false) | ||
| 323 | 325 | ||
| 324 | // 新闻数据 | 326 | // 新闻数据 |
| 325 | const newsItems = ref([]) | 327 | const newsItems = ref([]) |
| 326 | - | 328 | +const displayNewsItems = computed(() => { |
| 327 | -// 原始长度用于判断“最后一张”的逻辑 | 329 | + if (newsItems.value.length === 0) { |
| 328 | -const initialLength = ref(newsItems.value.length) | 330 | + return [] |
| 331 | + } | ||
| 332 | + // 克隆第一项和最后一项以实现无缝滚动 | ||
| 333 | + return [newsItems.value[newsItems.value.length - 1], ...newsItems.value, newsItems.value[0]] | ||
| 334 | +}) | ||
| 329 | 335 | ||
| 330 | // 上一张 | 336 | // 上一张 |
| 331 | const prevSlide = () => { | 337 | const prevSlide = () => { |
| 332 | - if (currentSlide.value > 0) { | 338 | + if (isTransitioning.value) return |
| 333 | - currentSlide.value-- | 339 | + isTransitioning.value = true |
| 334 | - // 离开最后一张时移除尾部克隆,避免轨道长度异常 | 340 | + currentSlide.value-- |
| 335 | - if (hasTailClone.value && currentSlide.value < initialLength.value - 1) { | 341 | + updateCarouselPosition() |
| 336 | - newsItems.value.pop() | 342 | + |
| 337 | - hasTailClone.value = false | 343 | + if (currentSlide.value === 0) { |
| 338 | - } | 344 | + setTimeout(() => { |
| 339 | - updateCarouselPosition() | 345 | + isTransitioning.value = false |
| 346 | + currentSlide.value = newsItems.value.length | ||
| 347 | + updateCarouselPosition(false) // 无动画地跳转 | ||
| 348 | + }, 300) // 等待过渡动画完成 | ||
| 349 | + } else { | ||
| 350 | + setTimeout(() => { | ||
| 351 | + isTransitioning.value = false | ||
| 352 | + }, 300) | ||
| 340 | } | 353 | } |
| 341 | } | 354 | } |
| 342 | 355 | ||
| 343 | // 下一张 | 356 | // 下一张 |
| 344 | const nextSlide = () => { | 357 | const nextSlide = () => { |
| 345 | if (isTransitioning.value) return | 358 | if (isTransitioning.value) return |
| 346 | - | 359 | + isTransitioning.value = true |
| 347 | - // 若还未到原始最后一张,正常前进 | 360 | + currentSlide.value++ |
| 348 | - if (currentSlide.value < initialLength.value - 1) { | 361 | + updateCarouselPosition() |
| 349 | - currentSlide.value++ | 362 | + |
| 350 | - // 当抵达原始最后一张时,预先追加尾部克隆以填充右侧空白 | 363 | + if (currentSlide.value === displayNewsItems.value.length - 1) { |
| 351 | - if (currentSlide.value === initialLength.value - 1 && !hasTailClone.value) { | 364 | + setTimeout(() => { |
| 352 | - newsItems.value.push(newsItems.value[0]) | 365 | + isTransitioning.value = false |
| 353 | - hasTailClone.value = true | 366 | + currentSlide.value = 1 |
| 354 | - } | 367 | + updateCarouselPosition(false) // 无动画地跳转 |
| 355 | - isTransitioning.value = true | 368 | + }, 300) // 等待过渡动画完成 |
| 356 | - attachTransitionEndOnce(false) | ||
| 357 | - updateCarouselPosition() | ||
| 358 | } else { | 369 | } else { |
| 359 | - // 处于原始最后一张:使用尾部克隆进行无缝循环 | 370 | + setTimeout(() => { |
| 360 | - if (!hasTailClone.value) { | 371 | + isTransitioning.value = false |
| 361 | - newsItems.value.push(newsItems.value[0]) | 372 | + }, 300) |
| 362 | - hasTailClone.value = true | ||
| 363 | - } | ||
| 364 | - currentSlide.value++ | ||
| 365 | - isTransitioning.value = true | ||
| 366 | - attachTransitionEndOnce(true) | ||
| 367 | - updateCarouselPosition() | ||
| 368 | } | 373 | } |
| 369 | } | 374 | } |
| 370 | 375 | ||
| 371 | -// 更新轮播位置:按卡片宽度+间距进行像素级位移 | 376 | +// 更新轮播位置 |
| 372 | -const updateCarouselPosition = () => { | 377 | +const updateCarouselPosition = (animated = true) => { |
| 373 | const carouselEl = newsCarousel.value | 378 | const carouselEl = newsCarousel.value |
| 374 | if (!carouselEl) return | 379 | if (!carouselEl) return |
| 375 | 380 | ||
| ... | @@ -384,35 +389,10 @@ const updateCarouselPosition = () => { | ... | @@ -384,35 +389,10 @@ const updateCarouselPosition = () => { |
| 384 | const gapPx = parseFloat(gapStr) || 8 | 389 | const gapPx = parseFloat(gapStr) || 8 |
| 385 | 390 | ||
| 386 | const distance = currentSlide.value * (itemWidth + gapPx) | 391 | const distance = currentSlide.value * (itemWidth + gapPx) |
| 392 | + carouselEl.style.transition = animated ? 'transform 0.3s ease' : 'none' | ||
| 387 | carouselEl.style.transform = `translateX(-${distance}px)` | 393 | carouselEl.style.transform = `translateX(-${distance}px)` |
| 388 | } | 394 | } |
| 389 | 395 | ||
| 390 | -// 动画结束后复位(如处于无缝循环场景) | ||
| 391 | -const attachTransitionEndOnce = (looping) => { | ||
| 392 | - const el = newsCarousel.value | ||
| 393 | - if (!el) { | ||
| 394 | - isTransitioning.value = false | ||
| 395 | - return | ||
| 396 | - } | ||
| 397 | - const handler = () => { | ||
| 398 | - el.removeEventListener('transitionend', handler) | ||
| 399 | - if (looping) { | ||
| 400 | - const prevTransition = el.style.transition | ||
| 401 | - el.style.transition = 'none' | ||
| 402 | - // 移除临时克隆,重置到第一张 | ||
| 403 | - newsItems.value.pop() | ||
| 404 | - hasTailClone.value = false | ||
| 405 | - currentSlide.value = 0 | ||
| 406 | - updateCarouselPosition() | ||
| 407 | - // 强制重绘后恢复过渡 | ||
| 408 | - void el.offsetHeight | ||
| 409 | - el.style.transition = prevTransition || 'transform 0.3s ease' | ||
| 410 | - } | ||
| 411 | - isTransitioning.value = false | ||
| 412 | - } | ||
| 413 | - el.addEventListener('transitionend', handler) | ||
| 414 | -} | ||
| 415 | - | ||
| 416 | // 处理新闻点击事件 | 396 | // 处理新闻点击事件 |
| 417 | const handleNewsClick = (item) => { | 397 | const handleNewsClick = (item) => { |
| 418 | // 这里可以添加跳转到新闻详情页面的逻辑 | 398 | // 这里可以添加跳转到新闻详情页面的逻辑 | ... | ... |
-
Please register or login to post a comment