vue导航点击scrollTop的平滑滚动

839 阅读1分钟
<template>
  <div :class="['container',{fixed:isFixedNav}]" v-if="isLoading">
    <div :class="['sort-nav']" ref="scrollbox" v-show="isShowNav">
      <div class="sort-list">
        <div :class="['sort-item',`nav${i}`,{active:current===i}]" v-for="(item,i) in groupInfo" :key="i" @click="changeNav(i)">
          <img :src="item.groupIcon" alt="">
          <h4>{{item.groupName}}</h4>
        </div>
      </div>
    </div>
    <div class="sort-content">
      <div :class="['sort-module',,`module${i}`]" v-for="(item,i) in groupInfo" :key="i">
        <div class="module-title" v-if="isShowNav">
          <p>{{item.groupName}}</p>
        </div>
        <div class="module-product">
          <div class="product-item" v-for="(it,j) in item.dataList" :key="j" @click="toMallProduct(it)">
            <div class="l-icon">
              <img :src="it.mainPic" alt="">
            </div>
            <div class="r-info">
              <div class="name">
                <i v-for="(x,y) in it.titleTag&&it.titleTag.split(',')" :key="y" :class="[{yx:x==='优选'},{xp:x==='新品'},{th:x==='特惠'},]">{{x}}</i><span>{{it.brand}}{{it.name}}</span>
              </div>
              <div class="desc">
                <p>{{it.description}}</p>
              </div>
              <div class="price">
                <i></i><b>{{it.priceAfterCoupon||it.salePrice}}</b> <em v-if="it.priceAfterCoupon">券后价</em> <span class="old">¥{{it.originPrice}} </span>
                <span v-if="it.monthSaleNum>0">月售{{it.monthSaleNum}}</span>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import BScroll from 'better-scroll'
import { queryKdfMallProductListV2 } from 'service'
export default {
  data() {
    return {
      isLoading: false,
      groupId: '',
      upGroupId: '',
      groupInfo: {},
      current: 0,
      scrollTop: 0,
      scrollHeight: 0, // 页面高度
      clientHeight: 0, // 屏幕高度
      windowRatio: 0, // 屏占比
      isFixedNav: false,
      timer: null
    }
  },
  computed: {
    isShowNav() {
      return this.groupInfo.length > 1
    },
    navOffsetHeight() {
      return document.documentElement.querySelector(`.sort-nav`).offsetHeight || 0
    },
    currentOffsetTop() {
      return document.documentElement.querySelector(`.module${this.current}`).offsetTop - this.navOffsetHeight || 0
    },
    nextOffsetTop() {
      if (this.current === this.groupInfo.length - 1) {
        return this.scrollHeight - this.clientHeight
      } else {
        return document.documentElement.querySelector(`.module${this.current + 1}`).offsetTop - this.navOffsetHeight || 0
      }
    }
  },
  filters: {
    computeSales(number) {
      let day = new Date().getDate()
      return parseInt(Number(number) * (day * 3 + 5) / 3)
    }
  },
  methods: {
    changeNav(i) {
      this.backToId(i)
    },
    toMallProduct(data) {
      sessionStorage.setItem('mallSortProductList', this.scrollTop)
      // console.log(data)
      this.routerPush({
        path: '/member/mallProduct',
        query: {
          mallProductId: data.id
        }
      })
    },
    getSortProductList() {
      this.isLoading = false
      const { uid, token } = this.$store.state.user.userInfo
      queryKdfMallProductListV2({
        uid,
        token,
        groupId: this.groupId,
        upGroupId: this.upGroupId,
        showStyle: 'groupList',
        productIds: '',
        pageNo: 0,
        pageSize: 200
      }).then(res => {
        // console.log(res)
        this.isLoading = true
        this.ToastWithLoading().hide()
        const {data, message, code} = res.data
        // console.log(data)
        if (code === 200) {
          this.groupInfo = data.data.productInfo
          if (this.isShowNav) {
            this.initScrollNav()
          }
          this.$nextTick(() => {
            this.setScrollTop(Number(sessionStorage.getItem('mallSortProductList')) || 0)
            sessionStorage.setItem('mallSortProductList', 0)
          })
        } else {

        }
      }).catch(e => {
        this.isLoading = true
        this.ToastWithLoading().hide()
        this.ToastWithText('获取数据失败')
      })
    },
    initScrollNav() {
      this.$nextTick(() => {
        // 设置顶部滚动 navlist
        this.scrollmethod = new BScroll(this.$refs.scrollbox, {
          bounce: true,
          eventPassthrough: 'vertical',
          scrollX: true,
          scrollY: false,
          preventDefault: false
        })
      })
    },
    backToId(id) {
      clearTimeout(this.timer)
      this.timer = null
      let elOffsetTop = document.documentElement.querySelector(`.module${id}`).offsetTop - this.navOffsetHeight + 3// 加3是为了提前切换tab
      let distance = document.documentElement.scrollTop || document.body.scrollTop // 获取当前页面的滚动条纵坐标位置
      // console.log(`当前${distance},元素${elOffsetTop}`)
      // 平滑滚动,每10ms一跳50
      let step = 50
      let smoothDown = () => {
        if (distance < elOffsetTop && distance + 2 * step < elOffsetTop) {
          distance += step
          this.setScrollTop(distance)
          this.timer = setTimeout(smoothDown, 10)
        } else {
          this.setScrollTop(elOffsetTop)
        }
      }
      let smoothUp = () => {
        if (distance > elOffsetTop && distance - 2 * step > elOffsetTop) {
          distance -= step
          this.setScrollTop(distance)
          this.timer = setTimeout(smoothUp, 10)
        } else {
          this.setScrollTop(elOffsetTop)
        }
      }
      if (elOffsetTop > distance) {
        smoothDown()
      } else {
        smoothUp()
      }
    },
    setScrollTop(scrollTop) {
      // console.log('setScrollTop', scrollTop)
      document.body.scrollTop = scrollTop
      document.documentElement.scrollTop = scrollTop
    },
    scrollInit() {
      this.clientHeight = document.documentElement.clientHeight || document.body.clientHeight
      this.windowRatio = document.documentElement.clientWidth / 375 || 1
      // console.log(this.windowRatio)
      window.addEventListener('scroll', () => {
        this.scrollTop = window.pageYOffset || document.scrollTop || document.body.scrollTop
        this.scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight
      })
    }
  },
  created() {
    this.groupId = this.$route.query.groupId || ''
    this.upGroupId = this.$route.query.upGroupId || ''
    document.title = this.$route.query.title || ''
    this.getSortProductList()
  },
  mounted() {
    this.scrollInit()
  },
  destroyed() {
    clearTimeout(this.timer)
  },
  watch: {
    current: {
      deep: true,
      handler(newStatus, oldStatus) {
        let dom = document.querySelector(`.nav${newStatus}`)
        // console.log(dom)
        this.$nextTick(() => {
        // 标题滚动到对应位置  五个参数 滚动到元素 时间  水平偏移 竖直偏移  后两个为true 则是中心位置 最后一个 设置动画效果
          this.scrollmethod.scrollToElement(dom, null, true, true)
        })
      }
    },
    scrollTop: {
      deep: true,
      handler(newStatus, oldStatus) {
        // console.log('scrollTop变化', newStatus)
        // 设置导航active
        // console.log(`当前${this.currentOffsetTop}---最大${this.nextOffsetTop}---位置${this.current}---滚动${this.scrollTop}`)
        if (this.scrollTop >= this.nextOffsetTop && this.current < this.groupInfo.length - 1) {
          // console.log('current变化+1', this.current)
          this.current = this.current + 1
        } else if (this.scrollTop < this.currentOffsetTop && this.current > 0) {
          // console.log('current变化-1', this.current)
          this.current = this.current - 1
        }
        // 设置导航吸顶
        if (this.isShowNav) {
          let navScrollTop = document.documentElement.querySelector(`.module0`).offsetTop - this.navOffsetHeight - 3// 减3是为了提前定位
          if (this.scrollTop >= navScrollTop) {
            this.isFixedNav = true
          } else {
            this.isFixedNav = false
          }
        }
      }
    }
  }
}
</script>

<style lang="less" scoped>
.container{
  max-width: 10rem;
  padding-bottom: 2.666667rem;
  .sort-nav{
    width: 10rem;
    height: 2.666667rem;
    background-color: #EDEEEF;
    padding: .266667rem 0;
    position: relative;
    overflow: hidden;
    z-index: 1;
    .sort-list{
      display: inline-flex;
      flex-wrap: nowrap;
      padding: 0 .4rem;
      .sort-item{
        width: 1.866667rem;
        height: 2.133333rem;
        background-color: #fff;
        border-radius: .08rem;
        margin-right: .266667rem;
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        img{
          display: block;
          width: 1.226667rem;
          height: 1.226667rem;
        }
        h4{
          font-size: .32rem;
          font-weight: 400;
          color: #000;
          line-height: .533333rem;
          margin-top: .106667rem;
        }
        &.active h4{
          font-weight: 600;
        }
      }
    }
  }
  .sort-content{
    background-color: #fff;
    .sort-module{
      padding: .266667rem .4rem;
      .module-title{
        padding: .266667rem 0;
        p{
          font-size: .32rem;
          font-weight: 400;
          color: #888;
          line-height: .48rem;
        }
      }
      .module-product{
        .product-item{
          display: flex;
          margin-bottom: .266667rem;
          &:last-child{
            margin-bottom: 0;
          }
          .l-icon{
            width: 2.666667rem;
            height: 2.666667rem;
            border-radius: .16rem;
            img{
              display: block;
              width: 100%;
              height: 100%;
            }
          }
          .r-info{
            flex: 1;
            padding: .053333rem 0 .053333rem .4rem;
            .name{
              height: .8rem;
              text-overflow: -o-ellipsis-lastline;
              overflow: hidden;
              text-overflow: ellipsis;
              display: -webkit-box;
              -webkit-line-clamp: 2;
              line-clamp: 2;
              -webkit-box-orient: vertical;
              word-break:break-all;
              i{
                display: inline-block;
                font-size: .32rem;
                font-weight: 600;
                padding:.053333rem ;
                border-radius: .053333rem;
                font-family: PangMenZhengDao;
                transform: scale(0.8);
                transform-origin: left top;
                &.yx{
                  color: #8E5800;
                  background: linear-gradient(180deg, #FAD961 0%, #F7B91C 100%);
                }
                &.xp{
                  color: #fff;
                  background: linear-gradient(180deg, #016DFE 0%, #3A87F1 100%);
                }
                &.th{
                  color: #fff;
                  background: linear-gradient(180deg, #B4EC51 0%, #429321 100%);
                }
              }
              span{
                vertical-align: middle;
                font-size: .346667rem;
                font-weight: 600;
                color: #000;
                line-height: .426667rem;
                height: .853333rem;
              }
            }
            .desc{
              padding-top: .133333rem ;
              min-height: 1.2rem;
              p{
                color: #FE9C1E;
                font-size: .266667rem;
                font-weight: 400;
                line-height: .533333rem;
              }
            }
            .price{
              height: .533333rem;
              line-height: .533333rem;
              display: flex;
              margin-top: .08rem;
              i{
                font-size: .32rem;
                font-weight: 600;
                color: #F93A52;
                transform: scale(0.8);
                transform-origin: left bottom;
              }
              b{
                font-size: .426667rem;
                font-weight: 600;
                color: #F93A52;
                margin-right: .106667rem;
              }
              em{
                font-size: .32rem;
                font-weight: 400;
                color: #F93A52;
                margin-right: .106667rem;
              }
              span{
                font-size: .293333rem;
                font-weight: 400;
                color: #888;
                margin-right:.106667rem;
                &.old{
                  text-decoration: line-through;
                }
              }
            }
          }
        }
      }
    }
  }
  &.fixed{
    padding-top: 2.666667rem;// 等于nav高度
    .sort-nav{
      position: fixed;
      left: 0;
      top: 0;
    }
  }
}
</style>