Files
rednote-tool-studio/vendor/minitool-zip-builder/references/css-compatibility.md
T
2026-09-17 21:54:33 +08:00

172 lines
9.1 KiB
Markdown
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.
# CSS 兼容性规范
> 小工具 CSS 的最低兼容基线是 **Android 8.1 出场 Chrome / WebView 61**。这不等于只能写旧 CSS:交付物采用“Chrome 61 可用的基线层 + 能力检测后的增强层”,在新内核上使用更合适的 CSS 能力。
## 1. 基线原则
- 基线层保证布局、文字、按钮和核心交互在 Chrome 61 可用;增强层可以使用新 CSS 改善布局、视觉或交互。
- 按功能点提供回退与增强,不维护两套完整页面或两份完整样式表,避免双份样式逐渐漂移。
- 根据特性选择检测方法:能用声明级回退的按层叠覆盖,能准确查询的使用 `@supports`,语法检测无法证明实际布局行为时使用 JS 做最小行为检测。
- 浏览器会静默丢弃无法解析的选择器、声明或整条 at-rule。不能像 JS 一样靠异常发现 CSS 不兼容,必须检查最终产物和实际布局。
- 兼容性以最终 zip 内的 CSS 为准。源码使用了构建工具,不代表产物已经兼容。
## 2. Chrome 61 不可作为唯一实现的能力
以下能力晚于 Chrome 61,不能作为唯一实现。先提供基线路径,再用适合该能力的检测方式启用增强。
| 能力 | 基线写法 / 降级方式 |
| --- | --- |
| Flexbox `gap` / `row-gap` / `column-gap` | 基线用子项单边 `margin`;增强用 JS 实际测量 Flex 布局后切换 class,不能只检测 `gap` 属性语法 |
| `aspect-ratio` | 固定媒体尺寸,或用百分比 `padding-top` 比例盒;内容绝对定位 |
| `min()` / `max()` / `clamp()` | 先写固定值、百分比或 `calc()`,再用媒体查询分档 |
| `inset`、`margin-inline`、`padding-block` 等逻辑属性 / 简写 | 使用 `top/right/bottom/left` 与 `margin-left/right`、`padding-top/bottom` 等物理属性 |
| `overflow: clip` | 使用 `overflow: hidden` |
| `:focus-visible` | 先提供 `:focus` 样式;增强规则不能让键盘焦点在基线端消失 |
| `:has()` | 由 JS 在父元素上切换状态 class |
| Container Queries(`@container`) | 使用 viewport 媒体查询,或由 JS 按容器尺寸切换 class |
| Subgrid | 使用普通 Grid、Flex 或显式轨道尺寸 |
| CSS Nesting、Cascade Layers(`@layer`)、`@property` | 已有构建链能可靠展开时才在源码使用;最终产物不得保留为核心规则 |
| `dvh` / `svh` / `lvh` | 先用 `%` / `100vh`;受软键盘影响的全屏高度用 JS 维护 CSS 变量并保留静态兜底 |
| `color-mix()`、`oklab()`、`oklch()` 等现代颜色 | 先写 `#hex`、`rgb()`、`rgba()` 或 `hsl()` 颜色 |
| `backdrop-filter` | 先给不依赖模糊的实色 / 半透明背景;模糊仅作增强,并同时考虑 `-webkit-backdrop-filter` |
| `text-wrap: balance` 等现代排版属性 | 保留普通换行;不要让其决定关键区域高度 |
## 3. 布局与前缀
Chrome 61 可使用 Flexbox、基础 Grid、媒体查询、CSS Variables、`calc()`、transform、transition 和 animation。仍须处理以下跨端差异:
- Flex 子项内有长文本、图片或滚动区域时,按方向显式设置 `min-width: 0` 或 `min-height: 0`,避免内容撑破容器。
- Flex 间距先用子项 `margin` 形成基线;需要时通过行为检测启用 `gap` 并清除 margin。动态增删子项时确认首尾间距仍正确。
- Grid 只使用基础显式 / 隐式轨道;间距使用 Chrome 61 可解析的 `grid-gap`。不要使用 Subgrid、Masonry 或依赖新语法的自动布局。
- 需要隐藏文本时使用 `overflow: hidden; text-overflow: ellipsis; white-space: nowrap`。多行截断使用 `display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: <行数>; overflow: hidden`,并保证截断失效时页面仍可用。
- `user-select`、`appearance`、文字截断和毛玻璃等 WebKit 相关能力按需同时写 `-webkit-` 前缀与标准声明。不要机械给所有属性加前缀。
- 关键操作不能只在 `:hover` 出现;触摸端默认可见,并可用 `@media (hover: hover)` 增加鼠标悬停效果。
## 4. 基线层与增强层
### 声明回退和 `@supports`
先写 Chrome 61 可用的完整基线,再覆盖增强:
```css
.panel {
background: rgba(255, 255, 255, 0.96);
}
@supports ((-webkit-backdrop-filter: blur(12px)) or (backdrop-filter: blur(12px))) {
.panel {
background: rgba(255, 255, 255, 0.72);
-webkit-backdrop-filter: blur(12px);
backdrop-filter: blur(12px);
}
}
```
同一属性使用“旧值在前、新值在后”的回退顺序:
```css
.page {
min-height: 100vh;
min-height: var(--app-height, 100vh);
}
```
安全区须先保留普通值,再使用容器注入变量与 `env()`:
```css
.bottom-bar {
padding-bottom: 0;
padding-bottom: var(--safe-area-inset-bottom, env(safe-area-inset-bottom, 0px));
}
```
`@supports` 适合检测 `aspect-ratio`、`backdrop-filter`、动态视口单位等能由单个声明准确表达的能力。需要由 JS 切换 class 时,可以使用 `CSS.supports()` 检测同类声明。任何检测路径都必须保留基础样式。
### Flex gap 必须检测布局行为
`CSS.supports('gap', '1px')` 和 `@supports (gap: 1px)` 只能证明浏览器认识该属性和值,不能证明 `gap` 在 Flexbox 中生效;支持 Grid gap、但不支持 Flex gap 的内核也可能通过语法检测。需要启用 Flex gap 时,实际创建一次 Flex 容器并测量:
```js
function supportsFlexGap() {
var flex = document.createElement('div');
flex.style.position = 'absolute';
flex.style.visibility = 'hidden';
flex.style.display = 'flex';
flex.style.flexDirection = 'column';
flex.style.rowGap = '1px';
flex.appendChild(document.createElement('div'));
flex.appendChild(document.createElement('div'));
document.body.appendChild(flex);
var supported = flex.scrollHeight === 1;
flex.parentNode.removeChild(flex);
return supported;
}
if (supportsFlexGap()) {
document.documentElement.classList.add('supports-flex-gap');
}
```
对应 CSS 只维护一套组件规则,其中 margin 是基线,gap 是增强:
```css
.actions {
display: flex;
}
.actions > * + * {
margin-left: 12px;
}
.supports-flex-gap .actions {
column-gap: 12px;
}
.supports-flex-gap .actions > * + * {
margin-left: 0;
}
```
行为检测放在包内经典脚本中,并在 `document.body` 存在后、核心页面渲染前执行。检测一次即可,不要在 resize 或渲染循环中反复测量。
不要用 UA、Android 版本或机型字符串决定样式能力。能力检测决定是否启用增强,Chrome 61 基线层始终保留。
## 5. 有构建链与无构建链
### 直接交付静态文件
没有现成构建链时,不为 CSS 兼容性临时引入 PostCSS、Autoprefixer 或新的 npm 依赖;直接按 Chrome 61 基线编写。
### 项目已有构建链
将目标浏览器至少设置为:
```text
Chrome >= 61
ios_saf >= 18.4
```
- 可使用已有的 Autoprefixer 补必要前缀;若源码使用 nesting 等新语法,须确认已有插件会把它转换成普通 CSS。
- Autoprefixer 只补前缀,不会把 Flex `gap`、`aspect-ratio`、`:has()`、Container Queries 或动态视口单位自动改写成等价的旧布局。
- 压缩器也须使用相同浏览器目标,避免把兼容写法重新合并成 Chrome 61 无法解析的现代语法。
- 交付前检查构建后的 CSS;zip 中只保留最终静态产物,不带 source map 和构建配置。
## 6. 视口、安全区与实测
- 页面宽度使用 `%`、Flex 或基础 Grid,不写死 `375px` 等单一机型宽度。
- `100vh` 在移动端地址栏、容器高度变化和软键盘出现时可能不等于可视高度。关键全屏区域优先使用父容器百分比;必须跟随可视高度时,由 JS 监听尺寸变化并维护 `--app-height`,同时保留 `100vh` 回退。
- 安全区规则须配合 `viewport-fit=cover`,具体组合见 [cross-platform-h5.md](./cross-platform-h5.md)。
- 现代桌面浏览器或 PC 模拟器通过不等于 Chrome 61 通过。能够运行旧内核时,至少检查首屏、滚动区、弹层、表单、横竖屏 / 尺寸变化和核心交互;无法运行时在交付说明中标记“Chrome 61 CSS 兼容性未实测”。
## 7. 交付检查
- [ ] Chrome 61 基线层能够独立完成核心布局和交互,新内核通过能力检测启用增强层
- [ ] 未维护两套完整 CSS;只为实际使用的现代能力提供局部回退与增强
- [ ] Flex gap 使用布局行为检测,或仅使用 margin 基线;未把 `@supports (gap: ...)` / `CSS.supports('gap', ...)` 当作 Flex gap 检测
- [ ] `aspect-ratio`、`clamp()`、逻辑属性、`:has()`、Container Queries、Subgrid、CSS Nesting、动态视口单位等实际使用项均有合适的回退与检测 / 构建转换
- [ ] Flex 子项已检查溢出与滚动,必要处有 `min-width: 0` / `min-height: 0`
- [ ] 关键操作在触摸端可见,不依赖 `:hover`;焦点样式不只依赖 `:focus-visible`
- [ ] 安全区、全屏高度、软键盘、长文本和多行截断均有基线回退
- [ ] 使用构建链时已检查最终 CSS,而不是只检查源码或 Autoprefixer 是否运行
- [ ] 未进行 Chrome 61 / Android 8.1 实际验证时明确标记“CSS 兼容性未实测”