前端吸顶效果、视差滚动效果

221 阅读1分钟

一、基本效果

动图.gif

基本实现

1. 原生js实现
点击查看详细内容
    <style>
      * {
        margin: 0;
        padding: 0;
      }
      body {
        height: 2000px;
      }
      .header {
        height: 100px;
        background-color: red;
      }
      .nav {
        line-height: 50px;
        background-color: greenyellow;
        text-align: center;
      }
      .nav.fixed {
        position: fixed;
        top: 0;
        width: 100%;
      }
      .container {
        height: 500px;
        background-color: yellow;
      }
      .container.marginTop {
        margin-top: 50px;
      }
    </style>
  </head>
  <body>
    <div class="header"></div>
    <div class="nav">我是导航栏</div>
    <div class="container">
      <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
        <li>5</li>
      </ul>
    </div>
    <script>
      const headerHeight = document.querySelector('.header').offsetHeight // header的高度
      const nav = document.querySelector('.nav')
      const container = document.querySelector('.container')
      window.addEventListener('scroll', () => {
        const scrollTop =
          document.documentElement.scrollTop ||
          window.pageYOffset ||
          document.body.scrollTop // 向上卷曲出去的距离
        // 当滚动条向上卷曲出去的距离大于等于header的高度时,给nav添加固定定位,并且给container添加
        if (scrollTop >= headerHeight) {
          nav.classList.add('fixed')
          container.classList.add('marginTop')
        } else {
          nav.classList.remove('fixed')
          container.classList.remove('marginTop')
        }
      })
    </script>
  </body>
2.vue中实现
点击查看详细内容
<template>
  <div class="ceiling">
    <header></header>
    <div class="container">
      <div class="nav">导航</div>
      <ul>
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
        <li>6</li>
      </ul>
    </div>
  </div>
</template>
<script>
export default {
  mounted() {
    this.ceiling()
  },
  methods: {
    ceiling() {
      const headerHeight = document.querySelector('header').offsetHeight + 20 // 20是上下边距加起来20px
      const nav = document.querySelector('.nav')
      const container = document.querySelector('.container')
      window.addEventListener('scroll', () => {
        const scrollTop =
          document.documentElement.scrollTop ||
          window.pageYOffset ||
          document.body.scrollTop
        console.log(scrollTop, headerHeight)
        if (scrollTop >= headerHeight) {
          nav.classList.add('fixed')
          container.classList.add('marginTop')
        } else {
          nav.classList.remove('fixed')
          container.classList.remove('marginTop')
        }
      })
    }
  }
}
</script>
<style lang="less" scoped>
.ceiling {
  height: 1000px;
  padding: 10px;
  header {
    height: 100px;
    background-color: red;
  }
  .container {
    margin-top: 10px;
    .nav {
      line-height: 40px;
      text-align: center;
      background-color: yellowgreen;
    }
    .nav.fixed {
      position: fixed;
      top: 0;
      width: calc(100% - 20px);
    }
    > ul {
      background-color: yellow;
      height: 500px;
    }
  }
  .container.marginTop {
    margin-top: 50px; // nav高度40 + 原有的margin-top: 10px
  }
}
</style>

二、将card容器顶到视口外

1、效果

0018f7759a6f4b74b7ebaa7db4bced44_tplv-k3u1fbpfcp-zoom-in-crop-mark_3024_0_0_0.webp

2、vue实现

点击查看详细内容
<template>
  <div class="ceiling" ref="ceilingRef">
    <header></header>
    <div class="container">
      <div class="card"></div>
      <div class="content" ref="contentRef">
        <div class="tabs"></div>
        <ul class="list">
          <li v-for="item in 40" :key="item">第{{item}}行</li>
        </ul>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      documentHeight: document.documentElement.offsetHeight, // 文档高度 667
      cardHeight: 0,
      residueHeight: 0, // 除container容器外剩余的高度:header+card+30
    }
  },
  mounted() {
    this.setHeight()
    this.ceiling()
  },
  methods: {
    ceiling() {
      const { documentHeight, cardHeight, residueHeight } = this
      window.addEventListener('scroll', () => {
        const scrollTop =
          document.documentElement.scrollTop ||
          window.pageYOffset ||
          document.body.scrollTop // 向上卷曲出去的距离
        const contentHeight = documentHeight + scrollTop - residueHeight
        this.$refs.contentRef.style.height = contentHeight + 'px'
        console.log(scrollTop, contentHeight, residueHeight)
        const listDom = document.querySelector('.list')
        listDom.style.overflow = scrollTop === cardHeight ? 'auto' : ''
      })
    },
    // 设置ceiling容器和content容器的高度
    setHeight() {
      const { documentHeight } = this
      const headerHeight = document.querySelector('header').offsetHeight // header容器的高度
      const card = document.querySelector('.card')
      const cardHeight =
        document.querySelector('.card').offsetHeight +
        +getComputedStyle(card).marginBottom.slice(0, -2) // card容器的高度 + card容器的下边距
      this.$refs.ceilingRef.style.height = documentHeight + cardHeight + 'px'
      this.$refs.contentRef.style.height = documentHeight - 180 + 'px'
      this.cardHeight = cardHeight
      this.residueHeight = headerHeight + cardHeight + 20
    }
  }
}
</script>
<style lang="less" scoped>
.ceiling {
  box-sizing: border-box;
  *,
  *:before,
  *:after {
    box-sizing: inherit;
  }
  header {
    height: 50px;
    background-color: rgba(255, 0, 0, 0.1);
    position: fixed;
    width: 100%;
  }
  .container {
    padding: 60px 10px 10px;
    display: flex;
    flex-direction: column;
    .card {
      height: 100px;
      background-color: rgba(255, 0, 0, 0.2);
      border-radius: 10px;
      margin-bottom: 10px;
    }
    .content {
      background-color: rgba(255, 0, 0, 0.3);
      border-radius: 10px;
      display: flex;
      flex-direction: column;
      .tabs {
        height: 50px;
        background-color: rgba(0, 255, 0, 0.4);
        border-radius: 10px 10px 0 0;
      }
      .list {
        flex: 1;
        overflow: hidden;
        background-color: rgba(0, 255, 0, 0.8);
        border-radius: 0 0 10px 10px;
      }
    }
  }
}
</style>

三、匹克官网底部视差滚动效果

1、匹克官网:www.peaksport.com/cn

2、效果

cdb76ecba70a4611911981b7fc25cfac_tplv-k3u1fbpfcp-zoom-in-crop-mark_3024_0_0_0.webp

3、原生js实现

点击查看详细内容
    <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
    <style>
      * {
        margin: 0;
        padding: 0;
      }
      .peak {
        position: relative;
      }
      .center {
        height: 800px;
        background-color: rgba(255, 0, 0, 0.1);
      }
      .foot-top {
        height: 222px;
        background: url('https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/229ab4d6f4324f5887ed0e64dad4a62b~tplv-k3u1fbpfcp-zoom-in-crop-mark:3024:0:0:0.awebp?')
            no-repeat center,
          #333;
        margin-bottom: 300px;
      }
      .foot-bottom {
        height: 300px;
        background-color: rgba(0, 255, 0, 0.4);
        position: absolute;
        bottom: 0;
        width: 100%;
      }
    </style>
  </head>
  <body>
    <div class="peak">
      <div class="center"></div>
      <div class="foot-top"></div>
      <div class="foot-bottom"></div>
    </div>
    <script>
      // jq的写法
      // const peakHeight = document.querySelector('.peak').offsetHeight // 固定高
      // const windowHeight = $(this).height() // 固定高
      // $(window).scroll(function () {
      //   const scrollTop = $(this).scrollTop() // 向上卷曲出去的距离,根据滚动条实时获取
      //   const h = peakHeight - windowHeight - scrollTop
      //   console.log(peakHeight, scrollTop, windowHeight, h)
      //   $('.foot-bottom').css({ position: h <= 0 ? 'fixed' : '' })
      // })

      // js的写法
      const peakHeight = document.querySelector('.peak').offsetHeight // peak容器的高度:800+222=1022,但此时html的高度不能设置死,因为footer-top的marginBottom为300,此时html的高度会被撑到1322
      const windowHeight = window.innerHeight // 可见区域高度 937 固定的
      const footBottom = document.querySelector('.foot-bottom')
      window.addEventListener('scroll', () => {
        const scrollTop =
          document.documentElement.scrollTop ||
          window.pageYOffset ||
          document.body.scrollTop // 向上卷曲出去的距离,根据滚动条实时获取
        const h = peakHeight - windowHeight - scrollTop
        footBottom.style.position = h <= 0 ? 'fixed' : ''
      })
    </script>
  </body>

4、vue实现

点击查看详细内容
<template>
  <div class="peak">
    <div class="center"></div>
    <div class="foot-top"></div>
    <div class="foot-bottom"></div>
  </div>
</template>
<script>
export default {
  mounted() {
    this.roll()
  },
  methods: {
    roll() {
      const peak = document.querySelector('.peak')
      const peakHeight = document.querySelector('.peak').offsetHeight // peak容器的高度,1322
      const windowHeight = window.innerHeight // 可见区域高度 667 固定的
      const footBottom = document.querySelector('.foot-bottom')
      window.addEventListener('scroll', () => {
        const scrollTop =
          document.documentElement.scrollTop ||
          window.pageYOffset ||
          document.body.scrollTop // 向上卷曲出去的距离,根据滚动条实时获取
        const h = peakHeight - windowHeight - scrollTop
        if (h <= 300) {
          footBottom.style.position = 'fixed'
          peak.style.paddingBottom = 300 + 'px'
        } else {
          footBottom.style.position = ''
          peak.style.paddingBottom = 0
        }
        console.log(
          peakHeight,
          windowHeight,
          scrollTop,
          h,
          peak.style.paddingBottom
        )
      })
    }
  }
}
</script>
<style lang="less" scoped>
* {
  margin: 0;
  padding: 0;
}
.peak {
  position: relative;
  padding-bottom: 300px; // 预留300pxfooter-bottom。外部容器,比如App组件设置了height: 100vh;不可以用原生js那样的margin-bottom,而应该设置peak容器的padding-bottom,用来存放footer-bottom容器
  .center {
    height: 800px;
    background-color: rgba(255, 0, 0, 0.1);
  }
  .foot-top {
    height: 222px;
    background: url('https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/229ab4d6f4324f5887ed0e64dad4a62b~tplv-k3u1fbpfcp-zoom-in-crop-mark:3024:0:0:0.awebp?')
        no-repeat center,
      #333;
  }
  .foot-bottom {
    width: 100%;
    height: 300px;
    background-color: rgba(0, 255, 0, 0.4);
    position: absolute;
    bottom: 0;
  }
}
</style>

四、美团外卖视差滚动效果

1、美团效果:

动图.gif

2、vue实现

超链接:vue基于vant封装上拉加载/下拉刷新组件ListScroller

点击查看详细内容
<template>
  <div class="meituan">
    <ListScroller ref="listScrollerRef" @on-pulldown-loading="refrash" @on-pullup-loading="loadMore">
      <header>
        <h4 class="address">凑凑火锅·茶憩(杭州金地广场店)</h4>
        <div class="input">
          <div class="search">搜索</div>
        </div>
      </header>
      <div class="classify"></div>
      <nav>导航</nav>
      <ul class="list" ref="listRef" v-if="list.length > 0">
        <li v-for="(item,index) of list" :key="index">
          <p>{{item.projectName}}</p>
          <p>{{item.addTime}}</p>
        </li>
      </ul>
      <div v-else class="empty">暂无数据~</div>
    </ListScroller>
  </div>
</template>
<script>
import ListScroller from '@/components/public/ListScroller'
import { listApi } from '@/api/request'
export default {
  data() {
    return {
      queryParams: { page: 1, pageSize: 10 },
      totalRecords: '',
      list: []
    }
  },
  created() {
    this.getList()
  },

  mounted() {
    this.ceiling()
  },
  methods: {
    ceiling() {
      const listScroller = this.$refs.listScrollerRef.$el
      const vanList = document.querySelector('.van-list')
      const address = document.querySelector('.address')
      const nav = document.querySelector('nav')
      const classify = document.querySelector('.classify')
      listScroller.addEventListener('scroll', () => {
        console.log(listScroller.scrollTop)
        if (listScroller.scrollTop >= classify.offsetHeight) {
          nav.classList.add('fixed')
          classify.style.marginBottom = 50 + 'px'
        } else {
          nav.classList.remove('fixed')
          classify.style.marginBottom = 0
        }
        // 当滚动条>=5时,address的高度为0,vanList的paddingTop为50;反之回到初始值
        if (listScroller.scrollTop >= 5) {
          address.style.height = 0
          vanList.style.paddingTop = 50 + 'px'
        } else {
          address.style.height = 30 + 'px'
          vanList.style.paddingTop = 80 + 'px'
        }
      })
    },
    async getList() {
      const { queryParams } = this
      const params = { ...queryParams, flowTrackType: 2 }
      const { success, data, totalRecords } = await listApi(params)
      if (success) {
        this.totalRecords = totalRecords
        if (queryParams.page === 1) {
          this.list = data
        } else {
          this.list.push(...data)
        }
      }
    },
    // 刷新
    async refrash(cb) {
      this.queryParams.page = 1
      await this.getList()
      cb && cb()
    },
    // 加载更多
    async loadMore(cb) {
      const { list, totalRecords } = this
      if (totalRecords > list.length) {
        this.queryParams.page++
        await this.getList()
        cb && cb()
      } else {
        cb && cb()
      }
    }
  },
  components: { ListScroller }
}
</script>
<style lang="less" scoped>
@yellow: #ffcc33; // 美团黄
/deep/ .list-scroller .van-list {
  height: 100vh;
  padding-top: 80px;
  transition: padding-top 0.3s;
  > header {
    position: fixed;
    top: 0;
    width: 100%;
    background-color: @yellow;
    padding: 0 10px;
    > h4.address {
      height: 30px;
      line-height: 30px;
      font-size: 14px;
      transition: height 0.3s;
      overflow: hidden;
    }
    .input {
      margin: 8px 0;
      border: 1px solid @yellow;
      background-color: #fff;
      height: 34px;
      border-radius: 17px;
      display: flex;
      align-items: center;
      justify-content: flex-end;
      .search {
        width: 50px;
        line-height: 28px;
        background-color: @yellow;
        border-radius: 14px;
        text-align: center;
        margin-right: 2px;
      }
    }
  }
  .classify {
    height: 100px;
    background-color: rgba(0, 0, 255, 0.1);
  }
  > nav {
    line-height: 50px;
    background-color: rgba(0, 255, 0, 0.6);
    text-align: center;
    &.fixed {
      position: fixed;
      top: 50px;
      width: 100%;
      z-index: 10;
    }
  }
  ul.list {
    padding: 10px;
    background-color: #eee;
    > li {
      height: 80px;
      background-color: #fff;
      margin-top: 10px;
      border-radius: 10px;
      padding: 10px;
      &:first-of-type {
        margin-top: 0;
      }
      > p {
        line-height: 20px;
      }
    }
  }
  .empty {
    padding: 100px;
    text-align: center;
  }
}
</style>

3、vue实现效果

动图.gif