You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现鼠标滚轮控制的无限滚动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;
  }
}

原理说明

  1. 内容复制:通过复制一份完全相同的分类内容,让容器总宽度为原始内容的2倍,保证滚动时前后视觉一致。
  2. 边界跳转:监听滚动位置,当滚动到单份内容的末尾时,瞬间将scrollLeft重置到起始位置;当滚动到最左侧时,瞬间跳转到单份内容的起始位置,用户视觉上感知不到跳转,形成无限循环。
  3. 样式优化:flex-shrink:0确保分类项不会被压缩变形,移除固定宽度让容器自适应内容,保证滚动逻辑正常运行。

内容的提问来源于stack exchange,提问作者Matrion

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 01:15:39