简单分析: h5实现某外卖APP的效果(2)

110 阅读2分钟

写在前面的话

上一篇 《简单分析: h5实现外卖APP的效果》 文章,将基本的原理其实都介绍完了,只是完整的外卖APP,应该还包括底部条。加上底部条的话,会和上篇文章略有不同,但原理一致

效果

外卖APP商家店铺界面效果.gif

加入底部条后的区别

  • 最外层容器,由原来的一个变成了两个,底部的青色区域是第一个容器,青色区域之上的是第二个容器(该容器是滚动容器)。青色区域之上的容器的高度为: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>