资讯动态

CSS选择器全攻略:从基础到伪元素,一篇理清所有核心写法

发布时间:2026/9/8 1:27:11 来源:尧图企业网站定制
聊到CSS不管你是刚入行还是已经写了几年都绕不开一个最基础也最核心的概念——CSS选择器。哪怕你用的是Tailwind、Bootstrap这类框架最终落地到浏览器里还是得靠选择器去命中元素。说白了选择器就是CSS的寻址系统选不中样式就写不出去。这篇内容不搞虚的就干一件事把CSS里的各类选择器逐个拆开讲清楚。从基础的通配符、标签、类、ID到进阶的属性选择器、伪类、伪元素再到优先级计算和实际踩坑经验全部过一遍。适合刚接触CSS的新手系统学习也适合写了几年代码但没时间整理知识体系的老手查漏补缺。看完你至少能回答“有哪些CSS选择器分别怎么用”这个问题并且在实战里知道该选哪个、不该选哪个。1. 选择器全景先给整个家族分个类CSS选择器加起来有几十种如果靠死记硬背很快就乱了。我的经验是先分类再逐个击破。按功能可以分成五个大类基础选择器、组合选择器、属性选择器、伪类选择器和伪元素选择器。每一类解决不同的问题先理解分类后面用起来才能对号入座。1.1 五大家族的功能定位基础选择器是最原始的那几个通配符、标签、类、ID。它们的定位是“直接命中目标元素”写法最简单但要小心优先级差异带来的坑。组合选择器解决的是“元素之间的位置关系”比如父子、兄弟、后代。典型代表是后代选择器空格、子元素选择器、相邻兄弟选择器和通用兄弟选择器~。它们的实用价值极高几乎是日常开发的主力。属性选择器通过元素的HTML属性来选比如[typetext]、[href^https]。它在处理表单、链接、动态类名的时候特别管用。伪类选择器核心是“状态与位置”比如:hover表示鼠标悬停状态:nth-child(2)表示第二个子元素。这类选择器让CSS具备了“逻辑判断”能力也是面试最爱问的一块。伪元素选择器则是“造出原本不存在的元素”比如::before、::after能凭空生成内容配合content属性可以做很多花活。1.2 一张速查表把全家福列出来为了让你心里先有张地图我把主流选择器按类别整理成一张表。后面每一类都会展开讲但先看这张表建立整体认知会轻松很多。分类写法示例作用通配符选择器*匹配页面所有元素标签选择器div匹配所有指定标签类选择器.box匹配所有带有该class的元素ID选择器#header匹配指定ID的唯一元素后代选择器div p匹配div内部所有p不管层级深浅子元素选择器div p匹配div的直接子元素p相邻兄弟选择器h2 p匹配紧跟在h2后面的p通用兄弟选择器h2 ~ p匹配h2后面的所有p属性选择器input[typetext]按属性及属性值匹配结构伪类li:nth-child(2)按位置匹配子元素状态伪类a:hover按交互状态匹配否定伪类div:not(.active)排除匹配的元素伪元素p::first-line匹配元素内部的一部分这张表不用背但每次写样式之前可以先想想“我要选的东西是在写标签、还是写状态、还是写位置”思路清晰了选择器自然就选对了。2. 最常用的基础选择器与组合选择器这章节是日常开发的绝对主力。业界统计过大多数项目的CSS里类选择器加后代选择器的使用率能占到80%以上。把这一块吃透你的CSS基础就稳了一大半。2.1 基础选择器逐个拆解通配符选择器*匹配所有元素。看着很爽但实际项目里我基本不用它做全局样式因为* { margin: 0; padding: 0; }这种写法在大型项目里会有性能问题它会遍历页面所有元素。现代实践更推荐用box-sizing配合类选择器做重置或者直接用reset.css里更精细的写法。标签选择器div、p、a等直接命中标签名。适合做全局基础样式比如设置a { color: inherit; text-decoration: none; }、ul { list-style: none; }这类。但注意标签选择器的优先级非常低权重只有1一旦类选择器参与很容易被覆盖。我见过不少新手用div { width: 100px; }去控制某个特定div结果后面加个class就失效了这就是没理解优先级。类选择器.class是日常开发里的“主角”。它的最大优势是复用性一个class可以挂在无数个元素上而且权重适中10不会像ID那样“一锤定音”难覆盖。命名的时候我习惯用语义化名字比如.card、.btn-primary别用.red、.left这种描述外观的后面改主题时会想骂人。ID选择器#id只能匹配一个元素而且权重极高100。我的建议是ID选择器留给JavaScript用或者页面里真正唯一的锚点元素CSS里尽量别用ID去写样式。原因很简单一旦用了ID后面想用类去覆盖它就得写更长的选择器纯给自己挖坑。2.2 组合选择器里藏着的“关系学”组合选择器通过符号连接多个简单选择器表达的是一种“关系”。这块特别重要因为HTML结构本身就是树状嵌套的理解关系才算真正理解CSS。后代选择器空格是用的最多的。div p表示div内部的所有p不管中间隔了几层。但它有个隐患选的范围比想象的大。比如.sidebar p只要p在侧边栏里哪怕嵌在五层深的列表里也会被选中。这对写全局样式很方便但如果你只想控制某一层就得小心了。子元素选择器只匹配直接子元素。div p就只选div的直属p孙子层级的p碰都不碰。这个在控制结构时非常好用可以避免样式意外“漏下去”。比如做导航菜单时ul li能精确控制一级菜单而后代选择器ul li会把二级、三级菜单全部卷进来。相邻兄弟选择器匹配紧跟在某个元素后面的“那一个”兄弟。典型场景是标题后的第一段文字h2 p { margin-top: 0; }去掉标题和紧随段落的间距。这种用法在排版里很常见。通用兄弟选择器~匹配某个元素后面的“所有”兄弟不需要紧挨着。比如h2 ~ p会选中h2后面所有层级相同的p。它和的区别就是“一个”和“一坨”的区别理解了这个用的时候就不会混。2.3 三个高频易错点先说第一个坑空格和的区别。我见过太多人用.card p去控制卡片里的段落结果不小心把卡片外部某个嵌套在p里的文本也带上了样式然后满屏找原因。实际上写.card p就精准得多。建议能写子选择器就写子选择器少用后代选择器这个习惯能减少大量样式泄漏问题。第二个坑 和 ~ 的区别。h2 p只命中一个h2 ~ p命中后面所有。写顺序的时候容易混记住口诀“加号是邻居波浪号是老乡”。第三个坑逗号连接的多选择器不能写错。div, .box是“同时选div和.box”而div .box是“选div里的.box”。一个逗号之差含义天壤之别。写组合选择器时先问自己一句我要表达的是“并列”还是“包含”3. 属性选择器靠属性精准定位的利器基础选择器和组合选择器用得再多也架不住有些场景它们搞不定。比如你想给所有typeemail的输入框加样式用类选择器得在每个input上加class费时费力。属性选择器这时候就派上用场了。3.1 七种属性选择器语法对照属性选择器的核心语法是用方括号[]包裹属性条件。一共七种我分别列一下[attr]只要元素有该属性就匹配不管属性值是什么。[attrvalue]属性值必须完全等于value。[attr~value]属性值以空格分隔的单词列表其中某个单词等于value。典型场景是匹配classa b c这种多类名。[attr|value]属性值等于value或者以value开头并紧跟连字符。典型场景是匹配语言比如[lang|en]能匹配en和en-US。[attr^value]属性值以value开头。[attr$value]属性值以value结尾。[attr*value]属性值包含value子串。看起来很多其实记住“^开头、$结尾、*包含”三个就够用。剩下几个场景比较窄用到再查也行。3.2 实际场景表单、链接、文件类型我举个最常见的组合。现在有个注册表单里面有typetext的用户名、typeemail的邮箱、typepassword的密码你可能想给它们不同的边框颜色。用属性选择器一行代码的事input[typetext] { border-color: #999; } input[typeemail] { border-color: #06c; } input[typepassword] { border-color: #f60; }这种做法比一个个加class干净太多而且以后加新输入框只要type写对样式自动生效。链接场景也很典型。外链和站内链接想用不同样式区分a[href^http] { color: #06c; } a[href^https://] { color: #090; } a[href$.pdf] { color: #c00; font-weight: bold; }a[href^http]能选中所有http开头的链接包括https。a[href$.pdf]能准确识别PDF附件给个醒目的颜色提示用户“点了会下载文件”。这些都是类选择器很难替代的。属性选择器和伪类还能组合。比如:not([disabled])可以选中所有没被禁用的按钮配合hover做交互效果体验很好。3.3 属性选择器的坑第一个坑是[attr~value]和[attr*value]的区别。~要求属性值里包含一个“单词”比如classbtn primary匹配[class~primary]*则是子串匹配>li:nth-child(3n) { background-color: #f5f5f5; }这里3n表示每隔3个取一个即第3、6、9……个元素。公式anb的a是步长b是偏移量。:nth-child(2n1)和:nth-child(odd)效果一样都是选奇数位置。:nth-child(-n3)是选前3个这个写法在“高亮前几条”的场景里特别好用。和:nth-child()对应的还有:nth-last-child()它是从后往前数。有个热搜词“css 除了倒数二个元素”这种需求用nth-last-child很直接。比如你想给一个列表里除了最后两个元素之外的所有元素加下边框可以这么写li:nth-last-child(n3) { border-bottom: 1px solid #eee; }n3的意思是从倒数第3个开始一直往前覆盖。这样倒数第1和第2个元素就没有下边框视觉上形成一个“最后两项是结账信息”的列表。这里要特别提醒一个高频坑:nth-child()是算“所有子元素的位置”不是算“同类型元素的位置”。如果父元素里有p和span混排:nth-child(2)即使第二个元素是span也会命中那个位置的p。如果你只想在同类型里按位置选得用:nth-of-type()它只统计同标签名的元素。我见过无数人在这两个上面栽跟头。4.2 否定伪类与选择列表:not()是CSS里非常实用的逻辑选择器它的作用是“排除指定的元素”。以前的:not()只支持一个简单选择器现在浏览器支持它接收一个复杂选择器列表比如input:not(:disabled) { border-color: #999; } p:not(.intro):not(.highlight) { color: #333; }:not()搭配:nth-child能解决很多“取反”需求。比如还是那个“除了倒数二个元素”的场景也可以写作li:not(:nth-last-child(-n2)) { border-bottom: 1px solid #eee; }这种写法更直观“排除最后两个”。两种写法效果一样用哪个看你个人习惯。我个人的感觉是:nth-last-child(n3)性能上略好一点点但:not()的可读性更强团队协作时不容易被误解。另外还有两个现代选择器:is()和:where()也值得掌握。:is(div, .box, #app)会取列表中任何一个匹配的元素但它有个特性优先级取参数中最高的那个。:where()也做同样的事但优先级永远是0不会影响具体某个选择器的权重非常适合用来做“零侵扰”的默认样式。4.3 状态伪类交互和表单的“表情”状态伪类主要配合交互使用。:hover是鼠标悬停:active是鼠标按下时:focus是元素获得焦点:focus-within是元素本身或它的子元素获得焦点。:focus-within很实用比如一个搜索框内部输入时想给整个搜索框容器加个边框高亮只写:focus是做不到的因为焦点在input上容器没有获得焦点。写成.search-wrap:focus-within就完美解决。表单相关的状态伪类也很常用:checked选中、:disabled禁用、:required必填、:valid通过验证、:invalid未通过验证、:placeholder-shown占位符显示中。举个经典场景输入框为空且是必填时显示一个红色边框填了内容之后恢复input:invalid { border-color: #c00; } input:valid { border-color: #090; }这只是伪类选择器的冰山一角。真正的实战中伪类还经常配合transition和animation实现各种交互效果比如热搜词里的“css卡片堆叠动画效果”核心逻辑就是靠:nth-child给每张卡片不同的初始位置和层级再配合:hover触发位移和旋转。选择器在这里不是配角它就是整个动画的结构骨架。5. 伪元素选择器凭空创造内容的魔法伪类和伪元素名字容易混但性质完全不同。伪类修饰的是“元素的状态”伪元素则像是往页面里“造”了一个原本不存在的元素。伪元素的写法是双冒号::比如::before、::after。老代码里也有单冒号的写法现在浏览器两种都兼容但规范推荐写双冒号个人也建议统一双冒号。5.1 before / after 与 content 绝配::before在元素内容的最前面插入一个行内元素::after在元素内容的最后面插入。它们的核心是content属性没有content伪元素就不会显示。content可以写字符串、可以写attr()取属性值、也可以留空配合背景图实现装饰。最常见的用法是做“小尾巴”比如三角形箭头。用CSS画三角形的原理很简单一个宽高为0的元素四条边框分别设置宽度和颜色剩余三条边设透明色就形成了三角形。配合::after可以直接给文字加个气泡尾巴.tip::after { content: ; position: absolute; top: 100%; left: 20px; border: 8px solid transparent; border-top-color: #333; }热搜词里那个“css三角形”其实也是这个原理。伪元素之所以比直接用div画三角形好是因为它不污染HTML结构可读性好还方便通过::after的定位来控制气泡位置。再比如“css涟漪光圈扩散”效果点击按钮后扩散的那一圈圈波纹实现思路就是在按钮上挂一个::before作为波纹层设置border-radius: 50%配合animation: ripple通过scale和opacity的变化形成向外扩散的效果。如果不用伪元素就得多写一个span标签既丑又难维护。5.2 词级伪元素针对性排版::first-letter用来控制元素的首字符常见于文章排版实现“首字下沉”效果p::first-letter { font-size: 3em; float: left; margin-right: 6px; }::first-line控制第一行文字一般用来设置首行加粗或颜色。注意这两个伪元素只对块级元素生效而且对中文这种没有空格分隔的语言::first-letter会按“字符”处理效果依然很灵。::selection控制用户选中文本时的背景色和文字颜色这个特别适合品牌感强的网站统一视觉。比如::selection { background: #ffe066; color: #333; }::placeholder控制表单输入框的占位符颜色但注意它没有标准属性各浏览器需要加前缀比如::-webkit-input-placeholder。5.3 伪元素和伪类的组合实战伪元素和伪类组合起来能做很多复杂的效果。比如做一个卡片hover时底部出现高光线最常见写法.card::after { content: ; position: absolute; left: 0; right: 0; bottom: 0; height: 3px; background: linear-gradient(90deg, #06c, #09c); transform: scaleX(0); transition: transform 0.3s; } .card:hover::after { transform: scaleX(1); }没有伪元素这里就必须多个div元素而有了伪元素卡片hover的高光动画就非常干净地实现了。同理液态玻璃质感的卡片呼应热搜词“css液态玻璃”通常也是用::before做背景模糊层加上backdrop-filter: blur()再叠加一个半透明的::after做高光才能出现那种“水润”的质感。6. 优先级、性能与规避踩坑策略很多人在选择器上写“翻车”不是因为不会用而是因为优先级没搞清楚导致样式被覆盖自己还找不出原因。这一章节把优先级算清楚再聊一些性能上的实用建议。6.1 优先级计算规则浏览器判断到底使用哪个样式声明靠的是优先级权重。规则很简单行内样式 ID选择器 类/属性/伪类选择器 元素/伪元素选择器。数字上通常记作行内1000ID 100类/属性/伪类 10元素/伪元素 1。通配符*权重为0。举个例子#nav .item:hover a { color: red; }这个选择器的权重是ID100 类10 伪类10 元素1 121。如果要覆盖它你后面必须写一个总权重超过121的选择器。优先级相同时后写的生效。实际开发中优先级的坑主要集中在“想覆盖插件样式”或“覆盖框架样式”的场景。比如Bootstrap里的btn-primary你写.btn-primary { background: red; }发现怎么都不生效因为Bootstrap自己也定义了.btn-primary而且加载顺序里你的样式可能在它之前。这时候正确做法是加一个body前缀body .btn-primary { background: red; }body是标签选择器权重为1整体权重变成11超过了Bootstrap的10。这个花招在覆盖UI库时非常管用比!important干净得多。6.2 性能建议别让选择器成为卡顿源头很多人以为CSS性能是浏览器的事跟自己没关系。实际上笨重的选择器在超大页面里确实会拖慢渲染。浏览器匹配选择器是从右往左进行的比如.content .box p浏览器会先找所有p再过滤祖先里有没有.box最后看.content。如果你的选择器写得很长每一层都要过滤一遍成本自然高。实用建议有这几条不要写超过三层以上的后代选择器一看就是结构太深。避免使用通配符*做全局匹配尤其别在关键路径上写* { box-sizing: border-box; }这种性能差的浏览器会卡。类选择器优于标签选择器.btn比div.btn性能高。避免在很深的层级写ul li ul li a这种完全可以用class来做。实际上现在的浏览器优化已经越来越强选择器性能在绝大部分项目里不是瓶颈。我更看重的是可维护性选择器太深说明你的HTML结构可能该重构了选择器堆太厚说明你的类名设计有问题。记住好的CSS不是写得有多炫而是删起来不心疼、改起来不出错。6.3 优先级实战一个覆盖与被覆盖的例子我做一个活动页时要覆盖第三方UI库的弹窗按钮样式。UI库的源码大概是.modal .modal-footer .btn-primary { background: #007bff; }这个选择器权重是3个类选择器 30。如果我只写.btn-primary权重只有10必输。我的做法body .modal .modal-footer .btn-primary { background: linear-gradient(135deg, #667eea, #764ba2); }权重body元素1 3个类30 31刚好压过UI库的30成功覆盖。注意这里我没用!important因为这会把优先级变成无限大以后想再覆盖就得一直用!important恶性循环。能用权重解决就别用!important。7. 高频面试题与避坑速查CSS选择器是前端面试的常客很多人的理论基础其实在这块是有漏洞的。我把常考的、常错的几个点整理出来顺便把排查思路也带上。7.1 几个必会面试题拆解问请说出div p、div p、div p、div ~ p分别表示什么这道题基本等于送分题但很多人会在和~上混淆。div p是div内部的所有pdiv p是div的直接子元素pdiv p是div后面紧邻的第一个pdiv ~ p是div后面的所有p。回答时最好默认补一句“用于同级或父子结构关系里”。问:nth-child(3)和:nth-of-type(3)有什么区别核心区别是:nth-child按元素在所有子元素里的序号计算:nth-of-type按同类型同标签名元素的序号计算。如果父元素只有p一种标签两者效果相同如果混用了span和p结果完全不同。面试官等这个。问怎么选出一个列表里除了最后两个以外的所有元素用:nth-last-child(n3)或者:not(:nth-last-child(-n2))。能写出两种方案再加一句“前者性能略好”就非常加分。问CSS选择器优先级怎么算行内1000、ID 100、类/属性/伪类10、元素/伪元素1。注意通配符0。强调!important会覆盖一切但能不用就不用。7.2 实际开发里的排查实录我在项目里遇到过几次典型的“选择器玄学”问题整理出来供参考。第一次是写.box :hover结果鼠标移到box内部的空白区域也能触发内部元素样式。原因是后代选择器加伪类实际上匹配的是“box内所有处于hover状态的元素”而不是“box处于hover时为了子孙设置的样式”。正确的需求应该写成.box:hover span选“box悬停时内部的span”。第二次是:nth-child在表格里使用始终不生效。排查半天发现表格里有thead和tbody两个容器nth-child计算的是tr在各自父容器里的序号并不是整个表格的全局行号。这种场景就得考虑把thead和tbody当成独立容器来处理。第三次是用属性选择器匹配>

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价