一、基本效果
基本实现
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、效果
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、效果
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; // 预留300px放footer-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、美团效果:
2、vue实现
点击查看详细内容
<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实现效果