Files
MoviePilot-Frontend/src/composables/useAvailableHeight.ts

88 lines
3.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { computed, ref, watch, onMounted, onUnmounted, nextTick } from 'vue'
import { usePWA } from '@/composables/usePWA'
/**
* 计算页面内容的可用高度,自动适配 iOS 安全区域和底部 Dock 栏。
*
* 通过 DOM 测量获取布局的实际 padding含 safe-area-inset-top/bottom
* 以及 Footer Dock 的实际高度,确保在任何设备上都不会被 Dock 遮挡。
*
* 计算公式: viewport - layoutPaddingTop - layoutPaddingBottom - footerDock - componentOffset
*
* @param componentOffset - 组件内部额外占用的空间(工具栏、分页栏等,默认 64
* @param minHeight - 最小高度(默认 300
*/
export function useAvailableHeight(
componentOffset: number = 64,
minHeight: number = 300,
) {
const { appMode } = usePWA()
// 响应式测量值
const viewportHeight = ref(window.innerHeight || document.documentElement.clientHeight)
const layoutPaddingTop = ref(72)
const layoutPaddingBottom = ref(24)
const footerDockMeasuredHeight = ref(0)
function updateMeasurements() {
viewportHeight.value = window.innerHeight || document.documentElement.clientHeight
// 测量 .layout-page-content 的实际 padding含 env(safe-area-inset-top) 等)
const layoutEl = document.querySelector('.layout-page-content') as HTMLElement | null
if (layoutEl) {
const style = getComputedStyle(layoutEl)
layoutPaddingTop.value = parseFloat(style.paddingTop) || 72
layoutPaddingBottom.value = parseFloat(style.paddingBottom) || 24
}
// 直接查询 Footer Dock DOM无论 appMode 状态
// Dock 通过 Teleport 挂载到 body存在即测量不存在即为 0
const footerEl = document.querySelector('.footer-nav-container') as HTMLElement | null
footerDockMeasuredHeight.value = footerEl ? footerEl.offsetHeight : 0
}
// appMode 异步变化时PWA 检测完成、屏幕尺寸变化等Dock 会出现/消失
// 需要等 DOM 更新后重新测量
watch(appMode, () => {
nextTick(updateMeasurements)
})
onMounted(() => {
nextTick(updateMeasurements)
window.addEventListener('resize', updateMeasurements)
if (window.visualViewport) {
window.visualViewport.addEventListener('resize', updateMeasurements)
}
})
onUnmounted(() => {
window.removeEventListener('resize', updateMeasurements)
if (window.visualViewport) {
window.visualViewport.removeEventListener('resize', updateMeasurements)
}
})
const availableHeight = computed(() => {
const vh = viewportHeight.value
// 布局顶部 padding含 safe-area-inset-top + navbar 高度)
const topPadding = layoutPaddingTop.value
// 布局底部 padding
const bottomPadding = layoutPaddingBottom.value
// 底部 Dock 栏遮挡高度(通过 DOM 测量,含 safe-area-inset-bottom
const footerDockHeight = footerDockMeasuredHeight.value
const available = vh - topPadding - bottomPadding - footerDockHeight - componentOffset
return Math.max(available, minHeight)
})
return {
availableHeight,
viewportHeight,
}
}