特点
- 仅最外部一个滚动条
- 随着向下滚动,个人感官是,顶部会逐渐减少,底部会逐渐增多,直到一个阈值时,菜单列表开始可以滚动
- 随着向下滚动,顶部导航会逐渐显示,并最终固定在顶部,中部的tab栏,会逐渐上升,直到顶部导航之下,也会固定在这个位置
- 如果是向上滚动,个人感官是,首先会让菜单列表滚动到最顶部,直至顶部再无菜单数据,外部才会开始向上滚动,直至滚动到最顶部,这期间tab栏会感觉,从顶部逐渐在下降,直至回归到他的初始位置,然后头部导航可能会逐渐隐藏,直至完全不可见
模拟的效果
逐步分析
主结构
主界面应该分成上中下4部分:最外层黑框是滚动容器,粉色代表顶部,绿色分割头部和主体,蓝色是主体,空白是为了方便区分,实际并不存在
主结构细化
- 最外层黑框是滚动容器,粉色代表顶部,绿色分割头部和主体,蓝色是主体,空白是为了方便区分,实际并不存在
- 顶部的黄色条是顶部导航条
- 底部的淡紫色部分是商品分类
- 底部的褐色部分是商品列表
- 黑色边框之外的部分,让滚动容器出现滚动条
粉色和黄色外加绿色区域高度的特殊性
- 黄色区域在粉色区域之内,绿色区域在粉色和蓝色区域之间,他们的位置和高度比较重要。因为黄色区域和绿色区域随着滚动条的滚动,最终的效果是漂浮在页面顶部。那么就需要知道滚动条滚动到什么位置的时候,需要将这两个区域设置为
fixed - 粉色加绿色区域的高度不会超过1屏的高度
蓝色区域高度特殊性
蓝色区域是没有高度的,他的高度应该是被内容撑出来的
褐色区域高度
褐色区域是没有固定高度的,会随着数据的增多而增加
淡紫色区域高度特殊性
淡紫色区域高度:100vh - 黄色区域高度 - 绿色区域高度,蓝色区域的初始高度也是被淡紫色区域撑出来的
重点:淡紫色区域fixed切换
当滚动的距离大于等于粉色区域高度 - 绿色区域高度,淡紫色区域设置为fixed,否则去掉fixed
黄色区域fixed切换
黄色区域默认就是fixed, 因为他会一直浮动在顶部,某些特殊情况是,可能他初始是完全透明的,但会随着滑动,逐渐显现出来。
绿色区域fixed切换
当滚动距离大于等于粉色区域高度 - 黄色区域高度时,绿色区域设置为fixed, 否则去掉fixed
代码
特别说明: class基本都使用的tailwindcss中的class,仅classify-panel-height为自定义
<!--
@author: pan
@createDate: 2022-12-06 16:10
-->
<script setup lang="ts">
import { ref } from 'vue'
const scrollDomRef = ref<HTMLElement>()
const splitDomRef = ref<HTMLElement>()
const headerNavDomRef = ref<HTMLElement>()
const headerPanelDomRef = ref<HTMLElement>()
const classifyPanelDomRef = ref<HTMLElement>()
const splitDomPositionClass = ref('')
const classifyPanelDomPositionClass = ref('')
function updateSplitDomPosition() {
const scrollDom = scrollDomRef.value
if (!scrollDom) return
const headerPanelDom = headerPanelDomRef.value
if (!headerPanelDom) return
const headerNavDom = headerNavDomRef.value
if (!headerNavDom) return
const splitDom = splitDomRef.value
if (!splitDom) return
const { offsetHeight: headerPanelDomHeight } = headerPanelDom
const { offsetHeight: headerNavDomHeight } = headerNavDom
const { scrollTop } = scrollDom
// 分割面板fixed定位scrollTop阈值 = 头部面板高度 - 头部导航高度
const fixedHeight = headerPanelDomHeight - headerNavDomHeight
if (scrollTop >= fixedHeight) {
// 超过阈值,则使用fixed定位
splitDomPositionClass.value = 'fixed top-8 left-0 right-0 h-8'
} else {
// 反之不使用特殊定位
splitDomPositionClass.value = ''
}
}
function updateClassifyPanelDomPosition() {
const scrollDom = scrollDomRef.value
if (!scrollDom) return
const headerPanelDom = headerPanelDomRef.value
if (!headerPanelDom) return
const splitDom = splitDomRef.value
if (!splitDom) return
const { offsetHeight: headerPanelDomHeight } = headerPanelDom
const { offsetHeight: splitDomHeight } = splitDom
const { scrollTop } = scrollDom
const fixedHeight = headerPanelDomHeight - splitDomHeight
if (scrollTop >= fixedHeight) {
// 超过阈值,则使用fixed定位
classifyPanelDomPositionClass.value = 'fixed top-16 left-0 w-14'
} else {
// 反之不使用特殊定位
classifyPanelDomPositionClass.value = ''
}
}
function onScroll() {
updateSplitDomPosition()
updateClassifyPanelDomPosition()
}
</script>
<template>
<!-- 最外层的滚动容器 -->
<div ref="scrollDomRef" class="h-full overflow-auto" @scroll="onScroll">
<!-- 头部面板包裹容器 -->
<div ref="headerPanelDomRef" class="bg-pink-100 h-32 relative">
<div ref="headerNavDomRef" class="h-8">
<!-- 外面这层div的作用是使界面高度不发生变化 -->
<div class="h-8 bg-blue-100 fixed top-0 left-0 right-0"></div>
</div>
</div>
<!-- 分割面板包裹容器 -->
<div class="h-8 bg-green-100">
<!-- 外面这层div的作用是当splitDomRef为fixed定位时,界面高度不发生变化 -->
<div
ref="splitDomRef"
class="bg-green-100 h-8"
:class="splitDomPositionClass"
></div>
</div>
<!-- 主体内容 -->
<main class="flex flex-row">
<!-- 侧边导航包裹面板 -->
<div class="bg-orange-100 w-14 flex-shrink-0">
<!-- 外面这层div的作用是当classifyPanelDomRef为fixed定位时,界面宽度不发生变化 -->
<div
ref="classifyPanelDomRef"
class="classify-panel-height bg-orange-100"
:class="classifyPanelDomPositionClass"
></div>
</div>
<!-- 右侧列表面本 -->
<div ref="mainListDomRef" class="flex-1 w-0">
<div v-for="i in 100" :key="i">第{{ i }}</div>
</div>
</main>
</div>
</template>
<style lang="scss" scoped>
// 分类面板高度
.classify-panel-height {
/*
为什么是4rem: 是`headerNavDomRef 的 h8` + `splitDomRef 的 h8`
h8是什么? 是tailwindcss的一个高度值, 实际是2rem, 两个 2rem 就是 4rem
*/
height: calc(100vh - 4rem);
}
</style>