1. 从一行<!doctype html>说起:为什么每个前端人都绕不开这套基础
打开任何一个网页,右键查看源代码,第一行大概率是<!doctype html>。这行看起来像注释又像标签的东西,是 HTML 文档的声明,告诉浏览器用标准模式来渲染页面。很多人学 HTML 的时候直接跳过它,觉得写不写无所谓,结果后面遇到盒模型计算异常、某些 CSS 属性不生效,排查半天才发现是文档模式的问题。我自己早期就踩过这个坑,一个页面在本地打开正常,部署到服务器上布局全乱,最后定位到是漏写了 doctype 导致浏览器进入了怪异模式。
HTML 教程这个主题看起来基础,但真正能把 HTML、CSS 以及它们之间的配合讲清楚的内容并不多。大部分教程要么只罗列标签,要么直接跳到框架,中间缺失了“为什么这样写”的逻辑。这篇内容面向的是刚接触网页制作的新手,也适合写了一段时间但基础不牢的开发者回头补课。我会从文档结构、标签语义、CSS 引入方式、盒模型、定位布局、动画过渡这几个核心板块展开,每个部分都配上可直接复制运行的代码和实际效果说明。
核心关键词 HTML 和 CSS 会贯穿全文,但不会为了堆砌而堆砌。读完之后,你应该能独立写出一个结构清晰、样式可控、兼容性过得去的静态页面,并且知道遇到问题时该往哪个方向排查。
2. HTML 文档骨架与核心标签的实战拆解
2.1 标准文档结构:每个标签都有存在的理由
一个标准的 HTML 文档结构长这样:
<!doctype html> <html lang="zh-cn"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>页面标题</title> </head> <body> <h1>主标题</h1> <p>段落内容</p> </body> </html>逐行拆解一下。<!doctype html>是 HTML5 的文档类型声明,不区分大小写,但习惯上写成小写。<html lang="zh-cn">中的lang属性告诉浏览器和搜索引擎这个页面的主要语言是简体中文,对屏幕阅读器和翻译工具都很重要。<meta charset="utf-8">声明字符编码,必须放在<head>的最前面,否则中文可能变成乱码。<meta name="viewport">是移动端适配的关键,没有它,手机浏览器会按照桌面宽度渲染然后缩小,字小到看不清。
<title>里的内容会显示在浏览器标签页上,也是搜索引擎结果页的标题来源。<body>里放所有可见内容。这个骨架看起来简单,但每一行都有明确用途,缺一个都可能出问题。
注意:
<meta charset>必须放在<head>开始后的前 1024 个字节内,否则浏览器可能来不及识别编码就已经开始解析了。
2.2 语义化标签:不只是 div 和 span
很多人写 HTML 从头到尾只用<div>和<span>,加 class 来区分。这样能跑,但语义丢失了。HTML5 引入了一批语义化标签,比如<header>、<nav>、<main>、<article>、<section>、<aside>、<footer>。它们和<div>在默认样式上几乎没区别,但带来的好处很实际。
搜索引擎爬虫会根据这些标签判断页面结构,屏幕阅读器用户可以通过标签快速跳转到导航区或主内容区。团队协作时,别人看你的代码能一眼看懂每块区域的功能。我现在的习惯是,能用语义标签就不用 div,只有在纯粹为了布局包裹且没有合适语义标签时才用 div。
表格类内容用<table>、<thead>、<tbody>、<tr>、<th>、<td>,不要用 div 模拟表格。表单用<form>、<label>、<input>、<select>、<textarea>,<label>的for属性和 input 的id关联后,点击文字也能聚焦输入框,这个细节对移动端体验提升很明显。
2.3 常用标签速查与易错点
下面这张表整理了我实际项目中高频使用的标签和容易写错的地方:
| 标签 | 用途 | 易错点 |
|---|---|---|
<a> | 超链接 | href和target配合,target="_blank"建议加rel="noopener" |
<img> | 图片 | 必须写alt,否则图片加载失败时用户不知道这里是什么 |
<ul>/<ol> | 列表 | 直接子元素只能是<li>,不能塞 div |
<input> | 输入框 | type属性决定行为,name属性决定提交时的键名 |
<button> | 按钮 | 在 form 内默认是 submit 类型,不想提交要写type="button" |
<br> | 换行 | 只用于文本内换行,不要用来做段落间距 |
<img>的alt属性我单独说一下。它不只是给搜索引擎看的,图片加载失败时浏览器会显示 alt 文字,视障用户靠它理解图片内容。如果图片纯粹是装饰性的,写alt=""让屏幕阅读器跳过,而不是省略 alt 属性。
3. CSS 引入方式与选择器体系:把样式管起来
3.1 三种引入方式的取舍
CSS 引入 HTML 有三种方式:行内样式、内部样式表、外部样式表。
行内样式写在标签的style属性里,优先级最高,但完全无法复用,改一个颜色要翻遍整个文件。内部样式表写在<head>里的<style>标签中,适合单页面且样式不多的场景。外部样式表用<link rel="stylesheet" href="style.css">引入,是最推荐的方式,浏览器可以缓存 CSS 文件,多个页面共用一套样式,维护成本最低。
我个人的原则是:永远不用行内样式,除非是 JavaScript 动态计算出来的值需要临时设置。内部样式表只在写 demo 或者邮件模板时用,正式项目一律外部样式表。
<link rel="stylesheet" href="./css/style.css"><link>标签放在<head>里,不要放到<body>末尾。虽然浏览器会继续渲染,但放到 head 里能让样式在页面绘制前就加载好,避免页面闪烁。
3.2 选择器优先级:为什么你的样式不生效
CSS 选择器优先级是新手最容易困惑的地方。简单记这个顺序:行内样式 > ID 选择器 > 类选择器/属性选择器/伪类 > 标签选择器/伪元素。同级别下,后写的覆盖先写的。
具体权重可以用数字表示:行内样式 1000,ID 100,类/属性/伪类 10,标签/伪元素 1。!important直接最高,但滥用会导致后期无法覆盖,我建议只在覆盖第三方库样式且没有其他办法时使用。
/* 权重 1 */ p { color: #333; } /* 权重 10 */ .text { color: #666; } /* 权重 100 */ #main { color: #999; } /* 权重 11,覆盖上面的 .text */ p.text { color: #000; }有个常见误区:*通配符选择器权重是 0,但它会匹配所有元素,性能开销比想象中大。在大型项目中,* { margin: 0; padding: 0; }这种重置写法会遍历整个 DOM 树,建议改成具体标签的重置或者用更精细的 reset 方案。
3.3 盒模型:理解 width 到底算的是什么
盒模型是 CSS 布局的基石。每个元素都是一个矩形盒子,由 content、padding、border、margin 四层组成。默认情况下,width只指 content 的宽度,padding 和 border 会额外撑大盒子。这就是为什么你设置width: 100px再加padding: 10px,实际占位变成 120px。
box-sizing: border-box可以让 width 包含 padding 和 border,这样计算更直观。现在大部分项目都会全局设置:
*, *::before, *::after { box-sizing: border-box; }这样设置后,width: 100px的元素无论加多少 padding 和 border,总占位始终是 100px,content 区域自动缩小。对于响应式布局来说,这个设置能省掉大量计算。
margin 有个经典问题:垂直方向相邻元素的 margin 会合并,取较大值而不是相加。比如上面元素margin-bottom: 20px,下面元素margin-top: 30px,实际间距是 30px 不是 50px。这不是 bug,是规范定义的行为。如果不想要合并,可以给父元素加overflow: hidden或者用 padding 替代 margin。
3.4 文本与字体样式:让内容读起来舒服
文本样式涉及 font-family、font-size、font-weight、line-height、color、text-align 等属性。font-family 要写字体栈,从首选到备选,最后加通用字体族:
body { font-family: "PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif; }line-height 建议用无单位数值,比如 1.6,这样会基于当前 font-size 计算,子元素继承的是比例而不是固定值。如果用line-height: 24px,子元素字体变大时行高不会跟着变,容易挤在一起。
文本溢出处理也是高频需求。单行溢出用:
.ellipsis { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }多行溢出用-webkit-line-clamp:
.ellipsis-multi { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }这个方案在主流浏览器上都能用,虽然带 webkit 前缀,但实际兼容性已经很好。
4. 布局与定位:从居中到响应式的完整方案
4.1 水平垂直居中的五种写法
居中问题是 CSS 的经典面试题,也是实际开发中反复出现的需求。我整理了几种常用方案,按场景选择。
第一种,flex 布局,最通用:
.parent { display: flex; justify-content: center; align-items: center; }第二种,grid 布局,代码更短:
.parent { display: grid; place-items: center; }第三种,绝对定位加 transform,适合弹窗这类脱离文档流的元素:
.child { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }第四种,绝对定位加 margin auto,需要知道元素宽高:
.child { position: absolute; top: 0; left: 0; right: 0; bottom: 0; margin: auto; width: 200px; height: 100px; }第五种,单行文本垂直居中,用 line-height 等于容器高度:
.parent { height: 40px; line-height: 40px; }实际项目中我首选 flex,兼容性好,代码直观,不需要知道子元素尺寸。grid 的place-items更简洁,但老项目里可能要考虑兼容性。
4.2 定位体系:relative、absolute、fixed、sticky
position属性有五个值:static、relative、absolute、fixed、sticky。
static 是默认值,元素按正常文档流排列。relative 是相对自身原位置偏移,不脱离文档流,原位置仍然占位。absolute 是相对最近的已定位祖先元素偏移,脱离文档流。fixed 是相对视口偏移,滚动页面时位置不变。sticky 是 relative 和 fixed 的结合,滚动到阈值前是 relative,达到阈值后变成 fixed。
absolute 的参照物是最近的position不为 static 的祖先元素。如果找不到,就相对初始包含块(通常是视口)。这个特性经常被用来做角标、下拉菜单、遮罩层。
.card { position: relative; } .badge { position: absolute; top: -8px; right: -8px; }sticky 做吸顶导航很实用:
.nav { position: sticky; top: 0; z-index: 100; }注意 sticky 生效需要满足几个条件:父元素不能有overflow: hidden或overflow: auto,否则会失效。这个坑我踩过好几次,排查半天才发现是父级 overflow 的问题。
4.3 响应式布局:媒体查询与弹性单位
响应式布局的核心是让页面在不同屏幕尺寸下都能正常显示。媒体查询是基础工具:
@media (max-width: 768px) { .sidebar { display: none; } .main { width: 100%; } }断点选择没有标准答案,一般参考主流设备宽度:320px、375px、768px、1024px、1280px。我习惯用 768px 作为移动端和平板的分界,1024px 作为平板和桌面的分界。
弹性单位方面,百分比、vw、vh、rem、em 各有用途。vw 是视口宽度的百分之一,做全屏宽度很方便。rem 是根元素 font-size 的倍数,配合媒体查询改根字号可以实现整体缩放。em 是相对父元素 font-size,嵌套时容易算错,我一般只在需要相对当前字号调整间距时用。
html { font-size: 16px; } @media (max-width: 768px) { html { font-size: 14px; } } .title { font-size: 1.5rem; /* 桌面 24px,移动 21px */ }5. 动画、过渡与交互细节:让页面活起来
5.1 transition 过渡:最简单的动效方案
transition 用于元素状态变化时的平滑过渡,比如 hover 变色、按钮按下、菜单展开。基本语法:
.btn { background: #3498db; transition: background 0.3s ease, transform 0.2s ease; } .btn:hover { background: #2980b9; transform: translateY(-2px); }transition 的属性值依次是:过渡属性、持续时间、缓动函数、延迟时间。缓动函数常用的有 ease、linear、ease-in、ease-out、ease-in-out,也可以用 cubic-bezier 自定义。
有个性能细节:transition 尽量只作用于 transform 和 opacity,这两个属性可以由 GPU 加速,不会触发重排重绘。如果过渡 width、height、margin 这些属性,浏览器需要重新计算布局,动画容易卡顿。
5.2 animation 动画:关键帧的玩法
animation 比 transition 更强大,可以定义多个关键帧,循环播放,控制方向。
@keyframes ripple { 0% { transform: scale(0.8); opacity: 1; } 100% { transform: scale(2); opacity: 0; } } .ripple { animation: ripple 1.5s ease-out infinite; }涟漪光圈扩散效果就是典型的 animation 应用:一个圆形元素从中心放大并淡出,循环播放。做这种效果时,记得给动画元素加will-change: transform或者用transform: translateZ(0)开启 GPU 加速,减少卡顿。
animation 的animation-fill-mode属性容易被忽略。默认情况下,动画结束后元素回到初始状态。如果想让元素停留在最后一帧,要写animation-fill-mode: forwards。
5.3 鼠标交互与伪类
CSS 伪类可以响应鼠标状态::hover悬停、:active按下、:focus聚焦、:visited已访问。做按钮交互时,这几个状态都要考虑:
.btn:hover { background: #2980b9; } .btn:active { transform: scale(0.98); } .btn:focus { outline: 2px solid #3498db; outline-offset: 2px; }:focus样式不要直接去掉 outline,那样键盘用户无法知道焦点在哪里。可以用:focus-visible只在键盘导航时显示焦点样式:
.btn:focus { outline: none; } .btn:focus-visible { outline: 2px solid #3498db; }鼠标移入事件除了 hover,还有mouseenter和mouseover的区别。hover 对应的是 mouseenter 的行为,不冒泡,只在进入元素本身时触发。mouseover 会在进入子元素时也触发,容易导致闪烁,一般用 hover 就够了。
6. 常见问题与排查技巧实录
6.1 样式不生效的排查清单
样式不生效是最高频的问题,我整理了一个排查顺序:
| 排查项 | 检查方法 | 常见原因 |
|---|---|---|
| 选择器是否匹配 | 浏览器开发者工具选中元素看 Styles 面板 | 类名拼写错误、层级写错 |
| 优先级是否被覆盖 | 看 Styles 面板中被划掉的属性 | 权重不够、后写的覆盖了先写的 |
| 文件是否加载 | Network 面板看 CSS 请求状态 | 路径错误、404 |
| 语法是否错误 | 看 Styles 面板是否有解析警告 | 少分号、括号不匹配 |
| 缓存问题 | 强制刷新 Ctrl+F5 | 浏览器缓存了旧 CSS |
开发者工具是排查样式的核心武器。选中元素后,Styles 面板会列出所有匹配的规则,被覆盖的会显示删除线,一眼就能看出哪条规则生效了。
6.2 布局塌陷与清除浮动
浮动元素会导致父元素高度塌陷,这是经典问题。现代布局用 flex 和 grid 已经很少用 float 了,但维护老项目时还是会遇到。清除浮动的方案:
.clearfix::after { content: ""; display: table; clear: both; }给父元素加这个类,就能包含浮动子元素的高度。不过新项目我建议直接用 flex,不需要清除浮动。
6.3 移动端适配的坑
移动端有几个高频问题。第一,点击延迟 300ms,现在加<meta name="viewport" content="width=device-width">基本解决了。第二,1px 边框在 Retina 屏上变粗,可以用 transform scale 或者 box-shadow 模拟。第三,iOS 上 input 聚焦时页面放大,把 input 的 font-size 设成 16px 以上可以避免。
input, select, textarea { font-size: 16px; }第四,100vh 在移动端浏览器地址栏收起展开时会变化,导致布局跳动。可以用100dvh或者 JavaScript 动态计算。
6.4 HTML 转 Markdown 与格式转换
有时候需要把 HTML 内容转成 Markdown,比如写文档或者迁移内容。简单场景可以手动转换,复杂场景用工具。核心映射关系:<h1>到#,<p>到空行分隔,<a>到[文字](链接),<img>到,<strong>到**,<em>到*。
反过来 Markdown 转 HTML 更常见,大部分静态站点生成器都支持。如果要把 HTML 表格转成 WPS 表格,直接复制粘贴通常能保留结构,或者用在线转换工具处理。
7. 工具链与学习路径建议
7.1 编辑器选择
写 HTML/CSS 的编辑器,VS Code 是目前最主流的选择。装几个插件体验会好很多:Live Server 可以起本地服务并自动刷新,Prettier 格式化代码,Auto Rename Tag 自动改配对标签,IntelliSense for CSS 补全类名。
如果只是临时改几行代码,浏览器的开发者工具就够用。Sources 面板可以直接编辑 CSS 并实时预览,改满意了再复制回文件。Ubuntu 上可以用 gedit 或者 VS Code,看个人习惯。
7.2 学习路径
HTML 和 CSS 的学习曲线是先陡后缓。前两周会觉得标签和属性太多记不住,写多了就会发现常用的就那几十个。我的建议是不要死记,遇到不会的查 MDN 文档,写几个完整页面自然就熟了。
练手项目可以从简单到复杂:个人简介页、产品卡片列表、登录表单、响应式导航栏、商品详情页。每个项目都强迫自己用语义化标签、外部样式表、flex 或 grid 布局,不要用表格布局或者行内样式偷懒。
JavaScript 可以等 HTML 和 CSS 有一定基础后再学。先把静态页面写明白,再学交互逻辑,否则容易两头顾不上。HTML 负责结构,CSS 负责表现,JavaScript 负责行为,这个分工要刻在脑子里。
7.3 代码规范与可维护性
类名命名推荐 BEM 风格:block__element--modifier。比如.card__title--large,一看就知道是 card 块下的 title 元素的大号变体。这样命名虽然长,但避免了样式冲突,团队协作时尤其重要。
CSS 文件组织可以按功能拆分:reset.css、layout.css、components.css、utilities.css,最后用一个 main.css 引入。项目大了之后还可以上 Sass 或 Less 做变量和嵌套,但小项目直接写原生 CSS 就够了,不要为了工具而工具。
我在实际项目中最大的体会是:HTML 结构写好了,CSS 会好写很多。如果发现 CSS 越写越乱,大概率是 HTML 结构有问题,该加容器的地方没加,该用语义标签的地方用了 div。回头重构 HTML 往往比在 CSS 里打补丁更有效。