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

实现带粘性日期头部的双栏任务列表滚动交互的技术问询

实现双栏日期关联的粘性待办列表

核心需求

实现双栏布局的待办任务列表:

  • 左侧显示当前日期框,右侧按日期分组展示任务
  • 左侧日期框需跟随对应日期的首个<li>元素滚动
  • 当日期框滚动到容器顶部时,切换为粘性固定,直到下一个日期组的首个<li>滚动上来替换它

实现步骤:关联左侧日期框与右侧<li>元素

1. 标记关键元素

给每个日期组的**首个<li>**添加data-date属性,明确对应日期;同时在左侧准备日期容器,用于动态切换日期内容:

<!-- 右侧列表修改 -->
<li data-date="1/01/2025"><span>List Item #1</span><span class="date">1/01/2025</span></li>
<!-- ...其他同日期项 -->
<li data-date="2/01/2025"><span>List Item #6</span><span class="date">2/01/2025</span></li>
<!-- ...其他同日期项 -->

<!-- 左侧容器修改 -->
<div id="leftColumn">
  <div class="date-box" id="stickyDate">1/01/2025</div>
</div>

2. 预处理日期组信息

通过JS收集所有日期组的首个<li>元素,记录它们的偏移量和对应日期:

const rightColumn = document.getElementById('rightColumn');
const stickyDate = document.getElementById('stickyDate');
const dateHeaders = Array.from(document.querySelectorAll('li[data-date]'));

// 存储每个日期组的关键信息:元素、偏移量、日期
const dateGroups = dateHeaders.map(el => ({
  element: el,
  topOffset: el.offsetTop,
  date: el.dataset.date
}));

3. 监听滚动,实现跟随与粘性逻辑

在滚动事件中,判断当前可视区域内的日期组,切换左侧日期框的内容和定位:

rightColumn.addEventListener('scroll', () => {
  const scrollTop = rightColumn.scrollTop;
  const containerTop = rightColumn.offsetTop;

  // 找到当前处于顶部的日期组
  let currentDate = dateGroups[0].date;
  let targetOffset = dateGroups[0].topOffset;
  let shouldStick = false;

  for (let i = dateGroups.length - 1; i >= 0; i--) {
    const group = dateGroups[i];
    // 当元素顶部进入容器可视区域时
    if (group.topOffset <= scrollTop) {
      currentDate = group.date;
      targetOffset = group.topOffset;
      // 判断是否需要切换为粘性定位
      shouldStick = scrollTop >= targetOffset;
      break;
    }
  }

  // 更新日期内容
  stickyDate.textContent = currentDate;

  // 切换定位方式:跟随元素/粘性固定
  if (shouldStick) {
    stickyDate.style.position = 'fixed';
    stickyDate.style.top = `${containerTop}px`;
  } else {
    stickyDate.style.position = 'absolute';
    stickyDate.style.top = `${targetOffset + containerTop}px`;
  }
});

4. 调整CSS适配逻辑

修改左侧列和日期框的样式,确保定位正确:

#leftColumn {
  background-color: #fafafa;
  position: relative; /* 作为绝对定位的父容器 */
  overflow: hidden;
}

#rightColumn {
  background-color: #fff;
  overflow-y: auto; /* 开启垂直滚动 */
}

.date-box {
  width: 100px;
  height: 80px;
  background-color: #ccc;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 18px;
  border-radius: 5px;
  color: #333;
  left: 20px; /* 与左侧列padding对齐 */
}

完整代码示例

HTML(含内嵌CSS与JS)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <style>
    * {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
    }

    body {
      display: flex;
      justify-content: center;
      align-items: center;
      min-height: 100vh;
      font-family: Arial, sans-serif;
      background-color: #f0f0f0;
    }

    .container {
      display: flex;
      width: 80%;
      height: 80vh;
      border: 0px solid #ccc;
    }

    #leftColumn,
    #rightColumn {
      flex: 1;
      padding: 20px;
      border: 0px solid #ddd;
      height: 100%;
    }

    #leftColumn {
      background-color: #fafafa;
      position: relative;
      overflow: hidden;
    }

    #rightColumn {
      background-color: #fff;
      overflow-y: auto;
    }

    ul {
      list-style-type: none;
    }

    li {
      height: 80px;
      margin-bottom: 10px;
      display: flex;
      justify-content: center;
      align-items: center;
      background-color: #e0e0e0;
      border-radius: 5px;
      font-size: 18px;
    }

    .date {
      font-size: 14px;
      color: #666;
      margin-left: 10px;
    }

    .date-box {
      width: 100px;
      height: 80px;
      background-color: #ccc;
      display: flex;
      justify-content: center;
      align-items: center;
      font-size: 18px;
      border-radius: 5px;
      color: #333;
      left: 20px;
    }
  </style>
</head>
<body>
  <div class="container">
    <div id="leftColumn">
      <div class="date-box" id="stickyDate">1/01/2025</div>
    </div>
    <div id="rightColumn">
      <ul>
        <!-- Items with date 1/01/2025 -->
        <li data-date="1/01/2025"><span>List Item #1</span><span class="date">1/01/2025</span></li>
        <li><span>List Item #2</span><span class="date">1/01/2025</span></li>
        <li><span>List Item #3</span><span class="date">1/01/2025</span></li>
        <li><span>List Item #4</span><span class="date">1/01/2025</span></li>
        <li><span>List Item #5</span><span class="date">1/01/2025</span></li>

        <!-- Items with date 2/01/2025 -->
        <li data-date="2/01/2025"><span>List Item #6</span><span class="date">2/01/2025</span></li>
        <li><span>List Item #7</span><span class="date">2/01/2025</span></li>
        <li><span>List Item #8</span><span class="date">2/01/2025</span></li>
        <li><span>List Item #9</span><span class="date">2/01/2025</span></li>
        <li><span>List Item #10</span><span class="date">2/01/2025</span></li>

        <!-- Items with date 3/01/2025 -->
        <li data-date="3/01/2025"><span>List Item #11</span><span class="date">3/01/2025</span></li>
        <li><span>List Item #12</span><span class="date">3/01/2025</span></li>
        <li><span>List Item #13</span><span class="date">3/01/2025</span></li>
        <li><span>List Item #14</span><span class="date">3/01/2025</span></li>
        <li><span>List Item #15</span><span class="date">3/01/2025</span></li>
      </ul>
    </div>
  </div>
  <script>
    const rightColumn = document.getElementById('rightColumn');
    const stickyDate = document.getElementById('stickyDate');
    const dateHeaders = Array.from(document.querySelectorAll('li[data-date]'));

    const dateGroups = dateHeaders.map(el => ({
      element: el,
      topOffset: el.offsetTop,
      date: el.dataset.date
    }));

    // 初始化位置
    stickyDate.style.top = `${dateGroups[0].topOffset + rightColumn.offsetTop}px`;
    stickyDate.style.position = 'absolute';

    rightColumn.addEventListener('scroll', () => {
      const scrollTop = rightColumn.scrollTop;
      const containerTop = rightColumn.offsetTop;

      let currentDate = dateGroups[0].date;
      let targetOffset = dateGroups[0].topOffset;
      let shouldStick = false;

      for (let i = dateGroups.length - 1; i >= 0; i--) {
        const group = dateGroups[i];
        if (group.topOffset <= scrollTop) {
          currentDate = group.date;
          targetOffset = group.topOffset;
          shouldStick = scrollTop >= targetOffset;
          break;
        }
      }

      stickyDate.textContent = currentDate;

      if (shouldStick) {
        stickyDate.style.position = 'fixed';
        stickyDate.style.top = `${containerTop}px`;
      } else {
        stickyDate.style.position = 'absolute';
        stickyDate.style.top = `${targetOffset + containerTop}px`;
      }
    });
  </script>
</body>
</html>

关键逻辑说明

  • 元素标记:通过data-date绑定日期组的首个<li>和左侧日期框的内容关联
  • 滚动监听:实时检测右侧列表的滚动位置,匹配当前可视区域内的日期组
  • 定位切换:根据元素偏移量判断是让日期框跟随对应<li>,还是固定在容器顶部

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 06:07:02