【译】CSS 才不是什么黑魔法呢

本文讲的是【译】CSS 才不是什么黑魔法呢,


CSS 才不是什么黑魔法呢

一起来揭开 CSS 的神秘面纱



如果你是一名 web 开发者,你可能会时不时地写一些 CSS。

当你第一次接触 CSS 时,似乎觉得 CSS 轻而易举。加边框,改颜色,小菜一碟。JavaScript 才是前端开发的难点,不是吗?

但是在你 web 开发生涯中的某天,这个想法变了!更糟糕的是,许多前端社区的开发者早已把 CSS 轻视为一门玩具语言。

然而,事实却是当我们碰壁时,我们中的许多人实际上未曾深入了解我们编写的 CSS 做了什么。

在我接受前端培训后的头两年,我曾从事全栈 JavaScript 开发,偶尔写一点点 CSS。作为JavaScript Jabber 评委会的一员,我一直认为 JavaScript 才是我吃饭的家伙,所以大部分时间我都花在 JavaScript 上。

然而直到去年,当我决定专注于前端时,才意识到根本无法像调试 JavaScript 那样轻松地调试 CSS!

我们都喜欢拿 CSS 开玩笑,但是我们中有多少人真的花时间去尝试理解我们正在编写或正在阅读的 CSS。当我们碰壁时,我们有多少人在解决问题的同时,会深入最底层(看看发生了什么)? 相反,我们止步于照搬 StackOverflow 上票数最高的答案,或者用一些黑科技(hack)手段随便应付一下,或者我们干脆撒手不管了:那是一个 feature 而不是一个 bug。

当浏览器以非预期的方式呈现 CSS 时,开发者常常感到非常困惑。但是 CSS 并不是黑魔法,而作为开发者,我们都明白计算机只会按照我们的指令去执行。

学习浏览器的内部工作原理将有助于掌握高级调试技巧和性能优化方案。虽然许多会议的演讲会讨论如何修复常见的 bug,但我的演讲(和这篇文章)的重点在于为什么会有这些 bug,为此我将深入介绍浏览器内部原理,看看我们的 CSS 是如何被解析和呈现。

DOM 与 CSSOM

首先,了解浏览器包含 JavaScript 引擎和渲染引擎非常重要,而本文将重点关注后者。例如,我们将讨论涉及 WebKit(Safari),Blink(Chrome),Gecko(Firefox)和 Trident / EdgeHTML(IE / Edge)的细节。浏览器将经历包括转换、标记化、词法分析和解析的过程,最终构建 DOM 和 CSSOM。(译注:CSSOM 即 CSS Object Model,定义了媒体查询,选择器和 CSS 本身的 API,这些 API 包括了通用解析和序列化规则,传送门:CSSOM

这一过程大致可以分为以下几个步骤:

  • 转换:从磁盘或网络读取 HTML 和 CSS 的原始字节。
  • 标记化: 将输入内容分解成一个个有效标记(例如:起始标签、结束标签、属性名、属性值),分离无关字符(如空格和换行符)。
  • 词法分析:和 tokenizer(标记生成器)类似,但它还标记每个 token 的类型(类型包括:数字、字符串字面量、相等运算符等等)。
  • 解析: 解析器接收词法分析器传递的 tokens,并尝试将其与某条语法规则进行匹配,匹配成功后将之添加到抽象语法树中。

一旦 DOM 树和 CSSOM 树创建完毕,渲染引擎就会将数据结构附加到所谓的渲染树中,并作为布局过程的一部分。

渲染树是文档的可视化表现形式,它按照正确的顺序绘制页面的内容。渲染树的构造过程遵循以下顺序:

  • 从 DOM 树的根节点开始,遍历每个可见节点
  • 忽略不可见的节点
  • 对于每个可见节点,找到合适的与 CSSOM 匹配的规则并应用它们
  • 发送包含内容和计算样式的可见节点
  • 最后,在屏幕上输出包含所有可见元素的内容和样式信息的渲染树。

CSSOM 可以对渲染树产生很大的影响,但不会影响到 DOM 树。

渲染

经历了布局和渲染树构建后,浏览器终于要开始将网页绘制到屏幕上并合成图层。

  • 布局:包括计算一个元素占用的空间以及它在屏幕上的位置。父元素可以影响子元素布局,某些情况下子元素也会反过来影响父元素。
  • 绘制:将渲染树中的每个节点转换为屏幕上的实际像素的过程。它涉及绘制文本、颜色、图像、边框和阴影。绘图通常在多个图层上完成,另外由于加载、执行 JavaScript 而改变了 DOM 会导致多次绘制 。
  • 合成:将所有图层合并在一个图层,作为最终屏幕上可见图层的过程。由于页面的各个部分可以绘制成多层,所以需要以正确的顺序绘制到屏幕上。

绘制时间取决于渲染树结构,元素的 width 和 height 的值越大,绘制时间就越长。

添加各种特效同样会增加绘画时间。绘制的顺序是按照元素进入层叠上下文的顺序(从后往前绘制),稍后我们再谈谈 z-index。如果你喜欢看视频教程,有一个很棒的关于绘制过程的 demo

当人们在谈论浏览器的硬件加速时,绝大多数都是指加速“合成”过程,也就是意味着使用 GPU 来合成网页的内容。

与使用计算机 CPU 进行合成的旧方式相比,使用 GPU 能带来相当多的速度提升,而合理利用 will-change 这一属性有助于此。(译注:will-change 相关资料传送门 will-change MDNEverything You Need to Know About the CSS will-change Property

举个例子:在使用 CSS transform 属性时,will-change 属性能提前告知浏览器 DOM 元素接下来会有哪些变化。这可以将一些绘制和合成操作移交给 GPU,从而大大提高有大量动画的页面的性能。使用 will-change 属性,对于滚动位置变化、内容变化、不透明度变化以及绝对定位坐标位置变化也有类似的性能收益。

有必要了解一件事:某些 CSS 属性将导致重新布局,而其他属性只会导致重新绘制。当然出于性能考虑,最好只触发重绘。

举个例子:元素的颜色改变后,只会对该元素进行重绘。而元素的位置改变后,会对该元素及其子元素(可能还有同级元素)进行布局和重绘。添加 DOM 节点后,会对该节点进行布局和重绘。一些重大变化(例如增大 html 元素的字体)会导致整个渲染树进行重新布局和绘制。

如果你像我一样,比起 CSSOM 更熟悉 DOM,那么让我们来深入了解一下 CSSOM。请务必注意,默认情况下,CSS 会被视为阻塞渲染资源。这意味着浏览器在构建完 CSSOM 之前,将挂起任何其它进程的渲染。

CSSOM 和 DOM 并不是一一对应的。具有 dispay:none 属性的元素、<script> 标签、<meta> 标签、<head> 元素等等不可见的 DOM 元素不会显示在渲染树中。

CSSOM 和 DOM 的另一个区别则在于解析 CSS 使用的是一种上下文无关语法。也就是说,CSS 渲染引擎不会自动补全 CSS 中缺少的语法,然而解析 HTML 创建 DOM 时则刚好相反。

解析 HTML 时,浏览器不得不结合 HTML 标签所在的上下文,而且只遵从 HTML 规范是不够的,因为 HTML 标签可能包含一些缺省的信息,并且无论解析成什么,最终都要渲染出来。(译注:这么做的目的是为了包容开发者的错误,简化 web 开发,例如能省略一些起始或者结束标记等等)

说了那么多,我们来回顾一下:

  • 浏览器向服务器发起 HTTP 请求
  • 服务器响应请求,并返回网页数据
  • 浏览器通过标记化将响应数据(字节)转换为 tokens
  • 浏览器将 tokens 转换为节点
  • 浏览器将节点插入 DOM 树
  • 等待构建 CSSOM 树

优先级

我们已经深入了解了不少浏览器的工作原理,那么接下来我们来看看一些更常见的开发痛点吧。首先说说优先级。

简单来说,CSS 的优先级是指以正确的层叠顺序应用规则。尽管可以使用多种 CSS 选择器来选中特定的标签,浏览器仍需要一种方式来决定最终哪些样式将会生效。在决策过程中,首先浏览器会计算每个选择器的优先级。

不幸的是,优先级的计算规则难倒了不少 JavaScript 开发者,所以让我们一起深入研究 CSS 优先级的计算规则。我们将使用以下的 html 结构作为例子:有一个类名为 container 的 div,在这个 div 里,我们嵌套了另一个 div,它的 id 是 main,我们又在这个 div 里嵌套了一个包含 a 标签的 p 标签。别偷看答案,你知道 a 标签的颜色是什么吗?

#main a {
  color: green;
}

p a {
  color: yellow;
}

.container #main a {
  color: pink;
}

div #main p a {
  color: orange;
}

a {
  color: red;
}

(译注:加一段 html 结构顺便防偷看答案 →_→)

<div class="container">
    <div id="main">
        <p>
            <a href="#">Test</a>
        </p>
    </div>
</div>

答案是粉色,它的优先级为:1,1,1。以下是其余选择器的优先级:

  • div #main p a: 1,0,3
  • #main a: 1,0,1
  • p a: 2
  • a: 1

优先级的每一个数的计算规则如下:

  • 第一个数:ID 选择器的数量
  • 第二个数:类选择器、属性选择器(不包含:[type="text"][rel="nofollow"])、以及伪类选择器(不包含::hover:visited)的数量和。
  • 第三个数:元素选择器与伪元素选择器(不包含: ::before::after)的数量和。

因此,对于以下选择器:

#header .navbar li a:visited

该选择器的优先级是:1,2,2。因为我们有 1 个 ID 选择器、1 个类选择器、1 个伪类选择器、还有 2 个元素选择器(lia)。你可以把优先级看作一个数字,比如 1,2,2 就是 122。这里的逗号是为了提现你优先级的数值并不是以 10 进制计算的。理论上你可以让一个元素的优先级为:0,1,13,4,其中的 13 并不会像 10 进制那样产生进位。(译注:不会变成 0,2,3,4)

定位

其次,我想花点时间讨论一下定位。正如前文所说的,定位和布局是密切相关的。

布局是一个递归的过程,当全局样式变化的时候,有时会在整个渲染树上(重新)触发布局,有时则仅在局部变化的地方增量更新。有一件有趣的事情值得注意:如果我们重新思考渲染树中的绝对定位元素,该对象在渲染树中的位置和它在 DOM 树中的位置不同的。

我也经常被问及应该使用 flexbox 还是 float 进行布局。毫无疑问,用 flexbox 进行布局相当方便,而且当应用于同一个元素时,flexbox 布局将在大约 3.5ms 内呈现,而 float布局可能需要大约 14ms。所以,磨砺你的 CSS 技能所带来的回报不下于磨砺你的 JavaScript 技能的回报。

Z-Index

最后,我想聊聊 z-index。起初 z-index 听起来很简单。HTML 文档中的每个元素都可以处在文档的每个其他元素的前面或后面。 而它也只适用于指定了定位方式的元素(译注:即,未被定位,非 position:static 的元素)。如果你尝试在没有被定位的元素上设置 z-index,则不会起作用。

调试 z-index 问题的关键是理解层叠上下文,并始终从层叠上下文的根元素开始调试。 层叠上下文是 HTML 元素的三维概念,这些 HTML 元素在一条假想的相对于面向视窗(电脑屏幕)的用户的 z 轴上延伸。换句话说,它是一组具有相同父级的元素,在同一个层叠上下文领域,层叠水平值大的那一个覆盖小的那一个。

每个层叠上下文都有一个唯一的 HTML 元素作为其根元素,并且在不涉及 z-index 和position 属性时,层叠规则很简单:层叠顺序与元素在 HTML 中出现的顺序相同。(译注:即,新绘制的元素会覆盖之前的元素)

当然,你也可以使用 z-index 之外的属性来创建新的层叠上下文,这会导致情况更为复杂。以下属性都会创建新的层叠上下文:

  • opacity 值不是 1
  • filter 值不是 none
  • mix-blend-mode 值不是 normal

顺便提一下,blend mode 决定了指定图层上的像素与其下方图层上的可见像素的混合方式。

transform 属性值不为 none 的元素同样会创建新的层叠上下文。例如 scale(1) 和translate3d(0,0,0)。同样顺便提一下,scale 属性是用于调整元素大小的,而translate3d 属性则会启用 GPU 加速让 CSS 动画更为流畅 。

所以,尽管你可能还没有设计师般的眼光,但希望你正向着 CSS 大师迈进!如果你有兴趣了解更多,我整理了一些学习资源





原文发布时间为:2017年7月25日


本文来自合作伙伴掘金,了解相关信息可以关注掘金网站。

时间: 2024-10-02 13:47:14

【译】CSS 才不是什么黑魔法呢的相关文章

[译]CSS的十大密技

css 在某blog看到一篇关于css的文章,觉得还挺有用的,大致译了一下,译文和原文如下,如果有译得不正确的地方或是对类似问题有其他更好地解决办法的,请不吝赐教! 译文: 1.css 字体简写规则 当使用css定义字体时你可能会这样做:font-size: 1em;line-height: 1.5em;font-weight: bold;font-style: italic;font-variant: small-caps;font-family: verdana,serif; 事实上你可以简

CSS行内对齐的黑魔法

本文和以前的文章类似,orange 尽量带给大家分享实际项目中的坑怎么填,当然只是提供思想,方法很多欢迎讨论,还有就是对于刚上手前端的新人不是特别友好,没关系,涉及到基础知识我会对应的进行指引,给出链接或给出提示,大家可以自行 Google(百度). 说到行内对齐大家可能会想到类似水平对齐,垂直对齐总结类型的文章,既然我们叫 黑魔法 就不会是基础的对齐教程,基础教程的文章好多,大家想必都知道多种方法实现对齐 <!--more--> 项目背景 还是 orange 所在公司的移动端项目,上案例 截

【译】前端开发者的基本要求

原文链接:http://rmurphey.com/blog/2012/04/12/a-baseline-for-front-end-developers/ 本文在github上的链接:https://github.com/chyingp/blog/issues/1 备注:第一次翻译技术文章,标题都纠结了好久不知道肿么翻译,如发现翻译不当之处,可点击github链接提交评论,thx- 前几天我为一个项目写README文档,我希望其他开发者能够看到这个项目,并从中学到一些东西.突然我意识到,若放在几

2天驾驭DIV+CSS!第五课(下)

大家经常会进入一个误区,会认为在Web2.0时代,只要页面中用了Table就是没有技术含量,就是丢人,要是页面中没有一个table,所有元素全部用DIV做,那就是牛人!大家注意了,要是某人对你说,我的整个网站没有应用一个Table,这时候你就可以认为这个人做页面没有什么技术含量 我们接着上节课,继续学习,我把页面整体效果发出来,方便大家学习   [第七步 内容左侧板块(ContentL)布局]我们分析一下他的结构,主要包括标题和文章内容两块,并且标题和内容之间有一条虚线,而第二篇文章的内容部分是

优化JS和CSS更快地下载网页图片

文章简介:我关注JS和CSS的重点也是如何能够更快地下载图片.图片是用户可以直观看到的.他们并不会关注JS和CSS.确实,JS和CSS会影响图片内容的展示,尤其是会影响图片的展示方式(比如图片轮播,CSS背景图和媒体查询).但是我认为JS和CSS只是展示图片的方式.在页面加载的过程 我的大部分性能优化工作都集中在JavaScript和CSS上,从早期的Move Scripts to the Bottom和Put Stylesheets at the Top规则.为了强调这些规则的重要性,我甚至说

学习CSS教程:学习CSS网页布局

文章简介:你也许知道什么叫选择器,什么叫属性,什么叫数值,也许你对css布局略懂一二,但这还远远不够.如果你想着从头开始学习HTML和CSS的话,我建议你认真查看下 this tutorial. 否则,在工作的时候,你依然陷入迷惘的泥潭中苦苦挣扎. 这个篇文章介绍的是现在广泛使用于网站布局领域的CSS基础. 你也许知道什么叫选择器,什么叫属性,什么叫数值,也许你对css布局略懂一二,但这还远远不够.如果你想着从头开始学习HTML和CSS的话,我建议你认真查看下 this tutorial. 否则

CSS网页制作实例教程:非常酷的日期效果

LearningjQuery.com的博客日志上的日期效果非常的酷,如下图: 其中文字的样式和垂直的年份就会告诉你这不是用图片来完成的.而且在标记语言中日期信息是是以文字出现的,就像通常的那样. 通过Firebug查看,代码非常简洁漂亮! 很明显每一个日期并没有各自的图片.它们都出自同一张图片(css sprites!),图片的不同地方被放置上了不同的内容:天.月和年.也许你会记得这和一年前Joost de Valk posted about 上的技术是一样的. 来看一下这张漂亮的图片: HTM

css学习的一些心得

css|心得 其实去年的时候,我就有过接触css了都是跟着一些网页教程学做的无非是用来控制一下link,vlink之类的那个时候觉得css好像也不过如此,对于层什么那个时候很不屑于顾,因为感觉层定位起来不如表格方便今年十月份的时候,正式开始学习css才发现一些网站不用表格,仍然可以做到非常的好除此之外css还有着对于大流量网站的速度优势到现在为止,断断续续的学习了两个月了,其间做了一些页面今天就讲讲自己的感受第一.手写css代码的过程中,常常做无用功.有的时候到经典论坛去看到cssplay去看,

分离到底!用XML+XSLT+CSS+JQuery+WebService组建Asp.Net网站

前言 早在Web标准化风潮到来之前,我就考虑过XML+XSLT建站了,我以为这是一种非常优雅.高效.低耦的方案,必将大行其道. 然而时至今日,使用这种方案建站的人依然寥寥无几,大家还都在抱着Web标准化不撒手,其实Web标准化只是将表现(Css)分离了出来,而数据和结构仍然混杂在一起,它绝对不是我们最终的归宿,它只是一个过渡品. XHtml标准的建立初衷,其实也就是为了向XML过渡的,XML+XSLT+CSS才真正做到数据.结构.表现的完美分离,才是我们的终极目标. 诚然,XML+XSLT确实有