写在前面的话
上一篇 《简单分析: h5实现外卖APP的效果》 文章,将基本的原理其实都介绍完了,只是完整的外卖APP,应该还包括底部条。加上底部条的话,会和上篇文章略有不同,但原理一致
效果
加入底部条后的区别
- 最外层容器,由原来的一个变成了两个,底部的青色区域是第一个容器,青色区域之上的是第二个容器(该容器是滚动容器)。青色区域之上的容器的高度为:
100vh - 青色区域高度 - 侧边橙色区域的高度,不再是
100vh - 4rem, 而应该是100vh - 6rem, 多的2rem是底部青色区域的高度
代码
<!--
@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="scroll-dom-height 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>
<div class="h-8 bg-lime-100">
<!-- 这里可以加个fixed定位的容器,也可以不加,加了能一定程度的缓解滚动到底部之后,继续滚动的橡皮筋效果问题 -->
<div class="h-8 bg-lime-100 fixed left-0 right-0 bottom-0"></div>
</div>
</template>
<style lang="scss" scoped>
// 分类面板高度
.classify-panel-height {
/*
为什么是6rem: 是`headerNavDomRef 的 h8` + `splitDomRef 的 h8` + 底部青色区域高度
h8是什么? 是tailwindcss的一个高度值, 实际是2rem, 三个 2rem 就是 6rem
*/
height: calc(100vh - 6rem);
}
.scroll-dom-height {
// 滚动容器高度 = 视口高度 - 青色区域高度
height: calc(100vh - 2rem);
}
</style>