暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

一起学css-精通选择器

技术对话 2022-12-07
278

打算通过一系列分享,和大家一起从头学css,目标如下:
帮助前端人员巩固和深入css相关领域的知识,最后能有不一样的收获
帮助对样式感兴趣的人员从入门到精通再到放弃
帮助自己系统的梳理基础性知识,查漏补缺

为什么是它

先来看一张老图

按照阅读习惯,我们从左往右看到的第一个就是选择器,这也是“主谓宾”中的“主”啊,重要优先程度可想而知。简单理解:选择器就是划定了要作用的样式的对象(群)。通俗的说:她(们)要开始化妆啦~ 那么这些她们都包含了那些人呢?

从问题开始

国际惯例,先抛出一个问题,下图是google首页,要想改变“手气不错”这个按钮样式,有多少种方法找到她改变她?

来,直接上答案!

  • 通过类名:

  • 通过标签名:

  • 通过属性:

  • 通过选择符:

  • 通过伪类:

  • 通过通配符:

  • ………

可以看到,方法是真的不少,但是相对来说,有的方法更准确,有的方法效率更高,有的方法使用更加合理。至于怎么选择,就通过下面的介绍有各位看官自己得出结论吧。

基础走起

选择器种类很多,我们按照用途,写法等的不同来做个简单的分类,定个基调。

  • 一般选择器

  1. Id选择器:#id

  2. 类选择器:.class

  3. 标签选择器:html

  4. 属性选择器:[type=submit]

  5. 通用选择器:*  请注意虽然*涵盖了基本所有的元素,但是并不包含::before和::after伪元素

  • 选择符

  1. 交集关系(无符号):.a.b

  2. 并集关系(,):.a,.b

  3. 后代关系(空格):.a .b

  4. 父子关系(>):.a > .b

  5. 相邻兄弟(+):.a + .b

  6. 兄弟关系(~): .a ~ .b

  7. 列(||):.col || td

  • 伪类

  1. :hover

  2. :focus

  3. :last-child

  4. :is

  5. :where

  6. :…

伪类非常多,数了下超过60个,而且还在不断增加迭代中,有很多伪类都非常的有用,如果感觉在写的样式非常复杂,不妨想想是不是可以用伪类来解决。特点显著:冒号(:)开头

  • 伪元素

  1. ::before

  2. ::after

  3. ::first-line

  4. ::selection

  5. ::…

比起伪类,伪元素就少多了,但也有15个以上,最常用的还是before和after了,特点也很显著:双冒号(::)开头,别跟我说IE!

一、从一般选择器看起,基本上比较的好理解,好使用,唯一有些复杂的就是属性选择器,来个表格规整下其实也就清晰了

写法含义说明
[attr]具有该属性即可[disabled] 不可用元素,vue的scoped样式隔离就是具有属性来实现的
[attr="val"]属性全匹配使用最多的一种,属性值相等
[attr~="val"]部分匹配,注意不是模糊匹配当属性值有多个(空格风格),可以匹配其中一个,如【name~="a"】 可以匹配


[attr|="val"]起始匹配,注意不是模糊匹配顾名思义,从属性开头开始匹配,匹配整个属性或则以“-”分割的前部分,如:[name|="a"] 匹配 name="a",name="a-x",name="b a-x"[name|="a"] 不匹配 name="ab"
[attr^="val"]以val开头的模糊匹配这个好理解了,就是不糊匹配,不过是从头开始,比如[name^="a"] 匹配 name="a",name="ab"[name^="a"] 不匹配 name=“b a”
[attr$="val"]]以val结尾的模糊匹配比较好理解了,就是和模糊匹配放在结尾的
[attr*="val"]包含val的模糊匹配val可在属性任何位置,如:a[href^="http"]:not[href*="epay.163.com"] 匹配外域链接

上表基本上已经把属性选择器的所有用法列完了,那么还有1个问题,属性选择器中的属性值是否区分大小写吗?

以前有个i属性用来忽略大小写[attr^="a" i],但是我实际测试多个浏览器后发现,目前基本已经不区分大小写了,大致可以得到以下结论:

1、ID 和 类 选择器 区分 大小写

2、标签选择器、属性选择器 不区分 大小写

3、伪类和伪元素不区分大小写

二、再来看选择符,其作用就是通过一个元素寻找到其后代或则兄弟元素,非常的好理解,这里就不再重复累诉了。但是大家有没有发现一个问题,所有的选择符都是选择后代或则后面兄弟的,没有对应的祖先父亲选择符,也没有大哥选择符,这是又是为什么?

其实也很简单,不是做不到,是不值得做,根据DOM的渲染规则,我们遵循从上而下的规则,如果有祖先选择器,那么必然会导致回溯,已经渲染好的元素会再次渲染,影响体验和性能

三、其次看伪类,伪类很多很好用,但是并不是那么好记的,所以还是用一个表格先来做个简单的归类吧

名称介绍作用
激活类伪类
(:hover,:active,:focus…)
用户行为相关的一些伪类主要用来在用户操作时附加样式
定位伪类
(:link,:visited,:any-link,:target…)
主要是与url相关的伪类主要用来表述链接的状态,:
输入伪类
(:enabled,:default:checked,:read-only…)
主要是表单类交互的伪类用来在和表单交互时,表单处在不同情况下的样式区别
结构化伪类
(:root,:empty,:first-child,nth-child…)
主要dom树结构关系的伪类用来直接选取某些特种元素或则根据其他节点选择当前元素
逻辑组合伪类
(:not,:is,:where,:has)
如其名,根据关系排查元素通过逻辑判断条件筛选到元素
其他伪类
(:scope,:host,:fullscreen,:dir,:lang…)
作用各异,单独了解作用各异,单独了解

虽然伪类选择器非常的多,但是大部分使用起来难度并不大,灵活的运用,合理的搭配往往能达到很多意想不到的效果。那么接下来就捡其中几个比较有意思的介绍下。

1、:empty 空元素伪类,不含内容(空包括格)不含子元素的元素,需要注意的是会把单标签也给囊括进去,使用场景如下:

避免空元素占位 :

比如有些元素没有内容,但是因为有padding,margin等样式,依旧会在页面上产生一些空白,用这个方法去除是最方便快捷的。

默认数据填充:

,这个很多用在列表查询中,如果有数据显示数据,没数据显示默认值,比起js或则模板判断都更加的简单方便。

2、:not 否定伪类,支持链式写法也支持复杂参数,主要用来更方便的元素匹配,同时配合其他选择器也能起到意想不到的效果,比如

更方面的匹配:

这里主要介绍下用法

tab切换:

tab切换再寻常不过了,我们一般使用js实现,但是用css实现会更加简单

3、:is,:where  任意匹配伪类,之所以把这2个放一起,是因为最终的达到的效果是差不多的,最大的区别可能就是优先级了,is的权重计算值以参数中权重最高的为其权重,而where无论参数是什么,最终的权重计算值始终为0(权重计算值会在下面介绍)。任意匹配伪类在我看来不是一个必选项,他并没有实现一些额外的功能,最大的最用就是精简选择器了,如下如图:

除此之外还可以用来突破一些框架的css作用域而不产生副作用,具体的可以查看文章,里面有较为详细的说明,当然这个也是利用了框架的“漏洞”而不是其本身真有什么特殊能力。

4、:has 具有选择器,可以实现类似父兄选择器的功能。什么!我们前面明明已经得出结论,因为性能问题不会有父兄的选择符的啊,怎么这里又蹦出个父兄的伪类选择器?

Igalia公司啃下了这个硬骨头,通过特定的缓存和过滤优化以及其他高级优化策略,最终解决了性能问题,相信不久的将来,父兄选择符也是会出现的。
,使用起来那就非常的简单了:
/**给”具有img子元素“的a元素添加样式,注意这里的样式是作用到a标签上的!**/
a:has(img){
}

四、最后看伪元素,这里不准备做过多的介绍,除了::before和::after 其他的使用频率并不高。

优先级

介绍完了选择器的种类说明,那么就需要来说说选择器最最重要的一个点:优先级,且看下图(计算值越高优先级也就越高)

等级定义计算值
0级通配选择器、选择符和逻辑组合伪类0
1级标签选择器,伪元素1
2级类选择器、属性选择器和伪类10
3级ID选择器100
4级style属性内联1000
5级!important10000

一般来说,上图的内容已经非常清晰透彻了,一个选择器的优先级,就是通过设定的计算值计算出来的。举个例子:

html body #nav .selected > a:hover

最终计算值为:1(html标签)+1(bod标签)+100(ID)+10(类)+0(选择符)+1(a标签)+10(伪类) = 123

但是我们可能还是有些疑惑的,来看看下面几个问题:

Q1:计算值一样怎么判断优先级

A:后来居上,谁写在后面,谁的优先级就高

Q2:样式和dom层级有什么关系

A:毫无关系,比如html div { } 和 body div { }是完全等效的

Q3:10个低级选择器能不能抵上一个高一级选择器

A:完全不能,目前一个层级的选择器保存在一个16位的空间中,所以如果想要超过上一层选择器,起码得写2的16次方个低级选择器才可以,而我们这里用10倍一般也够用了,因为没有人会写10个以上的同级别选择器,如果真遇到这种奇葩,也得知道,层级不可跨域

拓展延伸

那么到这里,我们是否对于选择器的使用和优先级都非常清晰了呢?不不,还不够,来看看@layer,翻译出来就是”层“,没错,实际也是层的概念,简单理解,就是给一堆样式套个壳变成一个层,然后对这个层进行降维打击,是唯二能降低选择器优先级的(还有一个是:where),总结起来就是有以下几个特点:

  • 未被层级(@layer)包裹的优先级高于被包裹的

  • 不同层级(@layer)之间等级分明,难以跨越

  • 层级(@layer)之间优先级按照后来居上的原则

  • 层级(@layer)中的优先级依据之前的逻辑

  • !important 权柄更大

我们直接通过下面几个题来理解上面的几个特点吧

/* 最终颜色为blue,遵循后来居上原则 */                 
@layer {
    div {
       color: red;
    }
}
@layer {
    div {
       color: blue;
    }
}
/* 最终颜色为red*/
@layer B,A;/**可以先定义后赋值,顺序为后来居上 **/
@layer A {
   div {
      color: red;
   }
}
@layer B {
   div {
      color: blue;
   }
 }
/* 最终颜色为yellow 优先级为yellow->red->blue 被层级包裹的优先级更低*/
div {
  color: yellow;
}
@layer A {
  div {
     color: red;
  }
  @layer B {
     div {
        color: blue;
     }
  }
}
/* 最终颜色为blue 优先级为blue->yellow->red */
div {
  color: yellow;
}
@layer A {
  div {
     color: red;
  }
  @layer B {
     div {
        color: blue !important/** !import的权柄更重,有了他,可以突破@layer的层级限制 **/
     }
  }
}

ok,通过上面的几个例子,应该对@layer层级有了一定的了解了吧,那么样式的优先级是不是到这里就完全搞懂了呢?,来看看下面这个例子吧

/* 优先级为blue->red->yellow 为什么?*/
.test {
    color: red !important;
}
div {
    color: yellow !important;
}
@layer A {
   div {
      color: blue !important;
   }
}

对于这个结果,大家是否感到很困惑?

因为!import 确实是有些反常的,来看一张图就会比较清晰了:

先对图上的名词解释下:

UA:是浏览器默认样式,只要我们代码头部有,浏览器就会加载默认样式,一般就是约定俗称的一些样式,比如给div加个display:block

site:作者样式,也就是我们写的css代码

local:用户样式,浏览器允许用户通过配置给其加载一些样式表,使用频率非常低。可以理解为定制化浏览器默认样式

ok,那回过头来看,样式的优先级非常明确,作者>用户>浏览器,但是一旦加上!import以后顺序就完全相反了,而这其中又把@layer分层给单独罗列了出来。site @layer !import的优先级高于site !important,所以上题中blue优先级最高。而red个yellow因为是在同一个层级(site)中,那么就依据权重计算值来判断了,很明显类(.test)高于标签(div),所以red优先级高于yellow

最后附上大漠老师一张比较完整的图

最佳推荐

基于上面的分析,总结了几个选择器相关的推荐写法,

  • 尽量用class,而不是id 虽然id的性能可能更好,但是差距微乎其微,分工明确,我们还是把id留给js用吧

  • 层次不要太多,层次多了,性能自然就差了

  • 把更精确的写在右边,为了减少回溯选择器的查询规则是从右到左,所以把更准确的写在右边,提升效率

  • 没有理由使用!important,!important 权柄太重,滥用只会让代码变得糟糕

  • 后代选择符比父子选择符更优, 两者性能差距不大,但是后代选择符不用担心dom的微调变更

其实来说,上面那几点都不是那么重要,随着现在浏览器越来越强大,一般的性能问题,代码大小问题其实都已经不是我们要迫切关注的问题了,写的精准,读的明白在我看来才是最为重要的。

总结一下

我们前面其实一直没有考虑兼容性,有不少兼容性并不是非常好,比如前面说到的@layer

但是这个不能成为我们不去学习,不去使用的理由。我相信随着浏览器的迭代更新,越来越多有意思的css功能将被全面支持,我们要做的就是与时俱进,让我们的代码变的更加的优雅可观。


-- End --

点击下方的公众号入口,关注「技术对话」微信公众号,可查看历史文章,投稿请在公众号后台回复投稿


文章转载自技术对话,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论