You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

横向旋转网站滚动导航激活:scrollTop还是scrollLeft?

解决旋转页面滚动时导航激活状态自动切换的问题

首先咱们得先揪出问题的核心:你的页面实际滚动的容器是#container,但你之前监听的是document的滚动事件,这完全不对——真正产生滚动行为的是这个被旋转处理的容器,不是整个文档。再加上旋转变换打乱了原生滚动轴的对应关系,直接用scrollTopscrollLeft在document上自然没效果。

下面是完整的解决方案,我会一步步拆解说明:

核心修改思路

  1. 绑定正确的滚动监听目标:把滚动事件绑定到#container,这才是实际带滚动条的元素
  2. 适配旋转后的位置计算:因为容器和内容都做了旋转,我们需要基于滚动容器的scrollTop来判断当前可视区域对应的section(旋转后scrollTop对应视觉上的横向滚动距离)
  3. 统一激活状态逻辑:把点击和滚动的状态切换复用同一套函数,避免代码冗余,保证状态一致性

修改后的完整代码

JavaScript部分

// --- 统一的图标状态控制函数(优化后更可靠)
const setIconState = (icon, isOn) => {
  icon.className = isOn 
    ? icon.className.replace('button-off', 'button-on') 
    : icon.className.replace('button-on', 'button-off');
};

const setIconActiveState = (icon, isActive) => {
  if (isActive) {
    icon.classList.add('active');
  } else {
    icon.classList.remove('active');
  }
};

// --- 点击切换逻辑(优化冗余代码)
document.querySelectorAll('.bottomnav span.icon').forEach(icon => {
  icon.onclick = (e) => {
    const clickedSpan = e.target;
    // 获取底部导航下的所有图标
    const allIcons = [...clickedSpan.closest('.bottomnav').querySelectorAll('span.icon')];
    
    // 重置所有兄弟图标的状态
    allIcons.forEach(icon => {
      if (icon !== clickedSpan) {
        setIconState(icon, false);
        setIconActiveState(icon, false);
      }
    });
    
    // 设置当前点击图标的激活状态
    setIconState(clickedSpan, true);
    setIconActiveState(clickedSpan, true);
  };
});

// --- 滚动切换逻辑(核心修复部分)
const container = document.getElementById('container');
const sections = [...document.querySelectorAll('#player section')];
const navLinks = [...document.querySelectorAll('.bottomnav a')];

// 给实际滚动容器绑定监听事件
container.addEventListener('scroll', () => {
  // 旋转后,容器的scrollTop对应视觉上的横向滚动距离
  const scrollPos = container.scrollTop;
  
  sections.forEach((section, index) => {
    // 因为旋转,section的offsetTop对应视觉上的横向起始位置
    const sectionTop = section.offsetTop;
    const sectionHeight = section.offsetHeight;
    
    // 判断当前滚动位置是否落在当前section范围内
    if (scrollPos >= sectionTop && scrollPos < sectionTop + sectionHeight) {
      // 重置所有导航的状态
      navLinks.forEach(link => {
        setIconActiveState(link.querySelector('span'), false);
        setIconState(link.querySelector('span'), false);
      });
      
      // 激活当前section对应的导航
      const activeLink = navLinks[index];
      setIconActiveState(activeLink.querySelector('span'), true);
      setIconState(activeLink.querySelector('span'), true);
    }
  });
});

HTML部分(替换<object><div>,避免不必要的渲染问题)

<link href="https://unpkg.com/ionicons@4.5.5/dist/css/ionicons.min.css" rel="stylesheet">
<div class="bottomnav" id="bottomnav">
  <a href="#1"><span class="icon ion-ios-radio-button-on active"></span></a>
  <a href="#2"><span class="icon ion-ios-radio-button-off"></span></a>
  <a href="#3"><span class="icon ion-ios-radio-button-off"></span></a>
</div>
<div class="container" id="container">
  <div id="player">
    <section class="card cardwhite" id="1">
      <div>
        <h2>Section 1</h2>
        <p>Description</p>
      </div>
    </section>
    <section class="card cardblack" id="2">
      <div>
        <h2>Section 2</h2>
        <p>Description</p>
      </div>
    </section>
    <section class="card cardwhite" id="3">
      <div>
        <h2>Section 3</h2>
        <p>Description</p>
      </div>
    </section>
  </div>
</div>

CSS部分(仅修正滚动条选择器,其他保留你的原有代码)

* {
  margin: 0;
  padding: 0;
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
}
html, body {
  color: #000;
  font-family: 'IBM Plex Sans', sans-serif;
  font-weight: 100;
  font-size: 7px;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
  scroll-behavior: smooth;
}
.bottomnav {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
  overflow: hidden;
  position: fixed;
  bottom: 0px;
  width: 100%;
  z-index: 2;
}
.bottomnav span {
  float: left;
  display: block;
  color: #888;
  text-align: center;
  padding: 14px 16px;
  text-decoration: none;
  font-size: 26px;
}
.bottomnav span:hover {
  color: #fac123;
}
.bottomnav span.active {
  color: #fac123;
}
#container {
  overflow-y: scroll;
  overflow-x: hidden;
  transform: rotate(270deg) translateX(-100vh);
  transform-origin: top left;
  position: absolute;
  width: 100vh;
  height: 100vw;
  white-space: nowrap;
  scroll-snap-type: y mandatory;
}
#container .card {
  width: 100vw;
  height: 100vh;
  display: inline-flex;
  position: relative;
  scroll-snap-align: start;
}
#player {
  transform: rotate(90deg) translateY(-100vh);
  transform-origin: top left;
  font-size: 0;
  width: 100vh;
  height: 100vh;
  display: flex;
}
#player section > div {
  width: 100vw;
  overflow-x: hidden;
}
section div > div {
  white-space: normal;
}
/* 修正滚动条选择器,对应容器id */
#container::-webkit-scrollbar {
  display: none;
}
section {
  padding: 5%;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
  position: relative;
  transition: .5s ease;
}
.cardwhite {
  color: white;
  background-color: black;
}
.cardblack {
  color: black;
  background-color: white;
}
h2 {
  font-size: 40px;
  font-weight: 700;
  font-family: 'IBM Plex Serif', sans-serif;
}
p {
  font-size: 10px;
  margin-bottom: 15px;
  font-weight: 100;
  font-family: 'IBM Plex Sans', sans-serif;
}

关键修改点说明

  1. 滚动监听目标修正:把监听对象从document换成了实际滚动的#container,这是最核心的修复
  2. 状态函数优化:用classList.add/remove替代字符串替换,避免重复替换导致的className混乱
  3. 适配旋转后的位置计算:因为容器做了270度旋转,container.scrollTop对应视觉上的横向滚动距离,section.offsetTop对应每个section的横向起始位置,以此判断当前可视区域
  4. 标签替换:把<object>换成<div><object>多用于嵌入外部资源,这里用普通div更合适,避免不必要的渲染异常

这样修改后,滚动页面时导航的激活状态就会自动切换,和点击操作的状态保持完全一致了。

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

火山引擎 最新活动