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

在可滚动Fragment中实现带粘性副标题的RecyclerView滚动交互

Got it, let's break down how to build this sticky scroll interaction step by step. I'll use vanilla HTML/CSS/JS for the example—this logic translates directly to mobile frameworks like React Native or Flutter too, with just a few syntax adjustments.

实现双区域粘性滚动交互方案

核心行为梳理

First, let's clarify the two key scroll states we need to handle:

  1. Initial Scroll Phase: When scrolling vertically anywhere, Section A slides upward entirely out of view, while Section B shifts up in sync. This continues until Section B's subtitle hits the top of the viewport.
  2. Sticky Lock Phase: Once the subtitle is fixed at the top, further scrolling only affects Section B's vertical list—loading/rolling through more content while the subtitle stays anchored.

Full Code Implementation

HTML Structure

We'll split the interface into the two required sections with clear nested elements:

<div class="app-container">
  <!-- 区域A:头部 + 横向滚动列表 -->
  <section class="section-a">
    <div class="section-a-header">
      <h2>区域A头部标题</h2>
    </div>
    <div class="horizontal-scroll-container">
      <div class="horizontal-item">横向项1</div>
      <div class="horizontal-item">横向项2</div>
      <div class="horizontal-item">横向项3</div>
      <div class="horizontal-item">横向项4</div>
      <div class="horizontal-item">横向项5</div>
      <div class="horizontal-item">横向项6</div>
    </div>
  </section>

  <!-- 区域B:带副标题的头部 + 垂直无限滚动列表 -->
  <section class="section-b">
    <div class="section-b-header">
      <h2>区域B主标题</h2>
      <h3 class="sticky-subtitle">区域B粘性副标题</h3>
    </div>
    <div class="vertical-infinite-list">
      <div class="vertical-item">垂直内容1</div>
      <div class="vertical-item">垂直内容2</div>
      <div class="vertical-item">垂直内容3</div>
      <div class="vertical-item">垂直内容4</div>
    </div>
  </section>
</div>

CSS Styling

Handle layout, horizontal scrolling, base sticky styles, and smooth transitions:

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

.app-container {
  min-height: 100vh;
}

/* 区域A样式 */
.section-a {
  background-color: #f5f5f5;
  transition: transform 0.15s ease-out; /* 平滑滑动过渡 */
}

.section-a-header {
  padding: 1.2rem 2rem;
}

.horizontal-scroll-container {
  display: flex;
  overflow-x: auto;
  gap: 1rem;
  padding: 1rem 2rem 2rem;
}

.horizontal-item {
  flex-shrink: 0;
  width: 160px;
  height: 110px;
  background-color: #e0e0e0;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* 区域B样式 */
.section-b-header {
  padding: 1.2rem 2rem;
  background-color: #e8e8e8;
}

.sticky-subtitle {
  margin-top: 0.8rem;
  /* 初始状态不固定,滚动到位置后添加fixed类 */
}

.sticky-subtitle.fixed {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  padding: 0.8rem 2rem;
  background-color: #e8e8e8;
  z-index: 10;
}

.vertical-infinite-list {
  padding: 0 2rem;
}

.vertical-item {
  height: 90px;
  background-color: #dcdcdc;
  border-radius: 8px;
  margin: 1rem 0;
  display: flex;
  align-items: center;
  padding: 0 1.2rem;
}

JavaScript Logic

Listen for scroll events, track positions, and toggle states:

const sectionA = document.querySelector('.section-a');
const stickySubtitle = document.querySelector('.sticky-subtitle');
const verticalList = document.querySelector('.vertical-infinite-list');

// 获取关键位置参数
const subtitleInitialTop = stickySubtitle.offsetTop;
const sectionATotalHeight = sectionA.offsetHeight;

// 监听滚动事件
window.addEventListener('scroll', () => {
  const currentScrollY = window.scrollY;

  if (currentScrollY < subtitleInitialTop) {
    // 第一阶段:区域A随滚动向上滑动
    sectionA.style.transform = `translateY(-${currentScrollY}px)`;
    stickySubtitle.classList.remove('fixed');
    verticalList.style.marginTop = '0'; // 重置列表上边距
  } else {
    // 第二阶段:区域A完全移出视口,副标题固定
    sectionA.style.transform = `translateY(-${sectionATotalHeight}px)`;
    stickySubtitle.classList.add('fixed');
    // 给列表添加上边距,避免内容被固定的副标题遮挡
    verticalList.style.marginTop = `${stickySubtitle.offsetHeight}px`;
  }

  // 无限滚动逻辑:滚动到接近底部时加载更多内容
  const scrollBottom = currentScrollY + window.innerHeight;
  const documentTotalHeight = document.documentElement.scrollHeight;
  
  if (scrollBottom >= documentTotalHeight - 250) {
    loadMoreVerticalContent();
  }
});

// 模拟无限滚动加载函数
function loadMoreVerticalContent() {
  const currentItemCount = verticalList.children.length + 1;
  for (let i = 0; i < 5; i++) {
    const newItem = document.createElement('div');
    newItem.className = 'vertical-item';
    newItem.textContent = `垂直内容${currentItemCount + i}`;
    verticalList.appendChild(newItem);
  }
}

Key Details to Note

  • Smooth Transitions: Adding transition to Section A ensures the upward slide feels natural instead of jerky.
  • Sticky Timing: We use offsetTop to get the subtitle's initial position relative to the viewport—this triggers the sticky state at the exact right moment.
  • Content Occlusion Fix: When the subtitle sticks, we add top margin to the vertical list so content doesn't disappear under the fixed element.
  • Performance Tip: For large lists, consider using virtual scrolling (e.g., React Virtualized, Vue Virtual Scroller) to avoid rendering thousands of DOM elements at once.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 06:35:32