如何实现鼠标滚轮控制的无限滚动Marquee效果?
实现Vanilla JavaScript无限滚动Marquee组件(滚轮控制)
我正在开发一款基于Vanilla JavaScript的多控制Marquee组件,目前已实现鼠标滚轮控制滑块功能,但希望添加无限滚动效果——用户可持续滚动,不会在左右两端停止。现有代码如下,请问如何改造?
现有代码
HTML
<!-- ========== begin ========== MARQUEE TABS ========== begin ========== --> <div class="categories"> <div class="inner_categories"> <!-- ========== begin ========== CATEGORY TABS ========== begin ========== --> <div class="category"> <a href="https://google.com">Google</a> </div> <div class="category"> <a href="https://yahoo.com">Yahoo</a> </div> <div class="category"> <a href="https://bing.com">Bing</a> </div> <div class="category"> <a href="https://facebook.com">Facebook</a> </div> <div class="category"> <a href="https://instagram.com">Instagram</a> </div> <div class="category"> <a href="https://twitter.com">Twitter</a> </div> <div class="category"> <a href="https://tiktok.com">TikTok</a> </div> <div class="category"> <a href="https://linkedin.com">Linkedin</a> </div> <div class="category"> <a href="https://behance.net">Bēhance</a> </div> <div class="category"> <a href="https://indeed.com">Indeed</a> </div> <div class="category"> <a href="https://slot.ng">SLOT</a> </div> <div class="category"> <a href="https://pointekonline.com">Pointek online</a> </div> <div class="category"> <a href="https://gsmarena.com">GSM Arena</a> </div> <div class="category"> <a href="https://stackoverflow.com">StackOverflow</a> </div> <div class="category"> <a href="https://youtube.com">YouTube</a> </div> <div class="category"> <a href="https://dailymotion.com">Daily Motion</a> </div> <div class="category"> <a href="https://vimeo.com">Vimeo</a> </div> <div class="category"> <a href="https://tumblr.com">Tumblr</a> </div> <div class="category"> <a href="https://naijaterra.com">NaijaTerra</a> </div> <div class="category"> <a href="https://gtbank.com">GT bank</a> </div> <div class="category"> <a href="https://outlook.com">Outlook</a> </div> <!-- ========== end ========== CATEGORY TABS ========== end ========== --> </div> </div> <!-- ========== end ========== MARQUEE TABS ========== end ========== -->
CSS
* { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Yu Gothic UI'; display: flex; align-items: center; justify-content: center; width: 100%; height: 100vh; background-color: #8a5bcb; } div.categories { border: 1px solid #000; display: flex; align-items: center; background-color: #30b090; overflow: hidden; height: 40px; width: 50vw; position: relative; } div.inner_categories { display: flex; white-space: nowrap; grid-column-gap: 10px; position: absolute; top: 50%; transform: translateY(-50%); left: 0; right: 0; width: 100%; } div.category { font-size: 12px; background-color: #E0E0E0; padding-top: 2px; padding-bottom: 2px; padding-left: 5px; padding-right: 5px; border-radius: 5px; } div.category:hover { cursor: pointer; } a { text-decoration: none; color: #111; font-weight: 600; }
JavaScript
const categories = document.querySelector('div.categories') const inner_categories = document.querySelector('div.inner_categories') categories.addEventListener("mousewheel", function(e) { if (e.deltaY > 0) { this.scrollLeft += 100; } else { this.scrollLeft -= 100; } });
改造方案:实现无限滚动
无限滚动的核心是复制内容+边界跳转,让用户滚动到边界时无缝衔接,视觉上没有停止感。
1. 修改HTML结构
在inner_categories内复制一份完全相同的category节点,让容器总宽度为原始内容的2倍,保证滚动衔接的连续性。
2. 调整CSS样式
- 移除
inner_categories的width:100%,让容器自适应双倍内容宽度 - 给
category添加flex-shrink:0,防止分类项被压缩变形 - 移除
inner_categories的white-space:nowrap(flex布局已支持横向排列)
3. 优化JavaScript逻辑
添加边界检测,当滚动到单份内容的末尾/起始位置时,瞬间重置滚动位置,实现无缝循环。
修改后的完整代码
HTML
<!-- ========== begin ========== MARQUEE TABS ========== begin ========== --> <div class="categories"> <div class="inner_categories"> <!-- 原始内容 --> <div class="category"> <a href="https://google.com">Google</a> </div> <div class="category"> <a href="https://yahoo.com">Yahoo</a> </div> <div class="category"> <a href="https://bing.com">Bing</a> </div> <div class="category"> <a href="https://facebook.com">Facebook</a> </div> <div class="category"> <a href="https://instagram.com">Instagram</a> </div> <div class="category"> <a href="https://twitter.com">Twitter</a> </div> <div class="category"> <a href="https://tiktok.com">TikTok</a> </div> <div class="category"> <a href="https://linkedin.com">Linkedin</a> </div> <div class="category"> <a href="https://behance.net">Bēhance</a> </div> <div class="category"> <a href="https://indeed.com">Indeed</a> </div> <div class="category"> <a href="https://slot.ng">SLOT</a> </div> <div class="category"> <a href="https://pointekonline.com">Pointek online</a> </div> <div class="category"> <a href="https://gsmarena.com">GSM Arena</a> </div> <div class="category"> <a href="https://stackoverflow.com">StackOverflow</a> </div> <div class="category"> <a href="https://youtube.com">YouTube</a> </div> <div class="category"> <a href="https://dailymotion.com">Daily Motion</a> </div> <div class="category"> <a href="https://vimeo.com">Vimeo</a> </div> <div class="category"> <a href="https://tumblr.com">Tumblr</a> </div> <div class="category"> <a href="https://naijaterra.com">NaijaTerra</a> </div> <div class="category"> <a href="https://gtbank.com">GT bank</a> </div> <div class="category"> <a href="https://outlook.com">Outlook</a> </div> <!-- 复制的内容,用于无限滚动衔接 --> <div class="category"> <a href="https://google.com">Google</a> </div> <div class="category"> <a href="https://yahoo.com">Yahoo</a> </div> <div class="category"> <a href="https://bing.com">Bing</a> </div> <div class="category"> <a href="https://facebook.com">Facebook</a> </div> <div class="category"> <a href="https://instagram.com">Instagram</a> </div> <div class="category"> <a href="https://twitter.com">Twitter</a> </div> <div class="category"> <a href="https://tiktok.com">TikTok</a> </div> <div class="category"> <a href="https://linkedin.com">Linkedin</a> </div> <div class="category"> <a href="https://behance.net">Bēhance</a> </div> <div class="category"> <a href="https://indeed.com">Indeed</a> </div> <div class="category"> <a href="https://slot.ng">SLOT</a> </div> <div class="category"> <a href="https://pointekonline.com">Pointek online</a> </div> <div class="category"> <a href="https://gsmarena.com">GSM Arena</a> </div> <div class="category"> <a href="https://stackoverflow.com">StackOverflow</a> </div> <div class="category"> <a href="https://youtube.com">YouTube</a> </div> <div class="category"> <a href="https://dailymotion.com">Daily Motion</a> </div> <div class="category"> <a href="https://vimeo.com">Vimeo</a> </div> <div class="category"> <a href="https://tumblr.com">Tumblr</a> </div> <div class="category"> <a href="https://naijaterra.com">NaijaTerra</a> </div> <div class="category"> <a href="https://gtbank.com">GT bank</a> </div> <div class="category"> <a href="https://outlook.com">Outlook</a> </div> </div> </div> <!-- ========== end ========== MARQUEE TABS ========== end ========== -->
CSS
* { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Yu Gothic UI'; display: flex; align-items: center; justify-content: center; width: 100%; height: 100vh; background-color: #8a5bcb; } div.categories { border: 1px solid #000; display: flex; align-items: center; background-color: #30b090; overflow: hidden; height: 40px; width: 50vw; position: relative; } div.inner_categories { display: flex; grid-column-gap: 10px; position: absolute; top: 50%; transform: translateY(-50%); left: 0; } div.category { font-size: 12px; background-color: #E0E0E0; padding-top: 2px; padding-bottom: 2px; padding-left: 5px; padding-right: 5px; border-radius: 5px; flex-shrink: 0; /* 防止分类项被压缩 */ } div.category:hover { cursor: pointer; } a { text-decoration: none; color: #111; font-weight: 600; }
JavaScript
const categories = document.querySelector('div.categories') const inner_categories = document.querySelector('div.inner_categories') // 获取单份内容的宽度 const originalContentWidth = inner_categories.offsetWidth / 2; categories.addEventListener("mousewheel", function(e) { e.preventDefault(); // 阻止页面整体滚动 const scrollStep = 100; if (e.deltaY > 0) { this.scrollLeft += scrollStep; } else { this.scrollLeft -= scrollStep; } // 检测滚动边界,实现无限循环 checkInfiniteScroll(); }); function checkInfiniteScroll() { // 滚动到右侧边界,重置到起始位置 if (categories.scrollLeft >= originalContentWidth) { categories.scrollLeft -= originalContentWidth; } // 滚动到左侧边界,跳转到单份内容的起始位置 else if (categories.scrollLeft <= 0) { categories.scrollLeft += originalContentWidth; } }
原理说明
- 内容复制:通过复制一份完全相同的分类内容,让容器总宽度为原始内容的2倍,保证滚动时前后视觉一致。
- 边界跳转:监听滚动位置,当滚动到单份内容的末尾时,瞬间将
scrollLeft重置到起始位置;当滚动到最左侧时,瞬间跳转到单份内容的起始位置,用户视觉上感知不到跳转,形成无限循环。 - 样式优化:
flex-shrink:0确保分类项不会被压缩变形,移除固定宽度让容器自适应内容,保证滚动逻辑正常运行。
内容的提问来源于stack exchange,提问作者Matrion
相关产品推荐
相关产品推荐

