如何实现页面滚动控制的水平Image Accordion(图片手风琴)交互效果?
滚动控制图片手风琴组件实现方案
实现核心思路
这个效果的本质是「滚动区间锚定+进度映射转换」,核心逻辑如下:
- 手风琴区域进入视口后先固定在屏幕上,将垂直滚动的进度,转换为手风琴横向展开的进度
- 提前设置足够高度的占位元素撑开页面,保证滚动条长度匹配手风琴完全展开所需的滚动距离
- 所有图片完全展开后,解除手风琴的固定定位,页面恢复正常垂直滚动逻辑
基础实现代码(原生JS+CSS,框架场景可直接复用逻辑)
HTML 结构
<!-- 页面其他上方内容 --> <div class="top-content" style="height: 100vh;"></div> <!-- 手风琴区域占位符,用来撑开滚动高度 --> <div class="accordion-placeholder"></div> <!-- 手风琴主体 --> <div class="image-accordion"> <div class="accordion-item"><img src="你的图片地址1" alt="展示图1"></div> <div class="accordion-item"><img src="你的图片地址2" alt="展示图2"></div> <div class="accordion-item"><img src="你的图片地址3" alt="展示图3"></div> <div class="accordion-item"><img src="你的图片地址4" alt="展示图4"></div> <!-- 可自行添加更多图片项 --> </div> <!-- 页面其他下方内容 --> <div class="bottom-content" style="height: 100vh;"></div>
CSS 样式
.image-accordion { top: 0; left: 0; width: 100vw; height: 100vh; display: flex; overflow: hidden; } /* 进入滚动触发区间后添加该类,固定手风琴在视口内 */ .image-accordion.fixed { position: fixed; } .accordion-item { height: 100%; /* 收起状态的最小宽度,可自行调整 */ min-width: 80px; transition: width 0.1s linear; overflow: hidden; } .accordion-item img { width: 100%; height: 100%; object-fit: cover; } /* 占位符高度=图片数量*单张展开所需滚动高度,示例为4张图、每张滚动300px展开,总高1200px */ .accordion-placeholder { height: 1200px; }
JS 交互逻辑
const accordion = document.querySelector('.image-accordion') const placeholder = document.querySelector('.accordion-placeholder') const items = document.querySelectorAll('.accordion-item') const itemCount = items.length // 单张图片完全展开所需的滚动距离,可自行调整控制滚动灵敏度 const perItemScrollDistance = 300 window.addEventListener('scroll', () => { const scrollY = window.scrollY // 动态计算触发区间的上下边界,避免页面布局变动导致位置偏移 const triggerTop = placeholder.offsetTop const triggerBottom = triggerTop + placeholder.offsetHeight if (scrollY >= triggerTop && scrollY < triggerBottom) { // 进入触发区间,固定手风琴 accordion.classList.add('fixed') // 计算当前滚动在触发区间内的进度,范围0-1 const totalProgress = (scrollY - triggerTop) / placeholder.offsetHeight // 计算当前应该展开到第几张图片 const activeIndex = Math.floor(totalProgress * itemCount) // 计算当前正在展开的图片的展开进度,范围0-1 const currentItemProgress = (totalProgress * itemCount) - activeIndex items.forEach((item, index) => { if (index < activeIndex) { // 已经完全展开的图片,占满整个视口宽度 item.style.width = '100vw' } else if (index === activeIndex) { // 当前正在展开的图片,宽度按进度动态计算 item.style.width = `${80 + (window.innerWidth - 80) * currentItemProgress}px` } else { // 还未到展开时机的图片,保持收起的最小宽度 item.style.width = '80px' } }) } else if (scrollY >= triggerBottom) { // 滚动超出触发区间,解除固定,恢复正常页面滚动 accordion.classList.remove('fixed') // 所有图片保持完全展开状态 items.forEach(item => item.style.width = '100vw') } else { // 还未滚动到触发区间,解除固定,所有图片保持收起状态 accordion.classList.remove('fixed') items.forEach(item => item.style.width = '80px') } })
可优化点
- 用
requestAnimationFrame处理样式更新,避免滚动事件高频触发导致的卡顿 - 加入节流函数减少滚动事件的计算量,不影响流畅度的前提下降低性能消耗
- 图片添加懒加载逻辑,避免初始加载资源过多影响首屏性能
- 移动端适配场景可调整收起状态的最小宽度、单张图片所需滚动距离,适配小屏幕显示
- 如果是Vue/React等框架场景,可将逻辑封装为组件,用响应式数据控制样式,无需直接操作DOM
内容的提问来源于stack exchange,提问作者Wanderson Castro
相关产品推荐
相关产品推荐

