Files
Friendship-and-chat-uniapp/src/sheep/platform/h5-shell.js
T
2026-07-15 20:20:05 +08:00

175 lines
5.5 KiB
JavaScript

const H5_STATUS_BAR_QUERY_KEYS = ['statusBarHeight', 'safeTop', 'safeAreaTop', 'appStatusBarHeight'];
const H5_MIN_STATUS_BAR_HEIGHT = 22;
const H5_MAX_STATUS_BAR_HEIGHT = 200;
let currentStatusBarHeight = H5_MIN_STATUS_BAR_HEIGHT;
let statusBarStyleObserver = null;
// 将 URL 参数、宿主全局变量和 postMessage 消息中的高度统一转换为数字。
function normalizeStatusBarHeight(value) {
if (value === undefined || value === null || value === '') {
return null;
}
const normalizedValue = typeof value === 'string' ? value.replace(/px$/i, '').trim() : value;
const height = Number.parseFloat(normalizedValue);
if (!Number.isFinite(height)) {
return null;
}
return Math.max(0, Math.min(height, H5_MAX_STATUS_BAR_HEIGHT));
}
// 宿主未提供高度或提供的高度过小时,统一使用 H5 最小状态栏高度。
function resolveStatusBarHeight(value) {
const height = normalizeStatusBarHeight(value);
return height === null ? H5_MIN_STATUS_BAR_HEIGHT : Math.max(height, H5_MIN_STATUS_BAR_HEIGHT);
}
function parseMessageData(data) {
if (!data) {
return null;
}
if (typeof data === 'string') {
try {
return JSON.parse(data);
} catch (error) {
return null;
}
}
return data;
}
// URL 参数优先级最高,同时兼容普通查询参数和 hash 查询参数。
function readStatusBarHeightFromQuery() {
const searchParamsList = [];
searchParamsList.push(new URLSearchParams(window.location.search));
const hashQuery = window.location.hash.includes('?') ? window.location.hash.split('?')[1] : '';
if (hashQuery) {
searchParamsList.push(new URLSearchParams(hashQuery));
}
for (const params of searchParamsList) {
for (const key of H5_STATUS_BAR_QUERY_KEYS) {
const height = normalizeStatusBarHeight(params.get(key));
if (height !== null) {
return height;
}
}
}
return null;
}
// 宿主可以在 H5 应用初始化前将状态栏高度注入 window。
function readStatusBarHeightFromWindow() {
const candidates = [
window.__APP_STATUS_BAR_HEIGHT__,
window.__UNI_STATUS_BAR_HEIGHT__,
window.__APP_SHELL_CONFIG__?.statusBarHeight,
window.__UNI_WEBVIEW_CONFIG__?.statusBarHeight,
];
for (const candidate of candidates) {
const height = normalizeStatusBarHeight(candidate);
if (height !== null) {
return height;
}
}
return null;
}
// 读取页面当前实际使用的 --status-bar-height。
export function getH5StatusBarHeight() {
if (typeof window === 'undefined' || typeof document === 'undefined') {
return 0;
}
const statusBarHeight = window
.getComputedStyle(document.documentElement)
.getPropertyValue('--status-bar-height')
.replace(/px$/i, '')
.trim();
return resolveStatusBarHeight(statusBarHeight);
}
// 依次从 URL、宿主全局变量中获取初始高度,均未提供时使用默认最小高度。
export function getH5HostStatusBarHeight() {
if (typeof window === 'undefined') {
return 0;
}
return resolveStatusBarHeight(readStatusBarHeightFromQuery() ?? readStatusBarHeightFromWindow());
}
// 只在 html 上设置一个状态栏变量,body 和其他子元素会自动继承。
export function applyH5HostStatusBarHeight(height) {
if (typeof window === 'undefined' || typeof document === 'undefined') {
return 0;
}
const statusBarHeight = resolveStatusBarHeight(height);
currentStatusBarHeight = statusBarHeight;
document.documentElement.style.setProperty('--status-bar-height', `${statusBarHeight}px`, 'important');
// 通知使用数值高度参与 JavaScript 布局计算的页面重新计算。
window.dispatchEvent(
new CustomEvent('app-shell-status-bar-change', {
detail: { height: statusBarHeight },
}),
);
return statusBarHeight;
}
// uni-app 重写 html 样式后,如果状态栏高度小于 22px,则立即恢复当前有效高度。
function observeStatusBarHeight() {
if (statusBarStyleObserver || typeof MutationObserver === 'undefined') {
return;
}
statusBarStyleObserver = new MutationObserver(() => {
const statusBarHeight = normalizeStatusBarHeight(
document.documentElement.style.getPropertyValue('--status-bar-height'),
);
if (statusBarHeight === null || statusBarHeight < H5_MIN_STATUS_BAR_HEIGHT) {
applyH5HostStatusBarHeight(currentStatusBarHeight);
}
});
statusBarStyleObserver.observe(document.documentElement, {
attributes: true,
attributeFilter: ['style'],
});
}
// 解析宿主运行期间通过 postMessage 发送的状态栏高度。
function resolveMessageStatusBarHeight(message) {
const data = parseMessageData(message?.data);
if (!data) {
return null;
}
return normalizeStatusBarHeight(
data.statusBarHeight ??
data.safeTop ??
data.safeAreaTop ??
data.payload?.statusBarHeight ??
data.payload?.safeTop,
);
}
// 初始化状态栏高度,并支持宿主通过全局方法或 postMessage 动态更新。
export function initH5HostShell() {
if (typeof window === 'undefined') {
return 0;
}
const safeTop = applyH5HostStatusBarHeight(getH5HostStatusBarHeight());
if (window.__FRIENDSHIP_H5_SHELL_READY__) {
return safeTop;
}
window.__FRIENDSHIP_H5_SHELL_READY__ = true;
observeStatusBarHeight();
window.setAppShellStatusBarHeight = (nextHeight) => applyH5HostStatusBarHeight(nextHeight);
window.addEventListener('message', (message) => {
const nextHeight = resolveMessageStatusBarHeight(message);
if (nextHeight !== null) {
applyH5HostStatusBarHeight(nextHeight);
}
});
return safeTop;
}