
前阵子带一位刚入行的实习生让他把设计稿切成静态页面。他交上来的代码打开一看满屏都是div连标题都用div classtitle加CSS调字号。我问他为什么不直接写h1他犹豫了一下说“怕这些标签自带样式影响我排版。”这个回答其实代表了相当一部分入门同学的真实困惑——觉得h1、h2、p这些标签只是“带样式的div”用哪个都行甚至不用更好。今天想把这件事说透。HTML标签不是样式工具它最核心的价值是给内容一个准确的身份说明。浏览器、搜索引擎、读屏软件全靠这些标签判断页面里每一块文字和图片到底是什么意思。这篇文章会从标签的底层逻辑讲起把开发里使用频率最高的标签族逐个拆开说明包括它们的基本写法、默认行为、常见坑和组合方式。适合刚学完HTML语法、正在写第一个完整页面的新手也适合那些写了很久div、想重新整理标签体系的前端同学。1. 标签的底层逻辑它给内容一个“身份说明”不是给一套“默认皮肤”1.1 标签在页面里到底做了什么很多人学HTML时第一个记住的就是“标签是成对出现的尖括号”但很少去想尖括号包裹的内容在浏览器里发生了什么。其实标签的作用可以类比成快递盒上的面单盒子还是那个盒子但贴了“易碎品”就会被轻拿轻放贴了“生鲜”就要冷藏运输。同理p告诉浏览器“这是一段文字”h1告诉浏览器“这是这块内容的标题”ul告诉浏览器“这里是一组没有顺序的列表项”。浏览器拿到标签后会调用两条处理链路。第一按照HTML规范里约定好的默认样式表给标签套上一层基础外观比如h1字号大、加粗a蓝色下划线ul前面带圆点。第二在内存里构建一棵DOM树把标签和内容组织成节点关系方便JavaScript去查找和操作。这两条链路决定了标签同时拥有“语义”和“默认表现”两个属性而默认表现只是语义的外在视觉结果不是标签存在的目的。所以当你想把一个文字放大加粗时直接用h1并改它的CSS样式这件事本身没问题有问题的是反过来——为了得到某个字号把一段正文包裹在h1里。页面视觉上可能看不出差别但搜索引擎会把这段文字当成一个重要标题去索引读屏软件也会按标题层级跳转整个文档结构就被搞乱了。1.2 我习惯把常用HTML标签分成四类做了七八年前端我自己心里有一个很简单的分类方法把标签按照承担的职责分成四类。第一类是文档骨架标签负责搭建页面的基础框架典型代表是html、head、body、meta、title。这类标签每个页面只能出现一次而且位置基本固定它们在DOM树的最外层决定了页面的元信息和整体结构。第二类是内容组织标签负责对页面主体内容切分段落和层级比如h1到h6、p、ul、ol、table、div。这类标签解决的是“内容怎么划分”的问题它们是页面上出现次数最多的一批。第三类是交互或媒体标签负责让用户和页面产生互动包括a、button、input、textarea、select、img、video。它们通常携带src、href、action这些特殊属性告诉浏览器要加载什么资源、点击后跳转到哪里、提交时把数据发给谁。第四类是语义辅助标签不负责具体内容而是给内容补充额外说明比如strong表示强调、em表示语气加重、small表示注释性小字、mark表示高亮。这类标签写在内容标签里层让一段文字的含义更精确。理解这个分类后再去记标签就不会觉得是死背字母表而是知道每个标签在文档里站的位置。后面几节我会按照这个分类一层层展开。2. 页面骨架五件套doctype、html、head、meta与title的搭配细节2.1 doctype不是HTML标签但少了它整个页面都会“降级”先说一个容易忽略的点!DOCTYPE html不是HTML标签它是一条文档类型声明。它的作用是告诉浏览器“请严格按照HTML5标准解析这份文档”。如果漏掉这行浏览器会进入一种叫“怪异模式”quirks mode的兼容状态用上世纪90年代的盒模型算法渲染页面。同样一个width: 100px的div在怪异模式下padding会往里面挤在标准模式下padding会往外撑新手写半天样式对不上多半都是这个原因。HTML5的doctype写法比老版本短得多就是!DOCTYPE html大小写都行但业界习惯统一用大写。它必须放在文件第一行前面连注释和空行都不能有。我见过有人把doctype放在head后面浏览器直接忽略整个页面的CSS布局全线崩坏排查了一下午才发现是doctype的位置错了。2.2html标签上的lang属性比想象中更重要html是所有标签的最外层容器官方叫法叫“文档根元素”页面里所有其他标签都得放在它的开标签和闭标签之间。大部分教程会告诉你html必须加一个lang属性但很少说为什么。langzh-CN表示页面主要语言是简体中文。这个属性直接影响三件事第一浏览器翻译插件会依据它判断要不要提示翻译第二读屏软件会按对应的语音包朗读文字如果你的页面是中文内容却写了langen屏幕阅读器会用英文音素读中文出来的是一串无法听懂的乱码第三搜索引擎的爬虫会把语言作为一种排名信号。所以即使你打开编辑器自动生成的模板里没写lang自己也应该补上。2.3 head里的meta、titleSEO和兼容性的第一道关卡head标签本身不展示任何页面内容它就像一个档案袋装着关于这个页面的元信息。所有meta、title、link、style这些标签都要放在head里。title是每个页面必须有的它的内容是浏览器标签页上显示的文字也是搜索结果里那个蓝色标题。一个好的title应该做到两点长度控制在30个汉字以内核心关键词放前面。比如一个介绍CSS选择器的页面写成“CSS选择器详解常用选择器与优先级规则 - 某技术博客”就比“某技术博客 - CSS选择器详解”更容易让用户一眼看出页面内容。meta标签里最常用的三个。第一个是meta charsetUTF-8它声明文档字符编码不写的话中文很可能显示成乱码。第二个是meta nameviewport contentwidthdevice-width, initial-scale1.0它保证页面在手机上按设备宽度渲染不写的话移动端会按980px的虚拟宽度缩放字小到看不清。第三个是meta namedescription content一句话描述它会在搜索结果里作为摘要展示好的描述能提升点击率。2.4 body用户看到的一切都从这里开始body是页面可见内容的唯一容器它不需要任何特殊属性但有两条实操经验值得记住。第一body里不应该直接放零散的文本节点所有文字和元素都应该至少被一个语义标签包裹比如p或者section这样DOM结构才是规整的树。第二body标签本身在CSS里经常被用来设置全局背景色或字体比如body { font-family: PingFang SC, Microsoft YaHei, sans-serif; }因为字体和背景色会默认继承给后代的块级元素写在body上是最合理的全局设置方式。3. 正文内容里最高频的六个标签族标题、段落、列表、链接、图片、表格3.1 标题标签h1~h6层级就是文档大纲标题标签是正文内容里最重要的语义标签。h1是页面最高级标题h2是次一级的章节标题依次递减到h6。它们默认的字号、粗细有差异但不要为了视觉差异去选标签级别。正确的做法是先想清楚内容结构再对应到标题层级最后用CSS覆盖默认样式。页面规划标题时有一个硬性建议一个页面只保留一个h1。因为h1相当于整篇文章的题目多个h1会让搜索引擎和读屏软件搞不清楚哪个才是主要内容的起点。如果一个页面里有多个独立板块每个板块用h2做标题板块内再细分小标题用h3这样一层层嵌套下来文档大纲就是一棵清晰有致的树。很多内容管理系统还会自动根据h1~h6生成目录这时候标题层级是否规范直接决定生成的目录能不能用。3.2p、br与文本格式化标签排版的基本单位p标签包裹的是一段独立成块的文字段落。段落之间默认有空行这是圆角舒服阅读节奏的关键之一。写HTML时要注意一段文字里的换行应该交给CSS的white-space或者块级元素自动换行去处理不要在p里硬塞十几个br来制造行距那是上古时代的行为。br是强制换行它表示“在当前位置换一行继续写”适合用在地址、诗歌这种需要精确控制断行的场景。它是少数没有内容的空标签通常写br就够了不需要br/br这种闭合成对写法。文本格式化标签里最容易被忽视的是strong和b的区别。strong代表“重要内容”语义是强调b只代表“外观上加粗”不附加任何重要性。读屏软件会加重朗读strong里的文字但对b没有任何特殊处理。所以想强调重点请用strong只是想加粗展示某个数字或标签名用b也完全合理。同理em表示语气强调i只是斜体外观两者不要混用。3.3 列表标签ul、ol、li与自定义列表列表分两种ul是无序列表列表项之间是同等级、没有先后意义的比如导航菜单、商品特性清单ol是有序列表列表项有先后顺序比如操作步骤、排行榜。写列表的时候最核心的嵌套规则是ul和ol的直接子元素只允许是li不允许是其他标签。也就是说如果你想在列表里放一个段落也得先把段落包进li里。常见的错误写法是ulp说明文字/pli项目/li/ul这在HTML标准里是非法的浏览器解析时会把这个p强行踢出ul。还有一种不太常用的自定义列表dl配合dt和dd使用专门表示“名词——描述”这样的配对关系。比如一个课程列表课程名放dt课程介绍放dd。它最大的价值是让“键-值”形式的内容有语义归属比用ul包两行文字更准确。3.4 链接标签ahref、target与锚点a标签是互联网的基石它把一个个独立页面串成一张网。最基础的用法是a hrefhttps://example.com链接文字/a。href属性控制点击后跳转的目标地址可以是网址也可以是相对路径比如hrefdetail.html表示当前目录下的另一个页面。新手容易忽略的是target属性。默认情况下点击a会在当前标签页跳转如果想要新标签页打开需要写target_blank。但是这里有一个安全问题新打开的页面可以通过window.opener对象反向控制原来的页面所以给target_blank的链接加上relnoopener noreferrer是一个好习惯。另外站内的用户协议、条款说明这类长文档不建议用新窗口打开强制新开标签页会把用户对页面上下文的记忆打断。href还可以写成href#id名实现页内锚点跳转。比如页面底部有个“回到顶部”的链接href#top配合页面顶部某个元素的idtop就可以实现。锚点跳转时浏览器会直接滚动到目标元素位置但不会触发平滑滚动动画想要丝滑过渡还需要一点CSSscroll-behavior: smooth。3.5 图片标签imgalt属性比src更重要img是少数没有闭合标签的元素它通过src属性加载外部图片资源通过alt属性提供图片的替代文本。很多新手写图片只写src觉得alt是多余的直到图片因为网络问题加载不出来页面上只留下一片空白才发现连提示都没有。alt的核心作用是“当图片不可见时告诉用户图片应该传达什么信息”。它的意义不只是SEO更重要的是无障碍访问。视觉正常的用户看图片很容易但依赖屏幕阅读器的用户只能听到alt里的文字。所以给每张有内容的图片写清楚描述性alt比如alt一位开发者在电脑前调试页面布局比不写要好得多。对于纯装饰性的图片比如一条分割线alt可以留空写alt这样可以告诉读屏软件“这张图没有任何信息请跳过”。img还支持width、height属性但建议只通过CSS控制尺寸。如果HTML里直接写了固定的宽高在响应式布局里还需要额外覆盖不如从一开始就不写。真正值得写的是loading属性loadinglazy可以让屏幕外的图片延迟加载页面首屏渲染速度会有明显提升但要注意首页首屏的图片不要设成lazy否则视口内的图片也要等滚动才出现体验反而更差。3.6 表格标签table做好数据表格别用来切页面在HTML刚发明那些年table曾被大量用于页面布局因为它能撑起多列结构。今天的多列布局有flex和grid两种更强大的CSS方案table就应该回归它最本职的场景——展示二维数据比如成绩单、价格对比表、账单明细。一个规范的表格结构分为三个部分table里用thead放表头行tbody放数据行tfoot放汇总行。每个行标签是trtable row表头单元格用th数据单元格用td。表头th默认加粗居中且有一个隐性的scope属性可以帮助读屏软件把表头和对应数据关联起来所以表格的表头一定不要用td代替。表格有一个实际开发中经常踩的坑单元格合并。colspan表示横向合并占几列rowspan表示纵向合并占几行。合并之后表格整体列数要一致否则会出现错位。我以前排查过一个bug一个列数缺失的表格在Chrome里看起来正常换到Safari就多出一条空白列查了很久才发现是对应行里少了一个td。4. 表单标签前端交互的入口也是最容易出细节问题的区域4.1form的action与method决定了数据去哪表单的作用是收集用户输入并提交给服务器。form标签本身并不会在页面上画出一个框但只要用户点击提交按钮浏览器就会把表单内所有带name属性的字段收集起来按照method指定的方式发给action指定的地址。methodget会把数据拼在URL后面发送比如search?keyword前端page1适合搜索这种不敏感且需要可收藏可分享的请求。methodpost会把数据放在请求体里发送适合登录、注册、提交订单这类不希望数据直接暴露在URL上的场景。如果是纯前端项目只用JavaScript拦截处理表单数据不交给后端那action和method也可以不写但一定要记得给form加一个onsubmit处理函数或者用preventDefault阻止默认提交否则点击提交按钮页面会刷新这是新手最常见的一个表单坑。4.2input的type类型绝不是只有文本框input是表单里最灵活的标签它的核心属性是type决定控件长什么样、接受什么格式的数据、移动端会弹出什么键盘。基础的几个typetext是单行文本框typepassword输入内容会显示成圆点typeemail会在移动端弹出带键的键盘并在部分浏览器里自动做格式校验typenumber只允许输入数字但要注意它校验的是数字格式而不是范围想要限制输入范围还需配合min、max属性typecheckbox生成复选框typeradio生成单选框typefile生成文件选择控件配合accept属性可以限制可选择的文件类型。还有一个很容易被低估的typedate它直接调起系统级日期选择器省去写日历组件的麻烦。不过它的显示格式受浏览器语言环境影响不同操作系统下长相不一致如果产品要求风格统一还是得用前端组件库的日期选择器。4.3textarea、select、button的正确打开方式textarea是多行文本输入框它的内容写在标签体内部而不是value属性里这是它和input最大的差异点。比如textarea请输入留言内容/textarea这个默认提示文字可以被用户编辑如果想做预设值又允许用户清空后提交需要配合JavaScript处理。select是下拉选择框每个可选项用option标签包裹。select namecityoption valueshanghai上海/optionoption valuebeijing selected北京/option/select其中selected属性表示默认选中的选项。选项的实际提交值是value里的内容而不是用户看到的文字这个设计让前后端可以解耦用户看到的是中文城市名后端收到的是稳定编码。button标签尽量用type属性显式声明类型。在form里不写type的button默认就是typesubmit点击会触发表单提交。如果想做一个只触发JavaScript事件而不提交表单的按钮必须写成button typebutton这是我被追问过最多的问题之一。4.4label和name看似不起眼却决定表单的可用性label标签用来把文字说明和表单项绑定起来写法是label forusername用户名/labelinput idusername typetext或者直接把input嵌在label内部。绑定的价值在于点击文字说明时对应输入框会自动聚焦这在PC端鼠标操作和移动端触控操作中都大幅提升了可点击区域。name属性则是表单提交时的字段名。没有name的input根本不会被浏览器放进提交数据里哪怕用户填了内容也不会发给后端。所以写表单的一个自查动作就是每个需要传值的控件都必须确保有name。复选框和单选框还有一个小细节同一组选项的name必须相同这样浏览器才会把它们视为一组实现单选互斥或复选分组。5. 语义化标签与无语义标签什么时候用div什么时候用header5.1div和span没有语义但也不是不能用div是最典型的无语义块级标签span是最典型的无语义行内标签。它们存在的意义就是作为“万能容器”布局和样式定位。前端基本功不在于“不用div”而在于“不滥用div”。判断标准很简单如果一段内容有一个现成的语义标签能表达就不要用div。比如文章标题就该用h1导航用nav独立的内容单元用article。只有当一个容器纯粹是为了CSS布局分组或者JavaScript操作定位而存在时才该轮到div出场。同样的span一般只用于对单个词、数字做样式微调比如给价格数字换个颜色或者给某段关键词加背景高亮。5.2 语义化标签组header、nav、main、article、section、aside、footerHTML5新增了一批带有明确页面区域含义的语义标签它们让页面结构从“一堆div”变成“一眼看懂的大纲”。header放在页面的顶部区域通常包含网站Logo、主导航、搜索框它也可以用在article内部表示文章的开头标题区。nav专门包裹导航链接集合一个页面可以有多组导航但主要导航应该都收在nav里。main是整个页面独一无二的主体内容区一个页面只能有一个main像侧边栏、页脚都不应该放在里面。article表示一个完整的、可独立分发的内容块比如一篇博客、一条新闻、一个商品卡片。section表示对内容按主题进行分组的区块通常会带上一个小小的标题。aside放与主体相关但非必要的旁支内容比如广告、相关推荐。footer是页脚放版权信息、联系方式、备案号这些收尾内容。这些标签的默认样式几乎没有所以不会像大家担心的那样“影响我排版”。它们的价值在于任何人看到一份HTML源码不需要读内容就能理解哪块是导航、哪块是正文、哪块是版权区。5.3 常见语义标签速查表为了让你更清楚记忆我把最常用的一批标签整理成一张表标签语义典型使用位置默认表现header页头/区块头部页面顶部、article开头块级nav导航区主导航、页脚导航块级main主体内容区页面中间唯一区域块级article独立内容单元博客文章、新闻条目块级section主题分区章节、面板块级aside侧栏/附加信息侧边栏、广告位块级footer页脚/区块尾部页面底部、article结尾块级div无语义容器纯布局容器块级span无语义行内容器局部样式包裹行内使用这些标签时最重要的是沿线组合。可以说header里嵌套navmain里嵌套articlearticle里再嵌套自己的header和footer这种嵌套是语义化结构的常态完全合法。5.4 一个改造案例从div堆砌到语义化我见过太多新手写出的页面长这样“div classheader、div classcontent、div classleft、div classright、div classfooter”。视觉上没问题但搜索引擎无法理解这个页面的结构层次。我给一个最简单的改造对应关系div classheader→headerdiv classnav→navdiv classmain-content→maindiv classsidebar→asidediv classfooter→footer改完之后CSS类名可以完全不改变因为语义标签可以用相同的类名和样式。真正改掉的只是标签本身而这段代码的可读性和无障碍性立刻提升一个档位。如果你正在做一个内容型产品比如博客或新闻网站这种语义化改造非常值得做因为它直接影响搜索引擎如何识别页面重点。6. 标签属性与嵌套规则的避坑清单6.1 布尔属性的真假不是所有属性都要写值HTML里有一类叫“布尔属性”的属性它们只要出现在标签上就表示开启该功能不需要写具体值。最常见的有disabled、checked、selected、readonly、required、multiple。比如input typecheckbox checked和input typecheckbox checkedchecked效果完全一样。但从可维护的角度我建议在团队项目中还是写完整值因为一些代码格式化工具和旧版语法检查器对简写支持不一致。容易出错的是条件渲染场景比如动态生成的表单里用JavaScript控制属性时写el.disabled true否则写el.disabled false千万不能用“删掉属性”来代表false因为只要事件里有任何遗留的逻辑残留属性一旦出现就意味着true。6.2 属性值引号与大小写统一是降低心智负担的最好方式HTML属性值通常用双引号包裹比如classitem。单引号也是合法的但如果你在项目里混用当className和字符串拼接时很容易陷入转义地狱。我的习惯是HTML里永远用双引号只有在JavaScript的模板字符串里需要写动态HTML时才用单引号包属性值。标签名和属性名大小写不敏感浏览器都能识别但HTML规范建议全部小写。实际开发中由于现代构建工具会统一转换大多数人不会碰到大小写问题。真正值得注意的是属性值的空格classitem active表示两个类名classitemactive是完全不同的一个类名写class时不要随手加多余空格也不要忘掉应有的空格。6.3 标签嵌套的硬性规则HTML嵌套有一些“红线”违反了浏览器会默默修正但修正结果往往不是你想要的。第一p标签内不能嵌套块级元素比如pdiv文字/div/p浏览器会自动把/p提前结束结果开闭标签错乱。文本段落里想要局部结构调整可以用span、strong、a这类行内元素。第二ul和ol的直接子元素必须全是li不能直接放div或者p。第三块级元素里嵌套行内元素是允许的但行内元素里不能嵌套块级元素比如a标签里放一个div这在HTML5中默认合法但会有微妙访问性问题一般不建议。第四表单标签form不能嵌套在另一个form里浏览器只认最外层那个。还有一个很多新手会问的a能包img吗当然可以图片也可以做成链接写法是a href...img src... alt.../a。a能包div吗在HTML5支持但要注意这会让可点击区域大幅扩大容易误点所以除非有明确的整卡可点击交互否则还是优先给具体文字和按钮加链接。6.4 面向新手的自查方法写完一个页面后不要急着打开浏览器截图看样式先做三件事。第一打开浏览器开发者工具F12在Elements标签页检查DOM树是否和预期结构一致重点看标签开闭是否匹配、嵌套是否合理。第二用自己的眼睛“朗读”一遍HTML源码如果光看标签不看内容就能说出“这是标题、这是导航、这是正文、这是版权区”说明结构是清晰的。第三可以使用在线的HTML校验器把代码贴进去做一次标准校验它会明确标出漏写的闭标签、不该嵌套的元素、缺失的alt这类问题。最后分享一个我个人比较受用的习惯每次写新页面时我会先在纸上画一画内容区块的层级图然后用语义标签把骨架搭好最后才去填充细节样式。这样做出来的页面无论对浏览器、搜索引擎还是对后续维护的同事都是最友好的。HTML标签这件事说到底是“在正确的位置写正确的标签”规则并不难难的是在每一次落笔时都保持这种意识。