大家好,我是Java1234_小锋老师,最近更新 《一天学会 CSS3 视频教程(无废话版) 》专辑,感谢大家支持。

本课程主要介绍和讲解 CSS3简介,CSS3 入门,CSS3 选择器与优先级,CSS3 单位、颜色、字体 与文本,CSS3 盒模型 与基础样式,CSS3 布局基础,CSS3 布局进阶,CSS3 视觉 增强,CSS3 变换与动画,CSS3 函数与变量,CSS3 响应式 与移动端适配,CSS3 工程化与最佳实践。
视频教程+课件+源码 打包下载 :
链接:https://pan.baidu.com/s/1o-zRfndo1HHrS_uFroOiCw?pwd=1234
提取码:1234
CSS3(在第二阶段目录里)
CSS3 工程化与最佳实践
CSS3 工程化的目标,是让样式在多人协作、长期迭代、多端适配中保持可维护、可复用、可扩展、高性能、可访问。
命名规范:BEM
BEM 即 Block__Element--Modifier,适合组件化开发。
Block:独立组件,如.cardElement:组件内部元素,如.card__titleModifier:状态或变体,如.card--featured、.card__title--large- 原则:低耦合,不依赖标签和层级,避免
.header .nav ul li a - 状态类可配合
is-、has-,如.is-active
css
.card {}
.card__title {}
.card__title--highlight {}
.card--disabled {}
优点是可读、可预测、易复用;缺点是类名可能较长,可结合工具类使用。
代码组织与模块化
常见目录结构:
text
styles/
base/ # reset、变量、字体、全局样式
layout/ # 布局:header、footer、grid
components/ # 组件:button、card、modal
pages/ # 页面级样式
themes/ # 主题变量
utils/ # 工具类、mixin、函数
vendors/ # 第三方样式
常用方法论:
- ITCSS:按层组织,从通用到具体
- 7-1 模式:7 个目录 + 1 个入口文件
- SMACSS:按基础、布局、模块、状态、主题分类
- Atomic CSS / Tailwind:原子类优先
- CSS Modules / styled-components:局部作用域,组件级样式
原则:单一职责、减少全局污染、避免深层嵌套,推荐嵌套不超过 3 层;可用 @layer 管理层叠优先级。
CSS 变量与主题切换
CSS 变量适合做设计令牌和主题系统。
css
:root {
--color-primary: #2563eb;
--color-bg: #ffffff;
--color-text: #111827;
}
[data-theme="dark"] {
--color-primary: #60a5fa;
--color-bg: #0f172a;
--color-text: #f8fafc;
}
body {
background: var(--color-bg);
color: var(--color-text);
}
切换方式:
js
document.documentElement.dataset.theme = "dark";
也可跟随系统:
css
@media (prefers-color-scheme: dark) {
:root {
--color-bg: #0f172a;
--color-text: #f8fafc;
}
}
建议区分基础变量和语义变量,例如 --blue-500 与 --color-primary。
reset.css 与 normalize.css
reset.css:清除浏览器默认样式,如margin、padding、列表样式,统一性强,但可能丢失语义默认表现。normalize.css:保留有用默认样式,修复跨浏览器差异,更温和。- 现代项目常用:
normalize.css+ 局部 reset,或使用现代 reset。
css
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
margin: 0;
font-family: system-ui, sans-serif;
line-height: 1.5;
}
img,
picture,
video {
max-width: 100%;
display: block;
}
不建议无脑同时全量引入 reset 和 normalize,按项目需要选择即可。
浏览器兼容与 Autoprefixer
使用 browserslist 定义目标浏览器,再通过 PostCSS + Autoprefixer 自动添加前缀。
.browserslistrc:
text
> 0.5%
last 2 versions
not dead
postcss.config.js:
js
module.exports = {
plugins: {
autoprefixer: {}
}
};
实践建议:
- 用
caniuse查询特性支持 - 用
@supports做渐进增强 - 不依赖已废弃特性
- 必要时提供回退样式
- 构建流程中自动处理前缀,而不是手写
渲染性能与优化
- 避免复杂选择器和过深嵌套,减少匹配成本
- 动画优先使用
transform、opacity,避免触发 layout - 减少重排重绘,避免频繁读取和修改布局属性
will-change谨慎使用,过多会消耗内存- 使用
contain: layout paint;限制渲染范围 - 使用
content-visibility: auto;优化长列表 - 关键 CSS 内联,非关键 CSS 延迟加载
- 字体使用
font-display: swap,减少 FOIT - 图片设置宽高,减少 CLS
- 控制 CSS 体积,拆分公共样式和页面样式
css
.animate {
transition: transform 0.2s ease, opacity 0.2s ease;
}
.card {
contain: layout paint;
}
可访问性与语义化
- 优先使用语义化 HTML,如
button、nav、main、label - 颜色对比满足 WCAG AA,正文通常至少
4.5:1 - 保留可见焦点,使用
:focus-visible - 尊重
prefers-reduced-motion - 表单必须有
label,错误提示不能只靠颜色 - 键盘可操作,避免直接
outline: none且无替代 - 屏幕阅读器专用类:
css
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
css
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
综合实例
<!DOCTYPE html>
<html lang="zh-CN" data-theme="light">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="color-scheme" content="light dark" />
<title>CSS3 工程化综合实例</title>
<!--
============================================================
生产环境目录结构(对应下方 <style> 中的分区注释)
============================================================
styles/
main.css # 入口,按顺序 @import / @use
base/_reset.css # 第 1 节
base/_tokens.css # 第 2 节
themes/_dark.css # 第 3 节
layout/_header.css # 第 4 节
layout/_footer.css
components/_button.css # 第 5 节
components/_card.css # 第 6 节
components/_field.css # 第 7 节
components/_task-list.css# 第 8 节
utils/_utilities.css # 第 9 节
utils/_a11y.css # 第 10 节
.browserslistrc
> 0.5%
last 2 versions
not dead
postcss.config.js
module.exports = {
plugins: {
'postcss-import': {},
autoprefixer: {},
'cssnano': {} // 生产环境压缩
}
}
—— 前缀(-webkit- 等)由 Autoprefixer 自动生成,源码里不手写。
============================================================
-->
<!-- 提前应用主题,避免首屏闪烁(FOUC) -->
<script>
(function () {
try {
var saved = localStorage.getItem('demo-theme');
var theme = saved || (window.matchMedia &&
window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
document.documentElement.setAttribute('data-theme', theme);
} catch (e) {
document.documentElement.setAttribute('data-theme', 'light');
}
})();
</script>
<style>
/* ==================================================================
1. base/_reset.css —— 现代 reset(normalize 的“温和”替代方案)
================================================================== */
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
-webkit-text-size-adjust: 100%; /* Autoprefixer 会保留该前缀 */
scroll-behavior: smooth;
}
body {
margin: 0;
font-family: system-ui, -apple-system, "Segoe UI", "PingFang SC",
"Hiragino Sans GB", "Microsoft YaHei", sans-serif;
line-height: 1.6;
background-color: var(--color-bg);
color: var(--color-text);
transition: background-color var(--transition-base),
color var(--transition-base);
}
h1, h2, h3, h4, p, ul, ol, figure {
margin: 0;
}
img, svg, video {
display: block;
max-width: 100%;
}
button, input, select, textarea {
font: inherit;
color: inherit;
}
/* ==================================================================
2. base/_tokens.css —— 设计令牌(Design Tokens)
组件只允许消费“语义令牌”,不直接写死十六进制色值
================================================================== */
:root {
color-scheme: light;
/* 2.1 基础令牌(原始色板,仅用于定义语义令牌) */
--blue-500: #2563eb;
--blue-600: #1d4ed8;
--blue-100: #dbeafe;
--slate-900: #0f172a;
--slate-500: #64748b;
--slate-200: #e2e8f0;
--slate-100: #f1f5f9;
--red-600: #dc2626;
--green-600: #16a34a;
/* 2.2 语义令牌 */
--color-bg: #f6f8fc;
--color-surface: #ffffff;
--color-header-bg: rgba(255, 255, 255, 0.85);
--color-border: var(--slate-200);
--color-text: var(--slate-900);
--color-text-muted: var(--slate-500);
--color-primary: var(--blue-500);
--color-primary-hover: var(--blue-600);
--color-on-primary: #ffffff;
--color-focus: var(--blue-600);
--color-danger: var(--red-600);
--color-success: var(--green-600);
--color-badge-bg: var(--blue-100);
--color-badge-text: var(--blue-600);
--color-code-bg: var(--slate-100);
/* 2.3 间距 / 圆角 / 字号 */
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-3: 0.75rem;
--space-4: 1rem;
--space-5: 1.5rem;
--space-6: 2rem;
--space-7: 3rem;
--radius-sm: 6px;
--radius-md: 10px;
--radius-lg: 16px;
--radius-full: 999px;
--font-sm: 0.875rem;
--font-lg: clamp(1.125rem, 1rem + 0.5vw, 1.375rem);
--font-2xl: clamp(1.6rem, 1.3rem + 1.4vw, 2.25rem);
/* 2.4 阴影与动效 */
--shadow-card: 0 1px 2px rgba(15, 23, 42, 0.06),
0 8px 24px rgba(15, 23, 42, 0.06);
--shadow-focus: 0 0 0 3px rgba(37, 99, 235, 0.35);
--transition-base: 160ms ease;
/* 2.5 布局 */
--container: 68rem;
--header-h: 4rem;
}
/* ==================================================================
3. themes/_dark.css —— 主题切换:只覆盖语义令牌,不动组件代码
================================================================== */
:root[data-theme="dark"] {
color-scheme: dark;
--color-bg: #0b1220;
--color-surface: #131c2e;
--color-header-bg: rgba(19, 28, 46, 0.85);
--color-border: #26334a;
--color-text: #e8eefc;
--color-text-muted: #9aa8c0;
--color-primary: #6ba1ff;
--color-primary-hover: #8ab6ff;
--color-on-primary: #08111f;
--color-focus: #8ab6ff;
--color-danger: #ff8b8b;
--color-success: #6ee7a8;
--color-badge-bg: rgba(107, 161, 255, 0.16);
--color-badge-text: #9cc2ff;
--color-code-bg: rgba(255, 255, 255, 0.08);
--shadow-card: 0 1px 2px rgba(0, 0, 0, 0.4),
0 8px 24px rgba(0, 0, 0, 0.35);
--shadow-focus: 0 0 0 3px rgba(107, 161, 255, 0.35);
}
/* ==================================================================
4. layout/_header.css + _footer.css
================================================================== */
.container {
width: 100%;
max-width: var(--container);
margin-inline: auto;
padding-inline: var(--space-4);
}
.site-header {
position: sticky;
top: 0;
z-index: 10;
background-color: var(--color-surface);
border-bottom: 1px solid var(--color-border);
transition: background-color var(--transition-base);
}
/* 渐进增强:支持 backdrop-filter 时才启用毛玻璃,否则回退为不透明背景 */
@supports ((backdrop-filter: blur(8px)) or (-webkit-backdrop-filter: blur(8px))) {
.site-header {
background-color: var(--color-header-bg);
-webkit-backdrop-filter: blur(8px);
backdrop-filter: blur(8px);
}
}
.site-header__inner {
display: flex;
align-items: center;
gap: var(--space-4);
min-height: var(--header-h);
}
.site-header__brand {
font-weight: 700;
color: var(--color-text);
text-decoration: none;
white-space: nowrap;
}
.site-nav__list {
display: flex;
gap: var(--space-4);
margin: 0;
padding: 0;
list-style: none;
}
.site-nav__link {
display: inline-block;
padding: var(--space-2) var(--space-1);
border-radius: var(--radius-sm);
color: var(--color-text-muted);
text-decoration: none;
transition: color var(--transition-base);
}
.site-nav__link:hover {
color: var(--color-primary);
}
/* BEM 修饰符表示当前状态 */
.site-nav__link--active {
color: var(--color-text);
font-weight: 600;
}
.theme-toggle {
display: inline-flex;
align-items: center;
justify-content: center;
width: 2.75rem;
height: 2.75rem;
margin-left: auto;
padding: 0;
border: 1px solid var(--color-border);
border-radius: var(--radius-full);
background-color: var(--color-surface);
color: var(--color-text);
cursor: pointer;
transition: border-color var(--transition-base),
background-color var(--transition-base);
}
.theme-toggle:hover {
border-color: var(--color-primary);
}
/* 图标切换:浅色显示月亮,深色显示太阳 */
.theme-toggle__icon--sun {
display: none;
}
:root[data-theme="dark"] .theme-toggle__icon--sun {
display: block;
}
:root[data-theme="dark"] .theme-toggle__icon--moon {
display: none;
}
.page {
padding-block: var(--space-7);
}
.section {
scroll-margin-top: calc(var(--header-h) + var(--space-4));
}
.section + .section {
margin-top: var(--space-7);
padding-top: var(--space-7);
border-top: 1px solid var(--color-border);
}
.section__title {
font-size: var(--font-lg);
line-height: 1.35;
}
.section__desc {
max-width: 62ch;
margin-top: var(--space-2);
margin-bottom: var(--space-5);
color: var(--color-text-muted);
font-size: var(--font-sm);
}
.site-footer {
padding-block: var(--space-6);
border-top: 1px solid var(--color-border);
color: var(--color-text-muted);
font-size: var(--font-sm);
}
/* ==================================================================
5. components/_button.css —— BEM + 局部 CSS 变量修饰符
================================================================== */
.btn {
/* 局部变量做“可配置默认值”,修饰符只覆盖变量 */
--btn-bg: transparent;
--btn-fg: var(--color-text);
--btn-border: var(--color-border);
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--space-2);
min-height: 2.75rem; /* 触控目标 ≥ 44px */
padding: var(--space-2) var(--space-4);
border: 1px solid var(--btn-border);
border-radius: var(--radius-md);
background-color: var(--btn-bg);
color: var(--btn-fg);
font-size: var(--font-sm);
font-weight: 600;
text-decoration: none;
cursor: pointer;
/* 只过渡可合成 / 低开销属性,避免触发 layout */
transition: background-color var(--transition-base),
border-color var(--transition-base), transform var(--transition-base);
}
.btn:hover {
transform: translateY(-1px);
}
.btn:active {
transform: translateY(0);
}
.btn--primary {
--btn-bg: var(--color-primary);
--btn-fg: var(--color-on-primary);
--btn-border: var(--color-primary);
}
.btn--primary:hover {
--btn-bg: var(--color-primary-hover);
--btn-border: var(--color-primary-hover);
}
.btn--ghost {
--btn-bg: transparent;
--btn-border: transparent;
}
.btn--ghost:hover {
--btn-bg: var(--color-code-bg);
}
.btn--lg {
min-height: 3rem;
padding: var(--space-3) var(--space-5);
font-size: 1rem;
}
.btn[disabled],
.btn--disabled {
opacity: 0.55;
cursor: not-allowed;
transform: none;
}
/* ==================================================================
6. components/_card.css
================================================================== */
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));
gap: var(--space-4);
}
.card {
display: flex;
flex-direction: column;
gap: var(--space-3);
padding: var(--space-5);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
background-color: var(--color-surface);
box-shadow: var(--shadow-card);
/* 性能:把重排 / 重绘影响限制在本元素内部 */
contain: layout paint;
transition: transform var(--transition-base),
box-shadow var(--transition-base), border-color var(--transition-base);
}
.card:hover {
transform: translateY(-2px);
}
.card--featured {
border-color: var(--color-primary);
box-shadow: 0 0 0 1px var(--color-primary), var(--shadow-card);
}
.card__badge {
align-self: flex-start;
padding: var(--space-1) var(--space-3);
border-radius: var(--radius-full);
background-color: var(--color-badge-bg);
color: var(--color-badge-text);
font-size: 0.75rem;
font-weight: 600;
}
.card__title {
font-size: 1.0625rem;
line-height: 1.4;
}
.card__text {
color: var(--color-text-muted);
font-size: var(--font-sm);
}
.card__footer {
display: flex;
align-items: center;
gap: var(--space-3);
margin-top: auto; /* 底部对齐 */
padding-top: var(--space-2);
}
/* ==================================================================
7. components/_field.css —— 表单可访问性
================================================================== */
.form {
display: grid;
gap: var(--space-4);
max-width: 32rem;
}
.field {
display: grid;
gap: var(--space-2);
}
.field__label {
font-size: var(--font-sm);
font-weight: 600;
}
.field__input {
padding: var(--space-3);
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
background-color: var(--color-surface);
color: var(--color-text);
transition: border-color var(--transition-base);
}
.field__input:hover {
border-color: var(--color-text-muted);
}
.field__hint {
color: var(--color-text-muted);
font-size: var(--font-sm);
}
/* 错误提示:图标 + 文案,不依赖颜色单独传达信息 */
.field__error {
display: flex;
align-items: center;
gap: var(--space-2);
color: var(--color-danger);
font-size: var(--font-sm);
}
.field--invalid .field__input {
border-color: var(--color-danger);
}
/* ==================================================================
8. components/_task-list.css —— content-visibility 性能示例
================================================================== */
.task-list {
display: grid;
gap: var(--space-2);
margin: 0;
padding: 0;
list-style: none;
}
.task-list__item {
padding: var(--space-3) var(--space-4);
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
background-color: var(--color-surface);
font-size: var(--font-sm);
/* 性能:视口外的列表项跳过渲染,长列表滚动更顺滑 */
content-visibility: auto;
contain-intrinsic-size: auto 3.25rem;
}
/* ==================================================================
9. utils/_utilities.css
================================================================== */
.stack {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--space-3);
}
code {
padding: 0.1em 0.35em;
border-radius: 4px;
background-color: var(--color-code-bg);
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 0.9em;
}
.icon {
flex: 0 0 auto;
}
/* ==================================================================
10. utils/_a11y.css —— 可访问性
================================================================== */
/* 10.1 屏幕阅读器专用文本 */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
/* 10.2 跳过导航(键盘用户第一个可聚焦元素) */
.skip-link {
position: absolute;
top: var(--space-2);
left: var(--space-4);
z-index: 100;
padding: var(--space-2) var(--space-4);
border-radius: var(--radius-md);
background-color: var(--color-primary);
color: var(--color-on-primary);
text-decoration: none;
transform: translateY(-200%);
transition: transform var(--transition-base);
}
.skip-link:focus-visible {
transform: translateY(0);
}
/* 10.3 统一可见焦点,绝不直接 outline: none */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
outline: 2px solid var(--color-focus);
outline-offset: 2px;
}
/* 10.4 尊重用户的“减少动态效果”设置 */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
/* 10.5 窄屏适配 */
@media (max-width: 40rem) {
.site-nav {
display: none; /* 实际项目可换成汉堡菜单 */
}
.page {
padding-block: var(--space-6);
}
}
</style>
</head>
<body>
<!-- 跳过导航:键盘 / 屏幕阅读器用户可直达主内容 -->
<a class="skip-link" href="#main">跳到主要内容</a>
<!-- ================= 页头:BEM 命名 + 主题切换 ================= -->
<header class="site-header">
<div class="container site-header__inner">
<a class="site-header__brand" href="#top">CSS3 工程化示例</a>
<nav class="site-nav" aria-label="主导航">
<ul class="site-nav__list">
<li><a class="site-nav__link site-nav__link--active" href="#components" aria-current="page">组件</a></li>
<li><a class="site-nav__link" href="#form">表单</a></li>
<li><a class="site-nav__link" href="#perf">性能</a></li>
</ul>
</nav>
<button class="theme-toggle" type="button" aria-pressed="false" aria-label="切换到深色主题">
<svg class="theme-toggle__icon theme-toggle__icon--moon" width="20" height="20"
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8Z" />
</svg>
<svg class="theme-toggle__icon theme-toggle__icon--sun" width="20" height="20"
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<circle cx="12" cy="12" r="4" />
<path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4" />
</svg>
</button>
</div>
</header>
<!-- ================= 主内容 ================= -->
<main class="page container" id="main">
<!-- ---------- 1. 按钮:BEM ---------- -->
<section class="section" id="components" aria-labelledby="components-title">
<h2 class="section__title" id="components-title">按钮:BEM 命名 + CSS 变量修饰符</h2>
<p class="section__desc">
基础块 <code>.btn</code> 只暴露三个局部变量
<code>--btn-bg</code> / <code>--btn-fg</code> / <code>--btn-border</code>,
修饰符 <code>.btn--primary</code>、<code>.btn--ghost</code> 仅覆盖变量,
组件内部规则零改动,天然可扩展。
</p>
<div class="stack">
<button class="btn btn--primary" type="button">主要按钮</button>
<button class="btn" type="button">次要按钮</button>
<button class="btn btn--ghost" type="button">幽灵按钮</button>
<button class="btn btn--primary btn--lg" type="button">大号按钮</button>
<button class="btn btn--primary" type="button" disabled>禁用状态</button>
</div>
</section>
<!-- ---------- 2. 卡片:模块化 + 修饰符 ---------- -->
<section class="section" aria-labelledby="cards-title">
<h2 class="section__title" id="cards-title">卡片:模块化布局与状态修饰符</h2>
<p class="section__desc">
卡片是独立 Block,不依赖父级选择器;<code>.card--featured</code> 表达“推荐”状态;
<code>.card</code> 上的 <code>contain: layout paint</code> 将重排重绘限制在卡片内部。
</p>
<div class="grid">
<article class="card">
<span class="card__badge">基础</span>
<h3 class="card__title">标准卡片</h3>
<p class="card__text">
使用语义令牌着色,切换主题时无需改动任何组件代码,只换令牌即可。
</p>
<div class="card__footer">
<button class="btn" type="button">查看详情</button>
</div>
</article>
<article class="card card--featured">
<span class="card__badge">推荐</span>
<h3 class="card__title">推荐卡片</h3>
<p class="card__text">
修饰符只负责“状态”,不重复定义布局与排版,避免样式漂移。
</p>
<div class="card__footer">
<button class="btn btn--primary" type="button">立即使用</button>
</div>
</article>
<article class="card">
<span class="card__badge">性能</span>
<h3 class="card__title">合成层动画</h3>
<p class="card__text">
悬停只改 <code>transform</code> 与 <code>box-shadow</code>,
不触碰 <code>width</code> / <code>top</code> 等触发重排的属性。
</p>
<div class="card__footer">
<button class="btn" type="button">了解详情</button>
</div>
</article>
</div>
</section>
<!-- ---------- 3. 表单:可访问性与语义化 ---------- -->
<section class="section" id="form" aria-labelledby="form-title">
<h2 class="section__title" id="form-title">表单:语义化标签与可访问性</h2>
<p class="section__desc">
<code>label</code> 通过 <code>for</code> 显式绑定;错误提示用
<code>aria-describedby</code> 关联、用 <code>aria-invalid</code> 标记状态;
错误信息同时具备图标与文字,不只依赖颜色。
</p>
<form class="form" novalidate>
<div class="field">
<label class="field__label" for="email">
邮箱地址 <span aria-hidden="true">*</span>
</label>
<input
class="field__input"
id="email"
name="email"
type="email"
required
autocomplete="email"
placeholder="you@example.com"
aria-describedby="email-hint" />
<p class="field__hint" id="email-hint">用于接收通知,不会公开显示。</p>
</div>
<div class="field field--invalid">
<label class="field__label" for="username">
用户名 <span aria-hidden="true">*</span>
</label>
<input
class="field__input"
id="username"
name="username"
type="text"
required
value="a"
aria-invalid="true"
aria-describedby="username-error" />
<p class="field__error" id="username-error">
<svg class="icon" width="16" height="16" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"
aria-hidden="true" focusable="false">
<circle cx="12" cy="12" r="10" />
<path d="M12 8v4M12 16h.01" />
</svg>
用户名至少需要 3 个字符
</p>
</div>
<div>
<button class="btn btn--primary btn--lg" type="submit">提交表单</button>
</div>
</form>
</section>
<!-- ---------- 4. 性能 ---------- -->
<section class="section" id="perf" aria-labelledby="perf-title">
<h2 class="section__title" id="perf-title">性能:content-visibility 与 contain</h2>
<p class="section__desc">
下面每个列表项都带有 <code>content-visibility: auto</code> 与
<code>contain-intrinsic-size</code>,视口外的元素会被浏览器跳过渲染,
长列表首屏渲染成本显著降低。
</p>
<ul class="task-list">
<li class="task-list__item">01 · 使用 <code>transform</code> / <code>opacity</code> 做动画</li>
<li class="task-list__item">02 · 避免深层嵌套选择器,降低匹配成本</li>
<li class="task-list__item">03 · 关键 CSS 内联,非关键 CSS 延迟加载</li>
<li class="task-list__item">04 · 字体使用 <code>font-display: swap</code></li>
<li class="task-list__item">05 · 图片设置宽高,减少 CLS</li>
<li class="task-list__item">06 · <code>will-change</code> 慎用,用完即撤</li>
<li class="task-list__item">07 · 用 <code>@layer</code> 管理优先级,减少 <code>!important</code></li>
<li class="task-list__item">08 · 拆分公共样式与页面样式,控制单文件体积</li>
<li class="task-list__item">09 · 用 Stylelint + Prettier 保证团队一致性</li>
<li class="task-list__item">10 · 用 PostCSS + Autoprefixer 处理浏览器前缀</li>
<li class="task-list__item">11 · 用 browserslist 明确目标浏览器范围</li>
<li class="task-list__item">12 · 用 <code>@supports</code> 做渐进增强与降级</li>
</ul>
</section>
</main>
<!-- ================= 页脚 ================= -->
<footer class="site-footer">
<div class="container">
<p>
覆盖内容:BEM 命名 · 代码组织与模块化 · CSS 变量与主题切换 · reset / normalize ·
Autoprefixer 兼容策略 · 渲染性能优化 · 可访问性与语义化
</p>
</div>
</footer>
<!-- ================= 交互脚本:主题切换 ================= -->
<script>
(function () {
var root = document.documentElement;
var STORAGE_KEY = 'demo-theme';
var mq = window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null;
function applyTheme(theme) {
root.setAttribute('data-theme', theme);
var btn = document.querySelector('.theme-toggle');
if (!btn) return;
var isDark = theme === 'dark';
btn.setAttribute('aria-pressed', String(isDark));
btn.setAttribute('aria-label', isDark ? '切换到浅色主题' : '切换到深色主题');
}
function readSaved() {
try { return localStorage.getItem(STORAGE_KEY); } catch (e) { return null; }
}
function save(theme) {
try { localStorage.setItem(STORAGE_KEY, theme); } catch (e) { /* 隐私模式忽略 */ }
}
// 同步按钮的无障碍属性(主题本身已在 <head> 中提前应用)
applyTheme(root.getAttribute('data-theme') || 'light');
// 用户未手动选择时,跟随系统主题变化
if (mq) {
var onSystemChange = function (e) {
if (!readSaved()) applyTheme(e.matches ? 'dark' : 'light');
};
if (mq.addEventListener) {
mq.addEventListener('change', onSystemChange);
} else if (mq.addListener) {
mq.addListener(onSystemChange); // 兼容旧版 Safari / Edge
}
}
// 手动切换
document.addEventListener('click', function (e) {
var btn = e.target.closest ? e.target.closest('.theme-toggle') : null;
if (!btn) return;
var next = root.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
applyTheme(next);
save(next);
});
})();
</script>
</body>
</html>
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/caoli201314/article/details/170145889




