CSS基本功从头练之Selector

3,245 阅读12分钟

CSS一直是我的短板,从来没有系统学过,一直都是使用第三方的样式库,或者在网上找点资料copy。最近感觉应该系统的梳理一下,否则很多概念还是不太清晰。还是属于边学边写的资料,难免各种漏洞,希望大家多指正。

Selector(选择器)

为什么会有选择器这个概念?因为样式最终是要应用到HTML的元素上的,那么哪一类样式应该应用到什么元素这个问题就催生了选择器的诞生。总体来说,Selector分标签选择器、类选择器、ID选择器和属性选择器

标签选择器

顾名思义是以HTML标签作为筛选依据的,比如下面的代码中,我们的可见标签一共有:<html><body><div><label><input><output>

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
  <div>
    <label for="weight">体重</label>
    <input id="weight" type="number">
  </div>
  <div>
    <label for="height">身高</label>
    <input id="height" type="number">
  </div>
  <div>
    <label for="bmi">BMI</label>
    <output id="bmi"></output>
  </div>

</body>
</html>

如果我们在css中有下面的定义,那么我们其实是对所有 <body> 标签(虽然事实上只有一个)应用样式背景色 #DCEDC8,而对所有 <input> 标签应用样式背景色 #8BC34A

body {
  background-color: #DCEDC8;
}

input {
  background-color: #8BC34A;
}

应用body和input标签样式的效果

类选择器

类选择器的筛选标准是用类来确定的,就是在html标签中,我们一般可以用 class="blablabla" 来完成样式的类指定。现在我们改写HTML,把所有label指定一个 class="label"

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
  <div>
    <label class="label" for="weight">体重</label>
    <input id="weight" type="number">
  </div>
  <div>
    <label class="label" for="height">身高</label>
    <input id="height" type="number">
  </div>
  <div>
    <label class="label" for="bmi">BMI</label>
    <output id="bmi"></output>
  </div>

</body>
</html>

然后在css中加上一个类样式定义,在css中我们在类名的前面加上一个 . 来表示这是个类样式定义。

.label {
  color: #fff;
}

使用label类样式定义后的效果

ID选择器

我们看到上面的HTML代码中,有一些元素是有 id 这个属性的,那么这个选择器当然就是用来选择 id="blablabla" 的元素的。如果我们在css中给BMI结果加入一个ID选择器,放大结果的字体。

#bmi {
  font-size: xx-large;
}

应用了ID选择器的效果

属性选择器

属性选择器就是对HTML标签中含有的属性进行筛选,比如上例中我们如果想对标签中 for 属性、值为weight的进行特殊处理,那么我们可以看到下图的效果。

label[for="weight"] {
  color: #FF4081;
}

应用属性选择器的效果

其实我感觉前面的ID选择器、类选择器甚至标签选择器都应该是属性选择器的一种特殊形式,比如我们其实把 #bmi 那段代码注释掉,换成属性选择器:选择 id="bmi" 的形式效果是一样的。

/* #bmi {
  font-size: xx-large;
} */

output[id="bmi"] {
  font-size: xx-large;
}

属性选择器的花样最多,下面我们来看看,如果不用 =,而使用 ~= 是什么效果。我们把bmi的label中的for改成 for="weight height"

  <div>
    <label class="label" for="weight height">BMI</label>
    <output id="bmi"></output>
  </div>

然后在css中用 ~= 替代 = 看看会发生什么?我们看到体重和BMI都应用了 #FF4081 这个颜色。也就说 ~= 表示选择所有含有等号后面的值的元素。

label[for~="weight"] {
  color: #FF4081;
}

使用 `~=` 代表选择含有该值的所有元素

但是注意一点,~= 后面的一定是一个完整的属性值,而不是含有这个字符串就行,比如上面HTML中如果我们写成 for="weight1 height" 那么这个选择器就无法生效了。

css1中还支持一种符号 |=,它会选择所有以等号后的值开头的元素,同样的,这里的开头指的是完整属性值而不是字符串意义上的开头。那么问题来了,有没有办法选择字符串呢?css3中增加了 ^=$=*= 这几个符号,是专门处理字符串这种需求的。题外话感觉这几个操作符很像正则表达式中的。

还是上面的例子,我们的html如下,注意bmi的label的for中是 weight1,而体重label的for中是 weight

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <script src="https://unpkg.com/@reactivex/rxjs@5.0.0-beta.7/dist/global/Rx.umd.js"></script>
  <title>JS Bin</title>
</head>
<body>
  <div>
    <label class="label" for="weight">体重</label>
    <input id="weight" type="number">
  </div>
  <div>
    <label class="label" for="height">身高</label>
    <input id="height" type="number">
  </div>
  <div>
    <label class="label" for="weight1 height">BMI</label>
    <output id="bmi"></output>
  </div>

</body>
</html>

css中我们使用 ^= 来试验一下,你会发现选择器又生效了。

label[for^="weight"] {
  color: #FF4081;
}

使用 `^=` 选择以字符串xxx开头的元素

同样的如果我们换成下面的css,你会发现所有的label都变色了,因为都是 eight这个字符串结尾的

label[for$="eight"] {
  color: #FF4081;
}

`$=`选择以字符串xxx结尾的所有元素

这时你会问,不对啊,bmi的label是 weight1 啊,但是请看仔细 for="weight1 height",这个字符串的最后是 height

*= 是选择字符串中包含某个值的元素,遇到它的话我比较好奇的是它能不能选择空格,因为 for="weight1 height"中含有一个空格。于是我做了个实验,用如下的css证明了是可以选择空格的。

label[for*=" "] {
  color: #FF4081;
}

`*=`选择包含xxx字符串的元素

上下文选择器

如果仅有上面这些也还不错,css的强大或者说复杂在于还特么能组合。这个上下文选择器可以看作组合的一种,它利用多种符号连接标签选择器来实现根据上下文选择元素的目的。

还是用实例说话,首先最简单的组合符号 ,,为了看的更清楚一些,我们重新定义HTML:在最外面包了一层 div,并且给每一层 div 都指定了一个样式类。

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <script src="https://unpkg.com/@reactivex/rxjs@5.0.0-beta.7/dist/global/Rx.umd.js"></script>
  <title>JS Bin</title>
</head>
<body>
  <div class="content">
    <div class="weight">
      <label class="label" for="weight">体重</label>
      <input id="weight" type="number">
    </div>
    <div class="height">
      <label class="label" for="height">身高</label>
      <input id="height" type="number">
    </div>
    <div class="bmi">
      <label class="label" for="weight1 height">BMI</label>
      <output id="bmi"></output>
    </div>
  </div>
</body>
</html>

css 中为几个 div 定义背景色,以便看的更清楚些。然后利用 , 定义了 div,label 的padding是 20px

...
.weight {
  background-color: gray;
}

.height {
  background-color: silver;
}

.bmi {
  background-color: darkgray;
}

.content{
  background-color: brown;
}

div,label {
  padding: 20px;
}

, 这种就比较简单,就是多个标签都使用这个样式,我们来看看效果:

采用了`,`表示“和”

只有div时的效果,仔细看会发现文字靠左边缘近了一些

只有label时的效果

这个操作符我认为只起到节省代码的作用;-)

后代选择器 (空格)

下面我们看看 (空格)这个操作符,还是为了更明显,我们在HTML中的最外层div之外加一行

<label for="something">我不在div中,所以没有变大</label>

我们在css中加上下面的定义,我们会看到,div内部的所有label的文字都变大了,但外部的没有。所以 x y表示选择在x元素内部的所有y

div label {
  font-size: x-large;
}

` `空格表示在div内部的所有label

子选择器 >

那么下面我们看看 >,同样需要改造一下HTML,在 <div class="content"> 下加入下面的代码。

<p>
  <label for="something">我的父节点不是div,所以没有变大</label>
</p>

将css改成

div>label {
  font-size: x-large;
  padding:20px;
}

下面看看效果,刚刚加入的那个label文字没有变大的原因在于它的父节点是 <p>

`div>label` 符号筛选父节点为div的label元素

“亲兄弟”选择器 +

接下来的这位就有意思了 +,你如果把 div+label 直接套用到上面的HTML中,会发现没什么作用,哪个字体也没变大。原因是这个符号是选择div 之后紧挨着 div的label元素。

那么我们的实验之前,请把下面的代码插入到最外层div之后

<label for="something">我不在div中但紧挨着div,所以变大了</label>
<label for="something">我虽然在div后但没有紧挨着div,所以没有变大了</label>

`+` 指的是紧挨着前面的元素的所有元素

"兄弟连"选择器 ~

有看后面的就有看前面的,接下来就是 ~ 闪亮登场了,div~label 是个什么效果捏? 事实证明 x~y 是用于选择前面有 x 的所有 y

div~label {
  font-size: x-large;
  padding:20px;
}

`div~label` 用于选择之前有div的所有label

伪类

为什么叫这个名字?我也不知道,感觉有点怪,听说一个说法是它们很像类,但我真没看出哪儿像。伪类一般都是以 : 开头,也不光是伪类的,伪元素也是 : 开头。

链接伪类

首先看链接伪类,这个其实没什么好讲,就是因为链接有几个状态,普通的选择器无法筛选出来导致硬生生的造出来这几个东东。需要注意的是,这些货必须按着下面的顺序给出才可以生效。其实我实验了一下,link和visited调换位置没影响,其他按顺序是因为这几个状态有时有重叠,写错顺序会导致样式被覆盖。比如鼠标按下未抬起其实同时也满足hover的条件,如果我们把hover放在最后,active放在前面,那么这时就会以最后的样式(hover)覆盖前面的样式(active)

  • link: 没有访问过也没有鼠标悬停或点击的状态。
  • visited:用户点击过这个链接之后的状态。
  • hover:鼠标指针正悬停在连接上。
  • active:链接正在被点击(鼠标在元素上按下,还没有释放)。

“选中状态”伪类

再来看 :focus 伪类,这个也是由于输入框处于聚焦状态时无法通过其他选择器选取而特别定制的,我们看一下效果

input:focus {
  background-color: #C5CAE9;
}

体重输入框聚焦的状态

同理,还有类似的对于复选框、单选框等选中状态的选择所设计的 :checked;为元素禁用和激活状态所设计的 :enabled:disabled。这些就不举例了,比较简单,如果有坑,请指教。

“排头兵”伪类

:first-letter:first-line:first-child 这几个跟在某个元素后面的作用是选取该元素的第一个文字(英文的话是字母)、第一行和父元素的第一个子元素中的所有该元素。还是看图说话吧。
我们把体重的selector加了一个首字母伪类放大字体200%,效果如下图,另两个的效果类似,只不过是第一行或者第一个子元素,自己可以实验一下。

.weight:first-letter {
  font-size:200%;
}

首字大写的效果

“前缀”和“后缀”伪类

为什么叫它们“前缀”和“后缀”呢?因为它们是作用于选择内容之前或之后的,为什么要设计这个选择器呢?我们的BMI计算器目前的两个输入值都是没有单位的。如果我们希望增加一个单位的话,当然可以在HTML页面增加,但是如果对于某一类值它们的单位就是某些文字的话,这么做就有点累。我们给css加上下面的样式看看效果。

.weight::after {
  content: "公斤";
}

.height::after {
  content: "厘米";
}

另外一个比较常见的技巧是利用这个伪类去改造默认的控件样式,比如复选框默认是方块型,如何改造成圆形呢?我们就可以先把复选框中的原有表现形式(方块)隐藏起来,然后利用“后缀”伪类画出一个圆来。

//html
<input type="checkbox" class="check">
//css
.check {
  background-color: #8BC34A;
  width: 40px;
  -webkit-appearance: none;
  appearance: none;
}
.check::after {
  content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#fff" stroke-width="3"/></svg>');
}

圆形复选框

“正着数和倒着数”伪类

css增加一堆很类似数组或集合中常用的选择器,这一类的大部分都是成对的,可以记成正着数和倒着数。为了方便记忆,我就起名叫“正着数和倒着数”伪类了。这一类有好多,包括选择首尾的: :first-of-type:last-of-type:first-child(这个不是css3引入的),:last-child;第n个子元素的 :nth-child(n):nth-last-child(n);第n个某类型子元素:nth-of-type(n):nth-last-of-type(n);当然对于特殊情况,比如只有一个的会有专门的处理::only-child:only-of-type

应用这种选择器可以非常方便的打造一些效果,比如交替颜色的表格。

table {
    width: 100%;
}

tr:nth-child(even){background-color: #f2f2f2}

交错颜色的表格

这里有问题了,这个 even 是怎么回事?这一类Selector是可以接受表达式作为参数的,而 evenodd 作为两个特别常用的东东被认定成关键字,其实 nth-child(even) 可以改写成 nth-child(2n)nth-child(odd)可以改写成 nth-child(2n+1)。是的,如果你愿意可以写出3的倍数,5的倍数等等序列等等。