• 仿照旧版支付宝生活服务模块-滚动定位 + 点击定位(上)


    想要实现的效果:

    1. 顶部tab栏标题与下面列表标题对应,滚动超过tab栏高度,吸顶定位
    2. 向下滚动时,列表出现标题与顶部tab栏高亮标题对应
    3. 点击顶部tab栏标题,下面列表滚动到对应标题,且tab标题栏相应左右滚动到合适位置

    具体效果如图:

    又到了说实现原理的时刻,让我想想哈,有了如下:

    1.吸顶:

      给页面添加一个滚动监听事件,在里面动态获取tab栏到顶部的距离, 以及滚动距离,二者比较确定是否添加吸顶类名

    2.滚动定位:

       由于顶部tab栏与下面列表栏都是用for循环生成的li标签,就可以通过遍历tab栏标题数组,当滚动距离+ 110(tab栏自身高度 + 到顶部距离) >= 列表中每个模块到顶部距离时,tab栏对应标题高亮,且左右滚动一段距离

    3.点击定位:

      获取点击标题,与内容列表中标题做判断,相同时,对应列表中模块进行滚动操作,滚动距离为 = 列表模块到顶部距离 - 110(tab栏自身高度 + 到顶部距离)

    4.设置列表高度:

    • 要想再点击最后一个标题,页面也滚动到最后列表模块的话,那就需要给列表设置足够的高度,有留白,好让他往上滚动
    • 计算公式 = 屏幕高度 + 列表中最后模块到顶部距离offsetTop - (110 + 列表中最后模块高度 + padding) 
    • 通过行内属性,动态添加高度,为了让页面一打开就设置好高度,需要在 mounted 钩子中添加以上计算

     tab栏横向滑动实现代码:

    <!-- 标题栏 -->
        <header class="title_father">
          <ul :class="['title_list_box',{'titleFixed':Fixed}]" ref="titleRef">
            <li
              v-for="(item,index) in titleList"
              :key="index"
              class="title_list_item"
              :class="{active: tabChose == index}"
              @click="handleTab(index)"
            >
              <div :class="{active: tabChose == index}">{{item.title}}</div>
            </li>
          </ul>
        </header>
    
    /************ 样式 ****************/
    .title_father {
        height: 1rem;
        background-color: #fff;
         100%;
        overflow: hidden;
        ::-webkit-scrollbar {
          display: none;
        }
        .title_list_box {
          padding-bottom: 0.2rem;
          height: 1rem;
          background-color: #fff;
          z-index: 5;
          display: -webkit-box;
          overflow-x: scroll;
          overflow-y: hidden;
          -webkit-overflow-scrolling: touch;
          border-bottom: 1px solid #f5f5f9;
          &.titleFixed {
            position: fixed;
            top: 1rem;
          }
          .title_list_item {
            height: 1rem;
            display: flex;
            align-items: center;
            margin: 0 0.25rem;
            font-size: 0.28rem;
            font-weight: 700;
            color: #333333;
            position: relative;
            &.active {
              color: #fd1a84;
              // border-bottom: 0.04rem solid #1d8ff3;
              &::after {
                content: "";
                position: absolute;
                 1.12rem;
                height: 0.04rem;
                background-color: #fd1a84;
                left: 0;
                bottom: 0.02rem;
              }
            }
            &:last-child {
               1.7rem;
              border-bottom: none;
              position: relative;
              &.active {
                &::after {
                  content: "";
                  position: absolute;
                   1.12rem;
                  height: 0.04rem;
                  background-color: #fd1a84;
                  bottom: 0.02rem;
                }
              }
            }
          }
        }
      }

    核心代码主要是上面标红部分:

    • html结构是: 一个父div 包含 ul标签
    • 父div,100%(一般是保持等于屏幕宽度),设置超出隐藏: overflow: hidden;
    • 子元素ul 设置自适应布局:display:-webkit-box;(li元素全部排成一行),设置x轴滚动,y轴隐藏,以及滚动顺滑
        overflow-x: scroll;
        overflow-y: hidden;
        -webkit-overflow-scrolling: touch;

    滚动定位 + 点击定位 实现代码:

    html:

        <!-- 标题栏 -->
        <header class="title_father">
          <ul :class="['title_list_box',{'titleFixed':Fixed}]" ref="titleRef">
            <li
              v-for="(item,index) in titleList"
              :key="index"
              class="title_list_item"
              :class="{active: tabChose == index}"
              @click="handleTab(index)"
            >
              <div :class="{active: tabChose == index}">{{item.title}}</div>
            </li>
          </ul>
        </header>
    
        <!-- 内容列表 -->
        <ul
          :class="['content_list_box',{'contentFixed':Fixed}]"
          :style="{height:(screenHeight + lastTop - 260) + 'px'}"
        >
          <li
            v-for="(item,index) in contentBox"
            :key="index"
            class="content_list_item"
            @scroll="contentScroll(index)"
            ref="contentRef"
            @click="goPage(item)"
          >
            <div class="content_title" v-if="index" ref="contentBox">{{item.title}}</div>
            <div class="content_box">
              <div
                v-for="(subItem,subIndex) in item.contentList"
                :key="subIndex"
                class="content_item"
                @click="goLifePage(subItem,subIndex)"
              >
                <div class="item_inner" :class="{'selectStatus':isEdit}">
                  <i
                    :style="{background:`url(${require(`@/assets/images/life/index/${subItem.icon_name}.png`)}) no-repeat center/100%`}"
                    class="icon"
                  ></i>
                  <p class="content_name">{{subItem.content_name}}</p>
                  <i class="jia_icon" v-if="isEdit" :class="{'jianIcon':subItem.changeIcon}"></i>
                </div>
              </div>
            </div>
            <div class="line" v-if="index<contentBox.length-1"></div>
          </li>
        </ul>

    js:

     data() {
        return {
          titleList: [
            {
              title: "视频娱乐",
              id: 0
            },
            {
              title: "卡密相关",
              id: 1
            },
            {
              title: "充值中心",
              id: 2
            },
            {
              title: "生活缴费",
              id: 3
            },
            {
              title: "车务交罚",
              id: 4
            },
            {
              title: "生活服务",
              id: 5
            }
          ], // tab标题数据
          tabChose: 0, // 高亮索引值
          contentBox: [
            {
              type: "videoEnt",
              title: "视频娱乐",
              contentList: [
                {
                  type: "iqiyiVideo",
                  icon_name: "aiqiyi@2x",
                  content_name: "爱奇艺",
                  changeIcon: false
                },
                {
                  type: "tencentVideo",
                  icon_name: "tengxunshipin@2x",
                  content_name: "腾讯视频",
                  changeIcon: false
                },
                {
                  type: "youkuVideo",
                  icon_name: "youkushipin@2x",
                  content_name: "优酷视频",
                  changeIcon: false
                }
              ]
            },
            {
              title: "卡密相关",
              contentList: [
                {
                  type: "jingdongEcard",
                  icon_name: "jingdongeka@2x",
                  content_name: "京东E卡卡密",
                  changeIcon: false
                },
                {
                  type: "wangyiEcard",
                  icon_name: "wangyi@2x",
                  content_name: "网易严选",
                  changeIcon: false
                }
              ]
            },
            {
              title: "充值中心",
              contentList: [
                {
                  type: "FlowRecharge",
                  icon_name: "liuliang@2x",
                  content_name: "流量充值",
                  changeIcon: false
                },
                {
                  type: "PhoneRecharge",
                  icon_name: "huafei@2x",
                  content_name: "话费充值",
                  changeIcon: false
                }
              ]
            },
            {
              title: "生活缴费",
              contentList: [
                {
                  type: "lifePayment",
                  icon_name: "shuifei@2x",
                  content_name: "水费查缴",
                  changeIcon: false
                },
                {
                  type: "lifePayment",
                  icon_name: "dianfei@2x",
                  content_name: "电费查缴",
                  changeIcon: false
                },
                {
                  type: "lifePayment",
                  icon_name: "ranqi@2x",
                  content_name: "燃气查缴",
                  changeIcon: false
                }
              ]
            },
            {
              title: "车务交罚",
              contentList: [
                {
                  type: "violationSearch",
                  icon_name: "chaxun@2x",
                  content_name: "违章查询",
                  changeIcon: false
                },
                {
                  type: "violationPay",
                  icon_name: "jiaofei@2x",
                  content_name: "违章缴费",
                  changeIcon: false
                }
              ]
            },
            {
              type: "",
              title: "生活服务",
              contentList: [
                {
                  type: "weather",
                  icon_name: "zhiliang@2x",
                  content_name: "天气预报",
                  changeIcon: false
                },
                {
                  type: "aqi",
                  icon_name: "yubao@2x",
                  content_name: "空气质量",
                  changeIcon: false
                }
              ]
            }
          ], // 列表数据
          Fixed: false, // 是否吸顶
          scroll: 0, // y轴滚动距离
          screenHeight: document.body.clientHeight, //屏幕高度
          titleArr: "", // tab栏数组
          contentArr: "", // 列表栏数组
          titleBox: "", // tab栏父元素
          titleTop: 0, // tab栏到顶部的距离
          titleBoxHeight: 0, //标题栏的高度
          lastTop: 0, //最后一个内容距离顶部的高度
        };
      },
      mounted() {
        this.titleArr = document.querySelectorAll(".title_list_item");
        this.contentArr = document.querySelectorAll(".content_list_item");
        this.titleBox = document.querySelector(".title_list_box");
        window.addEventListener("scroll", this.handleScroll);
        // 获取屏幕的高度
        const that = this;
        window.onresize = () => {
          return (() => {
            window.screenHeight = document.body.clientHeight;
            that.screenHeight = window.screenHeight;
          })();
        };
        this.setContentHeight();
      },
       methods: {
        // tab栏切换
        handleTab(index) {
          this.tabChose = index;
          if (this.titleList[index].title === this.contentBox[index].title) {
            this.contentScroll(index);
          }
        },
        // 点击内容发生相应变化
        contentScroll(index) {
          // 获取每一个内容offsetTop高度
          let contentTop = this.contentArr[index].offsetTop;
          // 获取tab栏的高度
          this.titleBoxHeight =
            this.titleBox.offsetHeight || this.titleBox.clientHeight;
          window.scrollTo(0, contentTop - 110); // 55
        },
        //滚动监听
        handleScroll() {
          // 获取滚动距离
          this.scroll =
            document.documentElement.scrollTop || document.body.scrollTop;
          // 获取tab栏到顶部的距离
          if (document.querySelector(".title_list_box")) {
            this.titleTop = document.querySelector(".title_list_box ").offsetTop;
          }
          // 判断距离进行吸顶操作
          if (this.scroll > this.titleTop) {
            this.Fixed = true;
          } else {
            this.Fixed = false;
            this.tabChose = 0;
          }
          this.titleTop = 40;
          // 循环判断 滚动的距离 + 标题盒子的高度 >= 某条内容的高度时,设置该标题高亮,标题栏发生相应的滚动
          for (var i = 0; i < this.titleArr.length; i++) {
            if (this.scroll + this.titleTop * 2 >= this.contentArr[i].offsetTop) {
              // 1.5
              this.tabChose = i + 1;
              // scrollLeft代替scrollTo,解决安卓手机在微信浏览器中scrollTo事件失效
              document.querySelector(".title_list_box").scrollLeft = i * 30; // 25
            }
          }
        },
        // 动态设置内容总体提高
        setContentHeight() {
          this.lastTop = this.contentArr[5].offsetTop;
          console.log(this.contentArr[5].offsetHeight);
        },
       }

    还有一部分样式代码,我就不在上传了,核心实现代码皆以上传

    分享一刻:

    一款类似于markdown的笔记应用

    https://github.com/88250/liandi

     

  • 相关阅读:
    ios添加第三方字体
    IOS 适应各种iphone屏幕尺寸
    sqlite第三方类库FMDB的使用
    IOS--沙盒机制
    用plist建立UITabController
    Xcode7 使用NSURLSession发送HTTP请求报错[转]
    网络开发--NSURLConnection类的简单介绍
    TF-IDF
    《汇编语言》——王爽 第12章 内中断
    操作系统 L4操作系统接口+L5系统调用的实现(网易公开课)
  • 原文地址:https://www.cnblogs.com/huangaiya/p/12841291.html
Copyright © 2020-2023  润新知