用position: sticky实现一个通讯录的功能

206 阅读8分钟

场景

在项目中,常常会碰上这么个效果,就是当页面滚动时要使某个元素(如导航栏、侧边栏、表头之类的)滚动到指定的位置不再滚动。

要想实现这个效果,就得用到接下来所讲的粘性定位

什么是粘性定位

position: sticky 是 CSS 中一种特殊的定位方式,它可以让元素在滚动到特定位置时"粘住"在视口中。

基本用法

.sticky-element {
  position: sticky;
  top: 0; /* 触发粘性的阈值 */
}

工作原理:

  1. 常规定位阶段:元素最初表现为相对定位(relative),保留在文档流中
  2. 粘性阶段:当视口滚动到元素达到指定的阈值(如 top: 0)时,元素"粘住"在视口中
  3. 脱离阶段:当元素的父容器完全滚出视口时,元素会随父容器一起滚出。即粘性元素只能在父容器内粘住,当父容器滚出视口时,粘性元素会随之滚出

案例

案例1

<!DOCTYPE html>
<html>
  <head>
    <style>
      body {
        margin: 0;
        font-family: Arial;
        /* 增加页面高度以演示滚动 */
        height: 2000px;
      }

      /* 粘性导航栏 */
      .sticky-nav {
        position: sticky;
        top: 0; /* 触发阈值:当导航栏碰到视口顶部时固定 */
        background: #333;
        color: white;
        padding: 15px;
        text-align: center;
      }

      /* 内容区块 */
      .content {
        padding: 20px;
        height: 800px; /* 增加高度以便滚动 */
        background: #f0f0f0;
      }
    </style>
  </head>
  <body>
    <div class="content">
      <p>滚动页面向下 ↓↓↓</p>
    </div>

    <!-- 粘性导航栏 -->
    <div class="sticky-nav">我是粘性导航栏</div>

    <div class="content">
      <p>继续滚动观察导航栏行为</p>
    </div>
  </body>
</html>
  1. 父元素是body元素,同时增加了body的高度是2000px,使其能够滚动。

  2. 给导航栏设置position: sticky; top: 0,当导航栏碰到视口顶部时固定。

这里需要注意top:0;相对于离该元素最近的具有滚动条的祖先元素,这里的具有滚动条的元素是body。

案例二

这个案例在上面案例的基础上增加了一个退出的功能。

当粘性元素的父元素离开了视口的时候,那么粘性元素也会随之退出。

<!DOCTYPE html>
<html>
  <head>
    <style>
      body {
        margin: 0;
        font-family: Arial;
      }

      /* 父容器 - 限制粘性元素的作用范围 */
      .container {
        height: 800px; /* 明确高度 */
        background: #f0f0f0;
        margin-bottom: 50px; /* 增加间隔 */
        border: 2px dashed #999; /* 可视化父容器边界 */
      }

      /* 粘性元素 */
      .sticky-box {
        position: sticky;
        top: 20px; /* 触发阈值 */
        height: 60px;
        background: #ff6b6b;
        color: white;
        display: flex;
        align-items: center;
        justify-content: center;
        font-weight: bold;
      }

      /* 普通内容块 */
      .content {
        height: 200px;
        padding: 20px;
      }
    </style>
  </head>
  <body>
    <div class="content">
      <h2>向下滚动 ↓↓↓</h2>
      <p>观察红色粘性块的行为变化</p>
    </div>

    <!-- 粘性元素的父容器 -->
    <div class="container">
      <div class="content">
        <p>父容器内的内容(上方空间)</p>
      </div>

      <!-- 粘性元素 -->
      <div class="sticky-box">我是粘性元素 (top: 20px)</div>

      <div class="content">
        <p>继续滚动我会先固定,然后突然消失(脱离阶段)</p>
      </div>
    </div>

    <div class="content">
      <h2>父容器外部的区域</h2>
      <p>当父容器完全滚出视口时,粘性元素会解除固定状态</p>
    </div>
    <div class="content">
      <h2>父容器外部的区域</h2>
      <p>当父容器完全滚出视口时,粘性元素会解除固定状态</p>
    </div>
    <div class="content">
      <h2>父容器外部的区域</h2>
      <p>当父容器完全滚出视口时,粘性元素会解除固定状态</p>
    </div>
    <div class="content">
      <h2>父容器外部的区域</h2>
      <p>当父容器完全滚出视口时,粘性元素会解除固定状态</p>
    </div>
  </body>
</html>

通过上面两个案例,我们理解了粘性元素的使用,这里总结一下:

  1. 确定粘性元素,给其添加position: sticky属性;

  2. 确定触发粘性行为的位置,即你希望它滚动到哪个位置不再滚动,如top:0;(注意:这里的top:0;相对于离该元素最近的具有滚动条的祖先元素,一般是视窗);

<div style="height: 300px; overflow: auto; border: 1px solid black">
  <!-- 滚动容器 -->
  <div style="height: 1000px">
    <div style="height: 80px; background-color: aquamarine"></div>
    <div
      style="position: sticky; top: 20px; background-color: antiquewhite"
    >
      我是粘性元素
    </div>
    <div style="height: 80px; background-color: aquamarine"></div>
  </div>
</div>
  1. 何时失效:当粘性元素的边缘和父元素的临界元素边缘重合时,会随着父元素一起滚走,这时粘性就失效了。也就是父元素离开了视口。

  2. Z-index问题:在某些情况下,如果页面上其他元素的z-index较高,可能会覆盖或影响粘性元素的可见性。

实现通讯录的功能

当页面滚动时,效果图如下:

image.png

image.png

image.png

image.png

image.png

<div
    class="relative max-w-md mx-auto bg-white dark:bg-slate-800 shadow-lg h-80 overflow-auto ring-1 ring-slate-900/5 -my-px"
  >
    <div class="relative">
      <div
        class="sticky top-0 px-4 py-3 flex items-center font-semibold text-sm text-slate-900 dark:text-slate-200 bg-slate-50/90 dark:bg-slate-700/90 backdrop-blur-sm ring-1 ring-slate-900/10 dark:ring-black/10"
      >
        A
      </div>
      <div class="divide-y dark:divide-slate-200/5">
        <div class="flex items-center gap-4 p-4">
          <img
            class="w-12 h-12 rounded-full"
            src="https://images.unsplash.com/photo-1501196354995-cbb51c65aaea?ixlib=rb-1.2.1&amp;ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&amp;auto=format&amp;fit=facearea&amp;facepad=4&amp;w=256&amp;h=256&amp;q=80"
          />
          <strong class="text-slate-900 text-sm font-medium dark:text-slate-200"
            >Andrew Alfred</strong
          >
        </div>
        <div class="flex items-center gap-4 p-4">
          <img
            class="w-12 h-12 rounded-full"
            src="https://images.unsplash.com/photo-1531123897727-8f129e1688ce?ixlib=rb-1.2.1&amp;ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&amp;auto=format&amp;fit=facearea&amp;facepad=4&amp;w=256&amp;h=256&amp;q=80"
          />
          <strong class="text-slate-900 text-sm font-medium dark:text-slate-200"
            >Aisha Houston</strong
          >
        </div>
        <div class="flex items-center gap-4 p-4">
          <img
            class="w-12 h-12 rounded-full"
            src="https://images.unsplash.com/photo-1517841905240-472988babdf9?ixlib=rb-1.2.1&amp;ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&amp;auto=format&amp;fit=facearea&amp;facepad=4&amp;w=256&amp;h=256&amp;q=80"
          />
          <strong class="text-slate-900 text-sm font-medium dark:text-slate-200"
            >Anna White</strong
          >
        </div>
        <div class="flex items-center gap-4 p-4">
          <img
            class="w-12 h-12 rounded-full"
            src="https://images.unsplash.com/photo-1531427186611-ecfd6d936c79?ixlib=rb-1.2.1&amp;ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&amp;auto=format&amp;fit=facearea&amp;facepad=4&amp;w=256&amp;h=256&amp;q=80"
          />
          <strong class="text-slate-900 text-sm font-medium dark:text-slate-200"
            >Andy Flint</strong
          >
        </div>
      </div>
    </div>
    <div class="relative">
      <div
        class="sticky top-0 px-4 py-3 flex items-center font-semibold text-sm text-slate-900 dark:text-slate-200 bg-slate-50/90 dark:bg-slate-700/90 backdrop-blur-sm ring-1 ring-slate-900/10 dark:ring-black/10"
      >
        B
      </div>
      <div class="divide-y dark:divide-slate-200/5">
        <div class="flex items-center gap-4 p-4">
          <img
            class="w-12 h-12 rounded-full"
            src="https://images.unsplash.com/photo-1501196354995-cbb51c65aaea?ixlib=rb-1.2.1&amp;ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&amp;auto=format&amp;fit=facearea&amp;facepad=4&amp;w=256&amp;h=256&amp;q=80"
          />
          <strong class="text-slate-900 text-sm font-medium dark:text-slate-200"
            >Bob Alfred</strong
          >
        </div>
        <div class="flex items-center gap-4 p-4">
          <img
            class="w-12 h-12 rounded-full"
            src="https://images.unsplash.com/photo-1531123897727-8f129e1688ce?ixlib=rb-1.2.1&amp;ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&amp;auto=format&amp;fit=facearea&amp;facepad=4&amp;w=256&amp;h=256&amp;q=80"
          />
          <strong class="text-slate-900 text-sm font-medium dark:text-slate-200"
            >Bianca Houston</strong
          >
        </div>
        <div class="flex items-center gap-4 p-4">
          <img
            class="w-12 h-12 rounded-full"
            src="https://images.unsplash.com/photo-1517841905240-472988babdf9?ixlib=rb-1.2.1&amp;ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&amp;auto=format&amp;fit=facearea&amp;facepad=4&amp;w=256&amp;h=256&amp;q=80"
          />
          <strong class="text-slate-900 text-sm font-medium dark:text-slate-200"
            >Brianna White</strong
          >
        </div>
        <div class="flex items-center gap-4 p-4">
          <img
            class="w-12 h-12 rounded-full"
            src="https://images.unsplash.com/photo-1531427186611-ecfd6d936c79?ixlib=rb-1.2.1&amp;ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&amp;auto=format&amp;fit=facearea&amp;facepad=4&amp;w=256&amp;h=256&amp;q=80"
          />
          <strong class="text-slate-900 text-sm font-medium dark:text-slate-200"
            >Bert Flint</strong
          >
        </div>
      </div>
    </div>
    <div class="relative">
      <div
        class="sticky top-0 px-4 py-3 flex items-center font-semibold text-sm text-slate-900 dark:text-slate-200 bg-slate-50/90 dark:bg-slate-700/90 backdrop-blur-sm ring-1 ring-slate-900/10 dark:ring-black/10"
      >
        C
      </div>
      <div class="divide-y dark:divide-slate-200/5">
        <div class="flex items-center gap-4 p-4">
          <img
            class="w-12 h-12 rounded-full"
            src="https://images.unsplash.com/photo-1501196354995-cbb51c65aaea?ixlib=rb-1.2.1&amp;ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&amp;auto=format&amp;fit=facearea&amp;facepad=4&amp;w=256&amp;h=256&amp;q=80"
          />
          <strong class="text-slate-900 text-sm font-medium dark:text-slate-200"
            >Colton Alfred</strong
          >
        </div>
        <div class="flex items-center gap-4 p-4">
          <img
            class="w-12 h-12 rounded-full"
            src="https://images.unsplash.com/photo-1531123897727-8f129e1688ce?ixlib=rb-1.2.1&amp;ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&amp;auto=format&amp;fit=facearea&amp;facepad=4&amp;w=256&amp;h=256&amp;q=80"
          />
          <strong class="text-slate-900 text-sm font-medium dark:text-slate-200"
            >Cynthia Houston</strong
          >
        </div>
        <div class="flex items-center gap-4 p-4">
          <img
            class="w-12 h-12 rounded-full"
            src="https://images.unsplash.com/photo-1517841905240-472988babdf9?ixlib=rb-1.2.1&amp;ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&amp;auto=format&amp;fit=facearea&amp;facepad=4&amp;w=256&amp;h=256&amp;q=80"
          />
          <strong class="text-slate-900 text-sm font-medium dark:text-slate-200"
            >Cheyenne White</strong
          >
        </div>
        <div class="flex items-center gap-4 p-4">
          <img
            class="w-12 h-12 rounded-full"
            src="https://images.unsplash.com/photo-1531427186611-ecfd6d936c79?ixlib=rb-1.2.1&amp;ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&amp;auto=format&amp;fit=facearea&amp;facepad=4&amp;w=256&amp;h=256&amp;q=80"
          />
          <strong class="text-slate-900 text-sm font-medium dark:text-slate-200"
            >Charlie Flint</strong
          >
        </div>
      </div>
    </div>
  </div>

上面代码的CSS采用tailwindcss

  1. 通过设置sticky top-0, A、B等元素变成了粘性元素。

  2. 当滚动时,因为给A设置了top:0;,所以A相对于最近的具有滚动条的祖先元素保持不动。

  3. 当继续滚动时,粘性元素的边缘和父元素的临界元素边缘重合时,会随着父元素一起滚走。也就是父元素都滚出了视口。

  4. 此时,B粘性元素就顶上去,当距离祖先滚动元素的距离等于top:0;时,就保证固定不动。

这样就用很简单的代码实现了一个通讯录的功能。