盒模型(Box Model)

盒模型的主要区域:内容盒子(Content Box)、内边距盒子(Padding Box)、边框盒子(Border Box)、外边距盒子(Margin Box)。

Box-Model

Content Box:这是内容所在的区域。此内容可以控制其父级的大小,因此通常是最可变大小的区域。

Padding Box:内边距盒子围绕内容盒子,是由 padding 属性创建的空间。如果我们的盒子设置了溢出规则,比如 overflow:auto 或者 overflow:scroll,滚动条也会占用这个空间。

Border Box:边框盒子围绕着内边距盒子,其空间被 border 值占用。边框是盒子的边界。

Margin Box:最后一个区域,即外边距盒子,是盒子周围的空间,由盒子上的 margin 规则定义。轮廓 outline 和盒子阴影 box-shadow 等属性也占据了这个空间,因为它们被绘制在顶部,所以它们不会影响我们盒子的大小。你可以在盒子上有一个 200pxoutline-width,并且包括边框在内的所有内容都将是完全相同的大小。

内容和大小

盒子根据其 display 值、设置的尺寸和其中的内容具有不同的行为。

你可以通过使用外部大小来控制盒模型内容大小:width:50px;

或者,也可以继续让浏览器根据内容大小为您做出决定,使用内部大小:width: min-width; 浏览器默认行为;

当我们显式的设置宽度 width 时,由于 CSS 使用默认的 box-sizing: content-box宽度只会作用于内容盒子。我们也可以使用 border-box 让盒子大小为:Border Box + Padding Box + Content Box。

JS 获取盒模型大小

Content Box

JS 无法直接获取内容盒子的大小,但是可以通过 Padding Box 的大小 - Padding 得到,代码如下:

function getContentWidth (element) {
  const styles = getComputedStyle(element)

  return element.clientWidth
    - parseFloat(styles.paddingLeft)
    - parseFloat(styles.paddingRight)
}

Padding Box

内边距盒子可以通过元素的 clientWidth 只读属性获取,Element.clientWidth 可以用来获取非内联元素的内边距盒子宽度,以像素计。该属性包括内边距 padding,但不包括边框 border、外边距 margin 和垂直滚动条(如果有的话)。

如果元素超出了指定大小并设置了溢出规则,此时如果要获取元素的实际内边距盒子,可以通过 Element.scrollWidth 只读属性来度量包括由于溢出而在屏幕上不可见的内容的宽度。宽度的测量方式与 clientWidth 相同,如果没有溢出,则 clientWidth 和 scrollWidth 相同。

Border Box

要获取包括边框在内的宽度,可以使用元素的 offsetWidth 只读属性。HTMLElement.offsetWidth 包括任何边框、内边距和垂直滚动条(如果有的话)。它不包括伪元素的宽度,例如 ::before::after

也可以使用元素的 getBoundingClientRect() 属性,绝大多数情况下两者是相等的。除了在有 变换 - transform 的情况下,offsetWidth 和 offsetHeight 返回元素的布局宽度和高度,而 getBoundingClientRect() 返回渲染的宽度和高度。

例如,如果元素有 width: 100px; 和 transform: scale(0.5); getBoundingClientRect() 将返回 50 作为宽度,而 offsetWidth 将返回 100。


它们的关系如下图:

js-doc-width

选择器(CSS Selector)

要将 CSS 应用于元素,需要先选择元素。CSS 为我们提供了许多不同的方法来执行此操作。这些选择器可以多个任意组合形成一个 CSS 规则。这些选择器包括:

基本选择器

  • 通用选择器Universal selector

    选择所有元素。(可选)可以将其限制为特定的名称空间或所有名称空间。 语法:* ns|* *|* 例子:* 将匹配文档的所有元素。

  • 元素选择器Type selector

    按照给定的节点名称,选择所有匹配的元素。 语法:elementname 例子:input 匹配任何 `` 元素。

  • 类选择器Class selector

    按照给定的 class 属性的值,选择所有匹配的元素。 语法.classname 例子.index 匹配任何 class 属性中含有 “index” 类的元素。

  • ID 选择器ID selector

    按照 id 属性选择一个与之匹配的元素。需要注意的是,一个文档中,每个 ID 属性都应当是唯一的。 语法:#idname 例子:#toc 匹配 ID 为 “toc” 的元素。

  • 属性选择器Attribute selector

    按照给定的属性,选择所有匹配的元素。 语法:[attr] [attr=value] [attr~=value] [attr|=value] [attr^=value] [attr$=value] [attr*=value] 例子:[autoplay] 选择所有具有 autoplay 属性的元素(不论这个属性的值是什么)。

分组选择器(Grouping selectors)

  • 选择器列表Selector list

    , 是将不同的选择器组合在一起的方法,它选择所有能被列表中的任意一个选择器选中的节点。 语法A, B 示例div, span 会同时匹配 元素和 元素。

组合器(Combinators)

  • 后代组合器Descendant combinator

    (空格)组合器选择前一个元素的所有后代节点。 语法:A B 例子:div span 递归匹配所有位于任意 元素之内的 元素。

  • 直接子代组合器Child combinator

    > 组合器选择前一个元素的直接子代的节点。 语法A > B 例子ul > li 匹配直接嵌套在 元素内的所有 元素。

  • 一般兄弟组合器General sibling combinator

    ~ 组合器选择兄弟元素,也就是说,后一个节点在前一个节点后面的任意位置,并且共享同一个父节点。 语法A ~ B 例子p ~ span 匹配同一父元素下, 元素后的所有 元素。

  • 紧邻兄弟组合器Adjacent sibling combinator

    + 组合器选择相邻元素,即后一个元素紧跟在前一个之后,并且共享同一个父节点。 语法:A + B 例子:h2 + p 会匹配所有紧邻在 元素后的 元素。

  • 列组合器Column combinator

    || 组合器选择属于某个表格行的节点。 语法: A || B 例子: col || td 会匹配所有 作用域内的 元素。

伪选择器(Pseudo)

  • 伪类

    : 伪选择器支持按照未被包含在文档树中的状态信息来选择元素。 例子:a:visited 匹配所有曾被访问过的 元素。

  • 伪元素

    :: 伪选择器用于表示无法用 HTML 语义表达的实体。 例子:p::first-line 匹配所有 元素的第一行。

使用技巧

  • 使用紧邻兄弟组合器为堆叠元素之间添加间隙。> * + *

仅当元素是 .top 的子元素的下一个兄弟元素时,才使用紧邻兄弟组合器添加间隙,并使用伪元素为间隙添加背景色。

也可以使用 column-gaprow-gap 为 Multi-column(多列布局)、Flexible Box(弹性盒子)以及 Grid layouts(网格布局)中的列或行之间添加间隙。

  • 使用 Emmet 类 CSS 选择器语法快速书写结构化代码块

例如使用下面的缩写:

#page>div.logo+ul#navigation>li*5>a{Item $}

将转换为:

<div id="page">
    <div class="logo"></div>
    <ul id="navigation">
        <li><a href="">Item 1</a></li>
        <li><a href="">Item 2</a></li>
        <li><a href="">Item 3</a></li>
        <li><a href="">Item 4</a></li>
        <li><a href="">Item 5</a></li>
    </ul>
</div>

大多数编辑器都支持 Emmet 缩写 Tab 键快速展开代码块,它的语法不限于 HTML,详情查看官方文档:Emmet Documentation

层叠

了解层叠算法有助于了解浏览器如何解决样式冲突。层叠算法分为 4 个不同的阶段。

  1. 资源顺序及位置
  2. 优先级 - 权重(Specificity)
  3. 来源(Origin)
  4. 重要性(!important)

资源顺序及位置

样式可以来自 HTML 页面上的各种来源,例如 <link> 标记、嵌入的 <style> 标记和元素 style 属性中定义的内联 CSS。

<link><style> 中的样式,靠后的顺序优先级更高,内连 style CSS 会覆盖其它两者的样式而不管它们的顺序,除非他们定义了 !important 属性。

优先级

本质上,不同类型的选择器有不同的分数值,把这些分数相加就得到特定选择器的权重,然后就可以进行匹配。

一个选择器的优先级可以说是由四个部分相加,可以认为是个十百千 — 四位数的四个位数:

  1. 千位: 如果声明在 style 的属性(内联样式)则该位得一分。这样的声明没有选择器,所以它得分总是1000。
  2. 百位: 选择器中包含ID选择器则该位得一分。
  3. 十位: 选择器中包含类选择器、属性选择器或者伪类则该位得一分。
  4. 个位:选择器中包含元素、伪元素选择器则该位得一分。

: 通用选择器 (*),组合符 (+, >, ~, ' '),和否定伪类 (:not) 不会影响优先级。

来源

层叠考虑了不同来源的 CSS,而不止我们编写的样式。此来源包括浏览器的内部样式表、浏览器扩展或操作系统添加的样式以及我们编写的 CSS。下面列出这些来源的优先级,从低到高:

  1. 用户代理基本样式 - User Agent,浏览器作为用户的代理,会帮我们设置默认应用于 HTML 元素的样式。
  2. 用户样式 - Local User,浏览器的用户所具有的样式,这些可以来自操作系统级别,例如基础字体大小。也可以来自浏览器扩展。
  3. 作者样式 - Authored CSS,我们程序员为网站编写的 CSS。
  4. 作者样式表中的 !important 声明,我们添加到您编写的声明中的任何 !important
  5. 用户样式表中的 !important 声明,来自操作系统级别或浏览器扩展级别 CSS 的任何 !important
  6. 用户代理样式表中的 !important 声明,浏览器提供的默认 CSS 中定义的任何 !important

Origin CSS specific

重要性

重要性从低到高依次如下:

  1. 普通类型的规则
  2. animation 类型规则
  3. !important 类型规则
  4. transition 类型规则

继承

每个 HTML 元素都有一些默认定义的 CSS 属性,并带有初始值。初始值是未继承的属性,如果级联无法计算该元素的值,则使用初始值显示默认样式。如果用户为父元素定义了样式,则会级联继承给子元素,但并不是所有 CSS 属性都是可以继承的,这是可继承属性的完整列表,参考 W3:

控制继承

inherit

设置该属性会使子元素属性和父元素相同。实际上,就是 “开启继承”,使用该属性可以让元素使用继承值而不是浏览器默认值。

initial

设置属性值和浏览器默认样式相同。如果浏览器默认样式中未设置且该属性是可继承的,那么会设置为 inherit

unset

将属性重置为自然值,也就是如果属性是可继承的那么就是 inherit,否则和 initial 一样。