快速滚动时Infinite carousel无限轮播出现卡顿问题
无限轮播快速滚动卡顿问题解决方案
问题说明
开发可拖拽/滚动的无限轮播组件时,遇到以下问题:当用户快速滚动(可按住Shift键测试),infiniteScroll函数内的判断逻辑会在carousel.scrollLeft()的更新生效前多次触发,导致carousel.scrollLeft()被重复调用,最终引发轮播卡顿。
解决思路
通过添加状态锁机制,避免滚动重置操作被重复触发:
- 新增一个状态变量标记当前是否正在执行滚动位置重置
- 触发重置逻辑前先检查状态锁,已锁定则直接跳过
- 完成滚动位置重置后再解锁,确保同一时间段内只执行一次重置操作
修改后的代码
JavaScript
const carousel = $('.carousel'); const carouselCards = carousel.children('.card'); let cardAmount = 3; // 轮播内可见卡片数量 let isScrollLock = false; // 滚动锁定状态,防止重复触发 carouselCards.slice(-cardAmount).get().reverse().forEach((e) => { let card = $(e); carousel.prepend(card.prop('outerHTML')); // 将最后三张卡片逆序添加到开头 }) carouselCards.slice(0, cardAmount).get().forEach((e) => { let card = $(e); carousel.append(card.prop('outerHTML')); // 将前三张卡片添加到末尾 }) let scrollTriggerStart = carouselCards.outerWidth() + carouselCards.outerWidth() / 2 - carousel.outerWidth() / 2; // 第二张卡片居中时的scrollLeft值 let scrollTriggerEnd = carousel[0].scrollWidth - carouselCards.outerWidth() - carouselCards.outerWidth() / 2 - carousel.outerWidth() / 2; // 倒数第二张卡片居中时的scrollLeft值 let seamlessPosStart = carouselCards.length * carouselCards.outerWidth() - carouselCards.outerWidth() / 2 - carousel.outerWidth() / 2; // 对应倒数第二张卡片的目标scrollLeft值 let seamlessPosEnd = carousel[0].scrollWidth - carouselCards.length * carouselCards.outerWidth() + carouselCards.outerWidth() / 2 - carousel.outerWidth() / 2; // 对应第二张卡片的目标scrollLeft值 const infiniteScroll = () => { // 如果正在执行重置,直接返回 if (isScrollLock) return; if (carousel.scrollLeft() <= scrollTriggerStart) { isScrollLock = true; carousel.addClass('no-transition'); carousel.scrollLeft(seamlessPosEnd); // 用requestAnimationFrame确保DOM更新完成后解锁 requestAnimationFrame(() => { carousel.removeClass('no-transition'); isScrollLock = false; }); } else if (carousel.scrollLeft() >= scrollTriggerEnd) { isScrollLock = true; carousel.addClass('no-transition'); carousel.scrollLeft(seamlessPosStart); requestAnimationFrame(() => { carousel.removeClass('no-transition'); isScrollLock = false; }); } } carousel.on('scroll', infiniteScroll);
CSS
*, ::before, ::after { box-sizing: border-box; margin: 0; padding: 0; } * { font: inherit; } html, body { min-height: 100vh; } ul:has([class]) { list-style: none; } body { font-family: sans-serif; display: flex; align-items: center; justify-content: center; } .wrapper { position: relative; display: flex; flex-direction: column; align-items: center; gap: 5em; width: 100%; padding-inline: 10em; } .carousel { display: grid; grid-auto-flow: column; grid-auto-columns: calc(100% / 2); overflow-x: auto; scroll-snap-type: x mandatory; border-radius: .5em; scroll-behavior: smooth; scrollbar-width: none; width: 100%; } .carousel::-webkit-scrollbar { display: none; } .carousel .card { aspect-ratio: 1; scroll-snap-align: center; background: rgba(255, 0, 0, 0.25); border-radius: .5rem; display: flex; align-items: center; justify-content: center; font-size: 2rem; scale: 0.95; border: 2px solid transparent; transition: 250ms ease; } .card.selected { border: 2px solid red; scale: 1; } .no-transition { scroll-behavior: auto; } .no-transition .card { scroll-snap-align: unset; }
HTML
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="style.css"> <title>Infinite carousel</title> </head> <body> <div class="wrapper"> <ul class="carousel"> <li class="card"> <h2>Card 1</h2> </li> <li class="card"> <h2>Card 2</h2> </li> <li class="card"> <h2>Card 3</h2> </li> <li class="card"> <h2>Card 4</h2> </li> <li class="card"> <h2>Card 5</h2> </li> </ul> </div> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> <script src="main.js"></script> </body> </html>
内容的提问来源于stack exchange,提问作者Itsjul1an
相关产品推荐
相关产品推荐

