横向旋转网站滚动导航激活:scrollTop还是scrollLeft?
解决旋转页面滚动时导航激活状态自动切换的问题
首先咱们得先揪出问题的核心:你的页面实际滚动的容器是#container,但你之前监听的是document的滚动事件,这完全不对——真正产生滚动行为的是这个被旋转处理的容器,不是整个文档。再加上旋转变换打乱了原生滚动轴的对应关系,直接用scrollTop或scrollLeft在document上自然没效果。
下面是完整的解决方案,我会一步步拆解说明:
核心修改思路
- 绑定正确的滚动监听目标:把滚动事件绑定到
#container,这才是实际带滚动条的元素 - 适配旋转后的位置计算:因为容器和内容都做了旋转,我们需要基于滚动容器的
scrollTop来判断当前可视区域对应的section(旋转后scrollTop对应视觉上的横向滚动距离) - 统一激活状态逻辑:把点击和滚动的状态切换复用同一套函数,避免代码冗余,保证状态一致性
修改后的完整代码
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; }
关键修改点说明
- 滚动监听目标修正:把监听对象从
document换成了实际滚动的#container,这是最核心的修复 - 状态函数优化:用
classList.add/remove替代字符串替换,避免重复替换导致的className混乱 - 适配旋转后的位置计算:因为容器做了270度旋转,
container.scrollTop对应视觉上的横向滚动距离,section.offsetTop对应每个section的横向起始位置,以此判断当前可视区域 - 标签替换:把
<object>换成<div>,<object>多用于嵌入外部资源,这里用普通div更合适,避免不必要的渲染异常
这样修改后,滚动页面时导航的激活状态就会自动切换,和点击操作的状态保持完全一致了。
内容的提问来源于stack exchange,提问作者Caitlin




