CSS 基础

CSS 基础知识及常用拓展

CSS选择器

标识案例 名称 说明
* 通用选择器
选择所有元素
p 元素选择器 按照给定的节点名称,选择所有匹配的元素
.hello 类选择器 按照给定的类名,选择所有匹配的元素
#world ID选择器 按照给定的ID,选择唯一匹配的元素
[attr=value] 属性选择器 按照给定的属性,选择所有匹配的元素,匹配规则在下面
div,span 选择器列表 使用,将不同组合器组合在一起,选择能被列表中的任意一个选择器选中的节点
div span 后代选择器 使用空格选择前一个元素的后代节点
div>span 直接后代选择器 使用>选择前一个元素的直接子代的节点
div~span 一般兄弟选择器 使用~选择兄弟元素,后一个节点在前一个节点后面的任意位置,且共享同一个父节点,例如 div~span 匹配同一个父元素下,<div>元素后的所有<span>元素
div+span 相邻兄弟选择器 使用+选择该节点之后的兄弟元素,后一个节点紧跟前一个节点,并且共享同一个父节点

属性选择器提供了多种语法来实现复杂匹配:

  • [attr]:标识带有以 attr 命名的属性的元素;

  • [attr=value]:标识带有以 attr 命名的属性,且属性值为 value 值的元素;

  • [attr~=value]:标识带有以 attr 命名的属性,且该属性是一个以空格作为分割的值列表,其中至少有一个属性值为 value 值的元素;

    举个例子:

    <div lang="en-us en-gb en-au en-nz">Hello World!</div>
    

    这里就可以通过 div[lang~="en-gb"]来匹配到上面的元素

  • [attr|=value]:标识带有以 attr 命名的属性,且属性值为 value 或者 value- 为前缀的元素;

    举个例子:

    <div lang="en-us en-gb en-au en-nz">Hello World!</div>
    

    这里可以通过 div[lang|="en"]来匹配上面的元素,该匹配规则常用于匹配语言简写。

  • [attr^=value]:标识带有以 attr 命名的属性,且属性值是以 value 值开头的元素;

  • [attr$=value]:标识带有以 attr 命名的属性,且属性值是以 value 值结尾的元素;

  • [attr*=value]:标识带有以 attr 命名的属性,且属性值至少包含一个 value 值的元素;

    <div lang="nononoen">Hello World!</div>
    

    使用[lang*="en"]能够匹配到上面的元素

  • [attr operator value i]:operator 为上述的匹配表达式,在 value 后添加 i 标识符就能够忽略值的大小写;

    举个例子:

    <div lang="ABC">Hello World!</div>
    

    使用[lang="abc" i]能够匹配到上面的元素

CSS 优先级

浏览器根据 CSS 的优先级来决定给元素应用哪个样式

选择器的优先级从高到低:!important >内联样式 > ID选择器 >【类选择器、属性选择器、伪类、伪元素】> 元素选择器 > 通用选择器

对于多选择器的情况,就需要计算其权重来决定应用哪个样式

样式的权重 = 内联样式 * 1000 + ID选择器的数量 * 100 + 类选择器的数量 * 10 + 元素选择器的数量 * 1

a{ color: red }  /* 权重 10 */
#box a{ color: green }  /* 权重 110 */

需要注意的是

  • 优先级无法通过权重升位,例如:1个ID选择器与20个类选择器组成的集合作比较,此时类选择器的权重是 200 高于ID选择器,但是样式仍然由 ID 选择器决定,低优先级的选择器始终无法覆盖高优先级的选择器。

  • 如果当计算出的权重相等时,后面的样式会覆盖前面的样式。

  • !important 拥有最高优先级,如果要覆盖其样式,可以使用更高优先级的选择器。

百分比参照物

在CSS中使用百分比首先需要知道百分比相对于谁,这里罗列了一些常见的CSS样式:

  • 相对于父级的宽的属性:

    widthmin-widthmax-widthleftrighttext-indentmarginpadding

    需要注意的是:这里的宽是指 content-area,并不包含 padding / margin / border,即使父元素设置了 box-sizing : border-box。

  • 相对于父级的高的属性:

    heightmin-heightmax-heighttopbottom

  • 相对于自身宽高的属性:

    border-radiusbackground-sizetransformtransform-originzoom clip-path

  • 相对于自身字号:

    line-height

Flex 计算方式

flex-grow 计算方式

假设有三个元素 width 属性值分别为:100、150、100,各自的 flex-grow 值为 1、2、3,父元素的宽度为 500,此时三个元素的实际宽度时多少?

flex-grow 是在原有的宽度基础之上分配多余的空间,父元素多余的空间是 500-(100+150+100) = 150,然后 150 被等分成 6 份,添加到各自的元素上,所以最后三个元素的增加的宽度分别为:

(1/6) x 150 = 25;
(2/6) x 150 = 50;
(3/6) x 150 = 75;

但如果三个 flex-grow 的值之和小于 1 呢?例如 flex-grow 的值分别为 0.1、0.2、0.3,那么多余的空间不会被全部分配到三个元素上,三个元素的宽度分别为:

150 x 0.1 = 15;
150 x 0.2 = 30;
150 x 0.3 = 45;

flex-shrink 计算方式

flex-shrink 默认值为 1,每个元素的收缩权重是其 flex-shrink 的值乘以其宽度。

假设有三个元素 width 属性值分别为:150、200、300,各自的 flex-shrink 值为 1、2、3,父元素的宽度为 500,需要被收缩的宽度为 150,按照上面的计算方式,得出三个元素的总权重为:

150 x 1 + 200 x 2 + 300 x 3 = 1450

每个元素被收缩的宽度为:

150 x (150 x 1 / 1450) = 15.5
150 x (200 x 2 / 1450) = 41.4
150 x (300 x 3 / 1450) = 93.1

如果三个 flex-shrink 的值之和小于 1,例如各自的 flex-shrink 值为 0.1、0.2、0.3,第一步还是计算总权重,得出 145,因为 flex-shrink 的值之和小于 1,所以多出的 150 宽度并不会被全部收缩,只会收缩一部分:150 * (0.1+0.2+0.3) = 90,那么三个元素被收缩宽度分别为:

90 x (150 x 0.1 / 145) = 9.3
90 x (200 x 0.2 / 145) = 24.8
90 x (300 x 0.3 / 145) = 55.8

怪异盒模型

怪异盒模型:IE5/6将内边距和边框计入宽度和高度,我们称之为怪异盒模型;

标准盒模型:width 和 height 指的是内容区域(content-area)的宽度和高度。增加内边距、边框和外边距不会影响内容区域的尺寸,这是W3C指定的标准模型。

通过设置 box-sizing 属性,可以将标准盒模型转换为怪异盒模型。

灰度转换

filter: grayscale(amount) : 对元素进行灰度转换,amount 介于 0 到 1 之间,默认为 0,可用作禁止访问效果。MDN

水平/垂直翻转

scaleX(-1)或者scaleY(-1)可以实现基于水平/垂直翻转

.flipx{
    transform:scaleX(-1); // 水平翻转
}

.flipx{
    transform:scaleY(-1); // 垂直翻转
}

inline、block、inlineBlock

inline(行内元素):

  • 行内元素不会独占一行,多个相邻行内元素会排列在同一行里,直到一行内排列不下才会换行,其宽度随内容的变化而变化;
  • 行内元素无视 width 和 height 属性;
  • 行内元素无视竖直方向的 margin 和 padding 属性,但是水平方向的 margin 或 padding 会生效;

block(块级元素):

  • 块级元素会独占一行,其宽度默认填满父级元素;
  • 块级元素可设置 width 和 height 属性;
  • 块级元素水平和竖直方向的 margin 和 padding 属性都会生效;

inline-block(行内块级元素):

  • 除了不会独占一行,其他特性和块级元素保持一致;

自适应图片的实现

根据DPR选择

如果统一使用普通分辨率的图片,在 Retina 屏幕下会显得很模糊;如果统一使用高分辨率的图片,则会增加带宽的压力。所以不同分辨率的屏幕应该使用不同分辨率的图片以达到最好的显示效果,要实现这个效果就需要使用 srcset 属性:

<img src="photo.png" srcset="photo@2x.png 2x" /> 

根据尺寸选择

根据DPR选择图片的写法简单,但是容易出错,例如:iphone 和 macbook 的 DPR 都是 2,但是 iphone 只需要 828px 宽度的图片就可以全屏展示,但是 macbook 需要 2880px 宽度的图片才能够实现全屏展示。这时候就需要根据实际需要尺寸选择图片,将 dpr 换做实际宽度:

<img src="https://via.placeholder.com/600"
     srcset="https://via.placeholder.com/600 600w,
             https://via.placeholder.com/1200 1200w,
             https://via.placeholder.com/1800 1800w,
             https://via.placeholder.com/2400 2400w" /> 

浏览器会根据屏幕的逻辑像素宽度(screen.width)乘以 DPR 得出的结果,选取最佳尺寸的图片,例如屏幕宽度为 1080px,DPR为2,那么计算的结果为 2160,与 2400 最为接近,所以会选取图片 photo2400w。

不过这种写法只适合全屏图片显示的场景,因为设备宽度是恒定不变的,所以图片不会随着可视宽度的变化而变化。如果要根据视口宽度而不是设备宽度来显示对应图片,那就需要再给 img 标签加上 sizes 属性,sizes 最小单位是由媒体查询语句和对应的图片大小构成的字符串,多个单位用逗号进行分隔

<img src="https://via.placeholder.com/600" 
     srcset="https://via.placeholder.com/600 600w,
             https://via.placeholder.com/1200 1200w,
             https://via.placeholder.com/1800 1800w,
             https://via.placeholder.com/2400 2400w"
     sizes="(max-width: 600px) 300px, (max-width: 1200px) 600px, (max-width: 1800px) 900px, (max-width: 2400px) 1200px, 600px"/> 

上述的 sizes 属性表示的是:当视口宽度小于 1200px 且大于 600px 时,图片的宽度会被设置成 600px,如果设备的 DPR 为 2,那么计算结果就是 600 * 2 = 1200,就会选择 1200w 对应的图片。

在实际开发中时,发现如果视口从小到大变化,图片会自适应加载更大的图片,但是从大到小变化时并不会加载更小的图片,这是因为浏览器在拥有更高的分辨率的图片时,就会忽略质量更低的图片

picture 标签

上面方法解决了尺寸和 DPR 自适应的问题,但是可读性太差,有没有可读性更好的写法?

有!<picture/> 标签能将尺寸配置和 DPR 配置分割开来,具有更好的可读性,需要注意的是 picture 标签内只能存在一个<img/>标签。

<picture>
  <source media="(min-width: 800px)" srcset="head.jpg, head-2x.jpg 2x">
  <source media="(min-width: 450px)" srcset="head-small.jpg, head-small-2x.jpg 2x">
  <img src="head-fb.jpg" srcset="head-fb-2x.jpg 2x" alt="a head carved out of wood">
</picture>

背景图片的DPR自适应

上面提到的图片自适应都是基于<img/>标签的,但很多场景中需要用到背景图片,这时候就可以使用image-set()函数来加载不同版本的图片,需要注意的是,image-set()函数目前只有 Chrome 和 Safari 浏览器进行了兼容。

background-image: image-set(
  url(icon1x.jpg) 1x,
  url(icon2x.jpg) 2x  /*在DPR为2时,会加载图片 icon2x.jpg*/
);

媒体查询

通过媒体查询中的min-resolution也可以实现高分辨率图片的加载,需要注意的是,min-resolution目前只有 Chrome 、Safari 和 Firefox 浏览器进行了兼容。

@media (min-resolution: 2dppx),
(-webkit-min-device-pixel-ratio: 2)
{
  /* 高分辨率的图片资源 */
}

CSS Modules

CSS Modules 是使用构建工具(例如 webpack),实现样式局部作用域的效果,用于避免样式间的污染,举个例子:

将 CSS 文件引入

import type from "./type.css";

element.innerHTML = 
  `<h1 class="${type.display}">
    This is a heading
  </h1>`;

在 webpack 的配置中打开 css-loader 的 modules 选项

  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          { loader: 'style-loader' },
          {
            loader: 'css-loader',
            options: {   
                modules: true
            }
          }
        ]
      }
    ]
  }

构建工具会将以下 CSS 代码和类名进行转换

/** type.css **/
.serif-font {
  font-family: Georgia, serif;
}

.display {
  font-size: 30px;
  line-height: 35px;
}

转换后的结果如下,所有的类名都变成了哈希值

._3Ukt9LHwDhphmidalfey-S {
    font-family: Georgia, serif;
}

._3XpLkKvmw0hNfJyl8yU3i4 {
  font-size: 30px;
  line-height: 35px;
}

<h1 class="_3XpLkKvmw0hNfJyl8yU3i4">
    This is a heading
</h1>

如果我们需要让类名更可读一些,可以配置 css-loader 的 localIdentName 属性

{ 
  loader: "css-loader",
  options: {
      modules: true,
      localIdentName: '[path][name]__[local]--[hash:base64:5]'
  }
}

此时生成的就是类似 src-style-index__display-sxU8x的类名。

如果想让某些样式变成全局样式,不进行类名的转换,可以使用:global()

:global(.serif-font) {
  font-family: Georgia, serif;
}
作者

BiteByte

发布于

2020-12-10

更新于

2024-01-11

许可协议