IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    JavaScript实现元素吸顶(踩小坑)

    Qing发表于 2023-10-20 22:02:38
    love 0

    背景

    页面内的tab导航需要在滚动到视口顶部的时候进行吸顶,这个功能算是比较常见,也比较容易实现。刚开始按照自己想到的最简单的方法来实现,写完代码后进行测试,发现页面有很明显的bug,心里直呼大意了,特此记录下。

    错误示例

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <style>
          .page-container {
            width: 1200px;
            margin: 0 auto;
          }
          .top-area {
            height: 300px;
            background-color: lightskyblue;
            margin-bottom: 20px;
          }
    
          .tab {
            padding: 0 20px;
            display: flex;
            justify-content: space-between;
            background-color: #f4f4f4;
          }
    
          .tab.fixed {
            position: fixed;
            width: 1200px;
            box-sizing: border-box;
            top: 0;
          }
    
          .tab > div {
            padding: 10px 20px;
          }
    
          .waterfall {
            display: flex;
            justify-content: space-between;
            flex-wrap: wrap;
            align-content: center;
          }
    
          .waterfall > div {
            margin-top: 20px;
            width: 220px;
            height: 300px;
            background-color: darkseagreen;
            margin-bottom: 12px;
          }
        </style>
      </head>
      <body>
        <div class="page-container">
          <div class="top-area">huge element</div>
          <div class="tab">
            <div class="tab1">tab1</div>
            <div class="tab2">tab2</div>
            <div class="tab3">tab3</div>
            <div class="tab4">tab4</div>
            <div class="tab5">tab5</div>
            <div class="tab6">tab6</div>
            <div class="tab7">tab7</div>
            <div class="tab8">tab8</div>
            <div class="tab9">tab9</div>
            <div class="tab10">tab10</div>
          </div>
          <div class="tab-holder"></div>
          <div class="waterfall">
            <div class="item1">item1</div>
            <div class="item2">item2</div>
            <div class="item3">item3</div>
            <div class="item4">item4</div>
            <div class="item5">item5</div>
            <div class="item6">item6</div>
            <div class="item7">item7</div>
            <div class="item8">item8</div>
            <div class="item9">item9</div>
            <div class="item10">item10</div>
            <div class="item11">item11</div>
            <div class="item12">item12</div>
            <div class="item13">item13</div>
            <div class="item14">item14</div>
            <div class="item15">item15</div>
            <div class="item16">item16</div>
            <div class="item17">item17</div>
            <div class="item18">item18</div>
            <div class="item19">item19</div>
            <div class="item20">item20</div>
            <div class="item21">item21</div>
            <div class="item22">item22</div>
            <div class="item23">item23</div>
            <div class="item24">item24</div>
            <div class="item25">item25</div>
            <div class="item26">item26</div>
            <div class="item27">item27</div>
            <div class="item28">item28</div>
            <div class="item29">item29</div>
            <div class="item30">item30</div>
            <div class="item31">item31</div>
            <div class="item32">item32</div>
            <div class="item33">item33</div>
            <div class="item34">item34</div>
            <div class="item35">item35</div>
            <div class="item36">item36</div>
            <div class="item37">item37</div>
            <div class="item38">item38</div>
            <div class="item39">item39</div>
            <div class="item40">item40</div>
            <div class="item41">item41</div>
            <div class="item42">item42</div>
            <div class="item43">item43</div>
            <div class="item44">item44</div>
            <div class="item45">item45</div>
            <div class="item46">item46</div>
            <div class="item47">item47</div>
            <div class="item48">item48</div>
            <div class="item49">item49</div>
            <div class="item50">item50</div>
          </div>
        </div>
      </body>
      <script>
        const tabElem = document.querySelector(".tab");
        const tabHolder = document.querySelector(".tab-holder");
    
        const handleScroll = () => {
          const { top, height } = tabElem.getBoundingClientRect();
          console.log("top", top);
          if (top < 0) {
            tabElem.classList.add("fixed");
            tabHolder.style.height = height + "px";
          } else {
            tabElem.classList.remove("fixed");
            tabHolder.style.height = 0;
          }
        };
    
        document.addEventListener("scroll", handleScroll);
      </script>
    </html>
    

    上面的代码在打开页面后,向上滚动过程中会发现tab导航一直在闪烁,原因就是tab元素在转变成固定定位的过程,浏览器需要重新计算页面布局和重绘元素,在此期间滚动事件执行了很多遍,导致获取到的top值会出现两极跳转现象,元素会固定定位和原来的定位之间来回切换。

    image.png

    解决方案

    上面的代码由于tab导航元素的定位会发生变化,导致在滚动过程中获取到的top值有问题,之前我们是使用需要定位的上边界来进行判断,那么我们可以选取相邻元素的上下边界是否达到条件来作为tab导航是否应该转为固定定位的依据。

    const topAreaElem = document.querySelector(".top-area");
    const tabElem = document.querySelector(".tab");
    const tabHolder = document.querySelector(".tab-holder");
    
    const handleScroll = () => {
      const { bottom } = topAreaElem.getBoundingClientRect();
      const { height } = tabElem.getBoundingClientRect();
      // 这个20是tab导航与上面相邻元素之间的margin
      if (bottom + 20 < 0) {
        tabElem.classList.add("fixed");
        tabHolder.style.height = height + "px";
      } else {
        tabElem.classList.remove("fixed");
        tabHolder.style.height = 0;
      }
    };
    
    document.addEventListener("scroll", handleScroll);

    完美解决,收官。



沪ICP备19023445号-2号
友情链接