PWA 应用在多颜色主题站点下,顶部状态栏颜色自定义初探
- Published On
- Updated On
背景
吾辈有需求:将自己的独立站点,可以在 移动端浏览器 Safari 菜单中配置添加到主屏幕后,能够以伪桌面 App 的效果打开运行。
其中,状态栏的颜色需要匹配当前的颜色主题,给用户/阅读者更好的 UI 体验。 另外,该需求个人一开始就明确需要支持,不显突兀的状态栏颜色能更好的带来页面沉浸感。
这就需要涉及 PWA(渐进式 Web 应用) 的概念和一些简要配置。
以下是一些当中经历/细节体察/或搜索成果的简要记录:
移动端浏览器 Safari 默认效果
当没有设置 PWA 配置相关时,在 Safari (目前自用可测试版本为 Safari v17,该版本对 PWA 各方面相关都已经有了很好的支持),将自己的站点在手机上以最原始的网页形式打开,可以观察到,状态栏的颜色很好的跟随页面主题颜色作了自适应。(此时个人代码中并不涉及 <meta name="theme-color" content="???" />
的显式设置)。
但是如果没有 theme-color
或者 apple-mobile-web-app-status-bar-style
的显式设置,则创立桌面 PWA 并打开,开发者会发现:状态栏的颜色不能再如同网页上的打开效果:自动更随页面主题颜色。(当然后续不排除得到官方更新的原生支持)
正题
服务于 Apple Web App 的配置
在头部标签 <head />
下,当没有 meta 标签关于 PWA 应用的显式声明时,PWA 桌面打开效果主要以 Web app manifests (文件名常见site.webmanifest
或其他同作用的配置文件如manifest.json
)为参考。该文件下有如下核心字段:
{
"name": "name",
"short_name": "name",
"description": "description",
"icons": [
{
"src": "icons/32x32.png",
"sizes": "32x32",
"type": "image/png"
},
...
],
"theme_color": "#364151",
"background_color": "#364151",
"display": "standalone"
}
其中 theme_color
影响到 WebApp 状态栏的颜色。
而此时如果将该字段删除后的 site.webmanifest 文件添加到项目中,则伪桌面应用打开时,不能保留上面提到的 状态栏的颜色很好的跟随网页主题颜色作了自适应这样的效果。所以需要保留该字段。
以上的对于网页单主题,或者无主题颜色的情况,如上的配置就能够很好的支持了。
meta 标签语义配置同 site.webmanifest 优先级
前一节特别提到:“在头部标签 <head />
下当没有 meta 标签关于 PWA 应用的显式声明时”,PWA 应用打开效果主要参考 Web app manifests 独立配置文件。
meta 标签语义配置优先级高过Web app manifests这样的独立配置文件,所以你可以如下的声明,服务于自己的 PWA 应用状态栏定义
<head>
<meta name="theme-color" content="#000000" />
</head>
当然,进一步的,如果站点本身有一套简单的明暗主题切换情况的话,如下声明即可很好的支持该需求
<head>
<meta
name="theme-color"
media="(prefers-color-scheme: dark)"
content="#000000"
/>
<meta
name="theme-color"
media="(prefers-color-scheme: light)"
content="#ffffff"
/>
</head>
! 但是额外需要注意的是,我们依然需要必要的Web app manifests这样的独立配置文件作为 PWA 应用主题色指定的兜底措施。原因显而易见,来自于版本支持问题:Safari 浏览器从最近的 v15 版本才开始支持该类的 meta 标签语义配置(兼容/支持表格可查)
服务于 PWA / Apple WebApp 的过时、不推荐配置。
本节可不作阅读,只是对检索结果的必要记录
在搜查对比 PWA 状态栏主题色配置方案中,一些过时的检索结果不可避免的出现在视线内。比如要对站点多主题颜色的情况作支持,部分检索结果给出这样的配置方案(年代久远,可以理解为是当时在不支持 meta#themeColor 配置下的无奈之举)
主要依赖 apple-mobile-web-app-status-bar-style
的 meta 语义配置标签。(官方配置文档可见)
那么如何支持多主题颜色的情况呢?如下配置:
<meta
name="apple-mobile-web-app-status-bar-style"
content="black-translucent"
/>
经本人测试,该配置有明显对 UI 减分的效果出现,black-translucent
该名称显然的,将状态栏设置为透字的透明效果,上机效果为:页面的文字会随滚动出现在状态栏底部,观感十分不好。
所以,如果你正打算配置字段如下:
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<!-- or -->
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<!-- or -->
<meta
name="apple-mobile-web-app-status-bar-style"
content="black-translucent"
/>
除去 content="default"
(该配置即保留 ios 默认的状态栏主题风格,并能够跟随系统的深色模式切换),应该必要了解, meta#themeColor 配置是对 meta#apple-mobile-web-app-status-bar-style 的完美替代。且也能服务于其他可能需部署平台(Android#Chrome)
多颜色主题风格站点下,初始 meta#themeColor 声明的局限性
如上推荐的一种初始的配置声明中
<head>
<meta
name="theme-color"
media="(prefers-color-scheme: dark)"
content="#000000"
/>
<meta
name="theme-color"
media="(prefers-color-scheme: light)"
content="#ffffff"
/>
</head>
也只能简单的支持明暗两套主题的切换情况。但如果是对于多颜色主题风格站点,可能就不是那么适合,或者说,简单两种状态栏的颜色配置不能满足吾辈对页面沉浸感方面的要求。
📌 meta#themeColor 动态赋初始值的实践
首先的,我们依然需要保留头部标签 <head />
下的 meta#themeColor 静态内容标签,便于之后的 DOM 查询操作:document.querySelector("meta[name='theme-color']")
。
<head>
<meta name="theme-color" content="cyan" />
</head>
如下代码以 React + TypeScript 为例,那么需要充分解决如下两点:
- 页面初始加载时,对 meta#themeColor 赋值,赋值匹配当前页面颜色主题关联的约定状态栏颜色值。
- 当用户切换主题时,需要额外的更新 meta#themeColor 赋值
先给出可以实现该期望效果但是并非最佳的实践方案:
(示例代码中一些类型或字段声明存在缺失,但不影响整体思路呈现,所以不作补充)
// import ...
export const themeCssValMap: Record<ThemeUniqKeyType, string> = {
default: "#333333",
dark: "hsl(231.43 14.29% 9.61%)",
soft: "hsl(36 18.52% 94.71%)",
loafer: "hsl(0 0% 100%)",
system: "#364151",
} as const;
export function useHdlMetaThemeOnce() {
const { theme: curTheme } = useTheme() as { theme: ThemeUniqKeyType };
React.useEffect(() => {
const metaT = document.querySelector("meta[name='theme-color']")!;
if (!metaT) return;
metaT.setAttribute("content", themeCssValMap[curTheme ?? "system"]);
}, [curTheme]);
}
❌ 上述实践不可忽视的两个弊端:
虽然上面的 React-Hooks 代码在必要处一次性调用,就能很好的同时处理上面提到的需充分解决的两点需求,但是有不可忽视的两个弊端:
-
meta#themeColor 初始赋值,人为可预见的,应该是和组件的生命周期、何时挂载到页面上无关的,而 React Hooks 的写法,则将对 meta#themeColor 赋初始值的行为,和该自定义 hook 调用处所在的函数组件作了强绑定,这样会带来的影响是:页面的状态栏的颜色更新总是有一定延迟,总能明显的观察到状态栏颜色有 head>meta#themeColor 中声明的
cyan
颜色显著的变化到约定的主题对应的状态栏颜色值。所以,我们需要将 meta#themeColor 初始赋值更提前/提到非组件的顶层上去执行! -
meta#themeColor 更新赋值,违背了 React 的最佳实践,即在考虑通过
React.useEffect
执行一些额外的副作用时,应该先考虑,这些副作用是否应该转而由事件回调函数(onClick/onSelect/onCheck)来执行,如此的实践,是减少React.useEffect
的不必要额外执行。显然的,我们对 meta#themeColor 更新赋值总是发生在用户手动切换主题时,我们可以明确的将该逻辑放置在事件响应回调中,而无需依赖 React.useEffect 对依赖项 theme 值的变更的响应执行。
✔ meta#themeColor 初始赋值的最终实践
这里也假定页面的主题名称有被缓存到 localStorage 中(这样也才能支持我们获取到当前主题名去适配状态栏颜色)
<body>
<script data-tid="theme-meta-hack-script">
(function () {
const themeCssValMap = {
// ...
};
const curTheme = localStorage.theme;
const $metaT = document.querySelector("meta[name='theme-color']");
if (!$metaT) return;
$metaT.setAttribute("content", themeCssValMap[curTheme ?? "system"]);
})();
</script>
</body>
“Hack Script” React Component
这里有必要额外补充,如果你的工程项目下,没有提供 Xxx.html 这样的入口级别文件,那么如上的代码插入可能不易实现。就比如吾辈当前站点使用的 NextJS 13 App Router 开发模式下,对开发者提供的可见入口文件为唯一确定的 /app/layout.tsx
下,但也可以通过如下手段在 DOM 树指定处插入上面的 <script />
标签,唯一带来的代价是可读性较差。
显然的,在组件 TSX/JSX 代码中,我们可以如下插入一段 script 标签:
function SomeHackScript() {
return (
<script
dangerouslySetInnerHTML={{
__html: `
(${function () {
/* ... code here */
}.toSring()})()
`,
}}
/>
);
}
✔ meta#themeColor 更新赋值的最终实践
假定原本已存在这样一个 React-Hook : const {theme, setTheme} = useTheme()
,
那么此刻在事件响应回调中我们调用 setTheme 时,需要额外的定位和操作 meta#themeColor 标签。吾辈最终将该操作和 setTheme 强耦合,导出一自定义 hook 如下:
以上。是对“PWA 应用在多颜色主题站点下,顶部状态栏颜色自定义初探”的吾辈简要分享。
更新说明了:
所以更正后:
// import ...
function useThemeEff2Meta() {
const { setTheme, ...rest } = useTheme();
- const metaTDomRef = React.useRef<HTMLMetaElement>(null);
- React.useEffect(() => {
- metaTDomRef.current = document.querySelector("meta[name='theme-color']")!;
- }, []);
const setThemeUponMeta = React.useCallback(
(t: ThemeUniqKeyType) => {
setTheme(t);
- if (!metaTDomRef.current) return;
+ const metaT = document.querySelector("meta[name='theme-color']")!;
+ if (!metaT) return;
/* 映射当前主题的颜色值 */
- metaTDomRef.current.setAttribute(
+ metaT.setAttribute(
"content",
themeCssValMap[t ?? "system"],
);
},
[setTheme],
);
return {
setThemeUponMeta,
// setTheme,
...rest,
};
}
PWA 应用在多颜色主题站点下,顶部状态栏颜色自定义初探
https://blog.ninoh.cc/blog/a5-meta-theme-color[Copy]转载或引用本文时请遵守“署名-非商业性使用-相同方式共享 4.0 国际”许可协议,注明出处、不得用于商业用途!分发衍生作品时必须采用相同的许可协议。