2.21 说说对 CSS 工程化的理解
CSS 工程化是为了解决以下问题:
宏观设计:CSS 代码如何组织、如何拆分、模块结构怎样设计?
编码优化:怎样写出更好的 CSS?
构建:如何处理我的 CSS,才能让它的打包结果最优?
可维护性:代码写完了,如何最小化它后续的变更成本?如何确保任何一个同事都能轻松接手?
以下三个方向都是时下比较流行的、普适性非常好的 CSS 工程化实践:
- 预处理器:Less、 Sass 等;
- 重要的工程化插件: PostCss;
- Webpack loader 等 。
基于这三个方向,可以衍生出一些具有典型意义的子问题,这里我们逐个来看:
(1)预处理器:为什么要用预处理器?它的出现是为了解决什么问题?
预处理器,其实就是 CSS 世界的“轮子”。预处理器支持我们写一种类似 CSS、但实际并不是 CSS 的语言,然后把它编译成 CSS 代码:
那为什么写 CSS 代码写得好好的,偏偏要转去写“类 CSS”呢?这就和本来用 JS 也可以实现所有功能,但最后却写 React 的 jsx 或者 Vue 的模板语法一样。
随着前端业务复杂度的提高,前端工程中对 CSS 提出了以下的诉求:
1.宏观设计上:我们希望能优化 CSS 文件的目录结构,对现有的 CSS 文件实现复用;
2.编码优化上:我们希望能写出结构清晰、简明易懂的 CSS,需要它具有一目了然的嵌套层级关系,而不是无差别的一铺到底写法;我们希望它具有变量特征、计算能力、循环能力等等更强的可编程性,这样我们可以少写一些无用的代码;
3.可维护性上:更强的可编程性意味着更优质的代码结构,实现复用意味着更简单的目录结构和更强的拓展能力,这两点如果能做到,自然会带来更强的可维护性。
这三点是传统 CSS 所做不到的,也正是预处理器所解决掉的问题。预处理器普遍会具备这样的特性:
嵌套代码的能力,通过嵌套来反映不同 css 属性之间的层级关系 ;
支持定义 css 变量;
提供计算函数;
允许对代码片段进行 extend 和 mixin;
支持循环语句的使用;
支持将 CSS 文件模块化,实现复用。
(2)PostCss:PostCss 是如何工作的?我们在什么场景下会使用 PostCss?
它和预处理器的不同就在于,预处理器处理的是 类CSS,而 PostCss 处理的就是 CSS 本身。Babel 可以将高版本的 JS 代码转换为低版本的 JS 代码。PostCss 做的是类似的事情:它可以编译尚未被浏览器广泛支持的先进的 CSS 语法,还可以自动为一些需要额外兼容的语法增加前缀。更强的是,由于 PostCss 有着强大的插件机制,支持各种各样的扩展,极大地强化了 CSS 的能力。
PostCss 在业务中的使用场景非常多:
提高 CSS 代码的可读性:PostCss 其实可以做类似预处理器能做的工作;
当我们的 CSS 代码需要适配低版本浏览器时,PostCss 的 Autoprefixer 插件可以帮助我们自动增加浏览器前缀;
允许我们编写面向未来的 CSS:PostCss 能够帮助我们编译 CSS next 代码;
(3)Webpack 能处理 CSS 吗?如何实现?
Webpack 在裸奔的状态下,是不能处理 CSS 的,Webpack 本身是一个面向 JavaScript 且只能处理 JavaScript 代码的模块化打包工具;
Webpack 在 loader 的辅助下,是可以处理 CSS 的。
如何用 Webpack 实现对 CSS 的处理:
Webpack 中操作 CSS 需要使用的两个关键的 loader:css-loader 和 style-loader
注意,答出“用什么”有时候可能还不够,面试官会怀疑你是不是在背答案,所以你还需要了解每个 loader 都做了什么事情:
css-loader:导入 CSS 模块,对 CSS 代码进行编译处理;
style-loader:创建style标签,把 CSS 内容写入标签。
在实际使用中,css-loader 的执行顺序一定要安排在 style-loader 的前面。因为只有完成了编译过程,才可以对 css 代码进行插入;若提前插入了未编译的代码,那么 webpack 是无法理解这坨东西的,它会无情报错。
2.22 为什么有时候⽤translate来改变位置⽽不是使用position进行定位?
translate 是 transform 属性的⼀个值。
改变transform或opacity不会触发浏览器重新布局(reflow)或重绘(repaint),只会触发复合(compositions)。
⽽改变绝对定位会触发重新布局,进⽽触发重绘和复合。
transform使浏览器为元素创建⼀个 GPU 图层,但改变绝对定位会使⽤到 CPU。
因此translate()更⾼效,可以缩短平滑动画的绘制时间。
⽽translate改变位置时,元素依然会占据其原始空间,绝对定位就不会发⽣这种情况。
具体的原理可查看 【前端基础系列】CSS篇-带你搞懂“硬件加速”
2.23 硬件加速的原理是什么?
面试中可能会经常会碰到怎么解决动画卡顿的问题,然后会引导到硬件加速。那么究竟什么是硬件加速,为什么它可以提高咱们的动画效率?我们今天就来一探究竟。
首先,我们先从 CPU 和 GPU 开始了解。
CPU 和 GPU 的区别
CPU
即中央处理器,GPU
即图形处理器。
CPU是计算机的大脑,它提供了一套指令集,我们写的程序最终会通过 CPU 指令来控制的计算机的运行。它会对指令进行译码,然后通过逻辑电路执行该指令。整个执行的流程分为了多个阶段,叫做流水线。指令流水线包括取指令、译码、执行、取数、写回五步,这是一个指令周期。CPU会不断的执行指令周期来完成各种任务。
GPU,是Graphics ProcessingUnit的简写,是现代显卡中非常重要的一个部分,其地位与CPU在主板上的地位一致,主要负责的任务是加速图形处理速度。GPU是显卡的“大脑”,它决定了该显卡的档次和大部分性能,同时也是2D显示卡和3D显示卡的区别依据。2D显示芯片在处理3D图像和特效时主要依赖CPU的处理能力,称为“软加速”。3D显示芯片是将三维图像和特效处理功能集中在显示芯片内,也即所谓的“硬件加速”功能。
要解释两者的区别,要先明白两者的相同之处:两者都有总线和外界联系,有自己的缓存体系,以及数字和逻辑运算单元。
一句话,两者都为了完成计算任务而设计。
两者的区别在于存在于片内的缓存体系和数字逻辑运算单元的结构差异:
CPU虽然有多核,但总数没有超过两位数,每个核都有足够大的缓存和足够多的数字和逻辑运算单元,并辅助有很多加速分支判断甚至更复杂的逻辑判断的硬件;
GPU 的核数远超CPU,被称为众核(NVIDIA Fermi有512个核)。每个核拥有的缓存大小相对小,数字逻辑运算单元也少而简单(GPU初始时在浮点计算上一直弱于CPU)。
从结果上导致CPU擅长处理具有复杂计算步骤和复杂数据依赖的计算任务,如分布式计算,数据压缩,人工智能,物理模拟,以及其他很多很多计算任务等。
GPU由于历史原因,是为了视频游戏而产生的(至今其主要驱动力还是不断增长的视频游戏市场),在三维游戏中常常出现的一类操作是对海量数据进行相同的操作,如:对每一个顶点进行同样的坐标变换,对每一个顶点按照同样的光照模型计算颜色值。
GPU的众核架构非常适合把同样的指令流并行发送到众核上,采用不同的输入数据执行。在通用计算领域有广泛应用,包括:数值分析,海量数据处理(排序,Map-Reduce等),金融分析等等。
简而言之,当程序员为CPU编写程序时,他们倾向于利用复杂的逻辑结构优化算法从而减少计算任务的运行时间,即 Latency。当程序员为GPU编写程序时,则利用其处理海量数据的优势,通过提高总的数据吞吐量(Throughput)来掩盖 Lantency。
目前,CPU 和 GPU 的区别正在逐渐缩小,因为GPU也在处理不规则任务和线程间通信方面有了长足的进步。
每一帧的执行步骤
一般浏览器的刷新率为60HZ,即1秒钟刷新60次。
1000ms / 60hz = 16.6 ,也就是大概每过 16.6ms 浏览器就会渲染一帧画面。
浏览器对每一帧画面的渲染工作都要在 16ms 内完成,超出这个时间,页面的渲染就会出现卡顿现象,影响用户体验。
简单概括下,浏览器在每一帧里会依次执行以下这些动作:
JavaScript:JavaScript 实现动画效果,DOM 元素操作等。
Style(计算样式):确定每个 DOM 元素应该应用什么 CSS 规则。
Layout(布局):计算每个 DOM 元素在最终屏幕上显示的大小和位置。由于 web 页面的元素布局是相对的,所以其中任意一个元素的位置发生变化,都会联动的引起其他元素发生变化,这个过程叫 reflow。
Paint(绘制):在多个层上绘制 DOM 元素的的文字、颜色、图像、边框和阴影等。
Composite(渲染层合并):按照合理的顺序合并图层然后显示到屏幕上。
减少或者避免 layout,paint 可以让页面减少卡顿,动画效果更加流畅。
完整的渲染流程
更具体一些,一个完整的渲染步骤大致可总结为如下:
渲染进程将HTML内容转换为能够读懂的DOM树结构。
渲染引擎将CSS样式表转化为浏览器可以理解的 styleSheets ,计算出DOM节点的样式。
创建布局树,并计算元素的布局信息。
对布局树进行分层,并生成分层树。
为每个图层生成绘制列表,并将其提交到合成线程。
合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。
合成线程发送绘制图块命令DrawQuad给浏览器进程。
浏览器进程根据DrawQuad消息生成页面,并显示到显示器上
普通图层和复合图层
上面的介绍中,提到了 composite
概念。
可以简单的这样理解,浏览器渲染的图层一般包含两大类:渲染图层(普通图层)以及复合图层
渲染图层:又称默认复合层,是页面普通的文档流。我们虽然可以通过绝对定位,相对定位,浮动定位脱离文档流,但它仍然属于默认复合层,共用同一个绘图上下文对象(GraphicsContext)。
复合图层,它会单独分配资源(当然也会脱离普通文档流,这样一来,不管这个复合图层中怎么变化,也不会影响默认复合层里的回流重绘)
某些特殊的渲染层会被提升为复合成层(Compositing Layers),复合图层拥有单独的 GraphicsLayer,而其他不是复合图层的渲染层,则和其第一个拥有 GraphicsLayer 父层共用一个。
每个 GraphicsLayer 都有一个 GraphicsContext,GraphicsContext 会负责输出该层的位图,位图是存储在共享内存中,作为纹理上传到 GPU 中,最后由 GPU 将多个位图进行合成,然后 draw 到屏幕上,此时,我们的页面也就展现到了屏幕上。
可以 Chrome源码调试 -> More Tools -> Rendering -> Layer borders中看到,黄色的就是复合图层信息。
硬件加速
硬件加速,直观上说就是依赖 GPU 实现图形绘制加速,软硬件加速的区别主要是图形的绘制究竟是 GPU 来处理还是 CPU,如果是 GPU,就认为是硬件加速绘制,反之,则为软件绘制。
一般一个元素开启硬件加速后会变成复合图层,可以独立于普通文档流中,改动后可以避免整个页面重绘,提升性能。
常用的硬件加速方法有:
最常用的方式:translate3d、translateZ
opacity 属性/过渡动画(需要动画执行的过程中才会创建合成层,动画没有开始或结束后元素还会回到之前的状态)
will-change属性(这个知识点比较冷僻),一般配合 opacity 与 translate 使用(而且经测试,除了上述可以引发硬件加速的属性外,其它属性并不会变成复合层),作用是提前告诉浏览器要变化,这样浏览器会开始做一些优化工作(这个最好用完后就释放)
<video>、<iframe>、<canvas>、<webgl>等元素
其它,譬如以前的 flash 插件
当然,有的时候我们想强制触发硬件渲染,就可以通过上面的属性,比如
1will-change: transform;
或者
1transform:translate3d(0, 0, 0);
使用硬件加速的注意事项
使用硬件加速并不是十全十美的事情,比如:
内存。如果GPU加载了大量的纹理,那么很容易就会发生内容问题,这一点在移动端浏览器上尤为明显,所以,一定要牢记不要让页面的每个元素都使用硬件加速。
使用GPU渲染会影响字体的抗锯齿效果。这是因为GPU和CPU具有不同的渲染机制。即使最终硬件加速停止了,文本还是会在动画期间显示得很模糊。
所以不要大量使用复合图层,否则由于资源消耗过度,页面可能会变的更加卡顿。
同时,在使用硬件加速时,尽可能的使用z-index,防止浏览器默认给后续的元素创建复合层渲染。
具体的原理是这样的:
webkit CSS3中,如果一个元素添加了硬件加速,并且z-index层级比较低,那么在这个元素的后面其它元素(层级比这个元素高的,或者相同的,并且releative或absolute属性相同的),会默认变为复合层渲染,如果处理不当会极大的影响性能。
简单点理解,其实可以认为是一个隐式合成的概念:如果a是一个复合图层,而且b在a上面,那么b也会被隐式转为一个复合图层,这点需要特别注意。
2.24 CSS动画和JS实现的动画分别有哪些优缺点?
CSS动画
优点
- 浏览器可以对动画进行优化
- 代码相对简单,性能调优方向固定
- 对于帧速表现不好的低版本浏览器,
CSS3
可以做到自然降级,而JS
则需要撰写额外代码
缺点
- 运行过程控制较弱,无法附加事件绑定回调函数
- 代码冗长,想用
CSS
实现稍微复杂一点动画,最后CSS
代码都会变得非常笨重
JS动画
优点
- 控制能力很强, 可以在动画播放过程中对动画进行控制:开始、暂停、回放、终止、取消都是可以做到的。
- 动画效果比
css3
动画丰富,有些动画效果,比如曲线运动,冲击闪烁,视差滚动效果,只有js
动画才能完成 CSS3
有兼容性问题,而JS
大多时候没有兼容性问题
缺点
- 代码的复杂度高于
CSS
动画 JavaScript
在浏览器的主线程中运行,而主线程中还有其它需要运行的JavaScript
脚本、样式计算、布局、绘制任务等,对其干扰导致线程可能出现阻塞,从而造成丢帧的情况
2.25 前端实现动画有哪些方式?
前端常用的动画实现方式有以下种:
- css3的
transition
属性 - css3的
animation
属性 - 原生JS动画
- 使用
canvas
绘制动画 - SVG动画
- Jquery的
animate
函数 - 用gif图片
1. css3的transition
transition
属性:
用来设置样式的属性值是如何从一种状态平滑过渡到另外一种状态
语法:
1transition: property duration timing-function delay;
transition是一种简写属性,它可以拆分为四个过渡属性。你可以 transition: 值1,值2,值3,值4 这样写,也可以:transition-property: 值1;,transition-duration:值2;,transition-timing-function:值2;,transition-delay:值4;这样写。
值 | 描述 |
transition-property | 规定设置过渡效果的 CSS 属性的名称。 |
transition-duration | 规定完成过渡效果需要多少秒或毫秒。 |
transition-timing-function | 规定速度效果的速度曲线。 |
transition-delay | 定义过渡效果何时开始。 |
演示代码:
<div></div> div{ width:50px; height: 50px; background-color: pink; } div:hover{ width:200px; }
效果图:
由上图可看出:鼠标移入移出时,width
状态的变化是瞬间完成的。
添加transition: 1s;
后
div{ width:50px; height: 50px; background-color: pink; transition: 1s; } div:hover{ width:200px; }
效果图:transition: 1s;
设置了width
属性状态变化的过渡时间为1秒。
transition`属性默认为:`transition: all 0 ease 0; transition:1s;` 等价于 `transition: all 1s ease 0;
2. css3的animation
animation
属性:比较类似于 flash 中的逐帧动画。学习过 flash
的同学知道,这种逐帧动画是由关键帧组成,很多个关键帧连续的播放就组成了动画在 CSS3
中是由属性keyframes
来完成逐帧动画的。animation
属性与transition
属性的区别:
transition
只需指定动画的开始和结束状态,整个动画的过程是由特定的函数控制,你不用管它。animation
可以对动画过程中的各个关键帧进行设置
演示代码:
<div></div> 1div{ width:50px; height:50px; background-color: pink; } div:hover{ animation: change1 5s; } @keyframes change1{ 25% {width:130px;background-color: red;} 50% {width:170px;background-color: blue;} 75% {width:210px;background-color: green;} 100% {width:250px;background-color: yellow;} }
效果图:
3. 原生JS动画
其主要思想是通过setInterval或setTimeout方法的回调函数来持续调用改变某个元素的CSS样式以达到元素样式变化的效果。
javascript 实现动画通常会导致页面频繁性重排重绘,消耗性能,一般应该在桌面端浏览器。在移动端上使用会有明显的卡顿。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <style type="text/css"> #rect { width: 200px; height: 200px; background: #ccc; } </style> </head> <body> <div id="rect"></div> <script> let elem = document.getElementById('rect'); let left = 0; let timer = setInterval(function(){ if(left<window.innerWidth-200){ elem.style.marginLeft = left+'px'; left ++; }else { clearInterval(timer); } },16); </script> </body> </html>
上面的例子中,我们设置的setInterval时间间隔是16ms。一般认为人眼能辨识的流畅动画为每秒60帧,这里16ms比(1000ms/60)帧略小一些,但是一般可仍为该动画是流畅的。
在很多移动端动画性能优化时,一般使用16ms来进行节流处理连续触发的浏览器事件。例如对touchmove、scroll事件进行节流等。通过这种方式减少持续事件的触发频率,可以大大提升动画的流畅性。
4. 使用canvas
绘制动画
canvas作为H5新增元素,是借助Web API来实现动画的。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style> *{ margin:0; padding:0; } </style> </head> <body> <canvas id="canvas" width="700" height="550"></canvas> <script type="text/javascript"> let canvas = document.getElementById("canvas"); let ctx = canvas.getContext("2d"); let left = 0; let timer = setInterval(function(){ ctx.clearRect(0,0,700,550); ctx.beginPath(); ctx.fillStyle = "#ccc"; ctx.fillRect(left,0,100,100); ctx.stroke(); if(left>700){ clearInterval(timer); } left += 1; },16); </script> </body> </html>
注释:通过getContext()获取元素的绘制对象,通过clearRect不断清空画布并在新的位置上使用fillStyle绘制新矩形内容实现页面动画效果。
Canvas主要优势是可以应对页面中多个动画元素渲染较慢的情况,完全通过javascript来渲染控制动画的执行。可用于实现较复杂动画。
5. SVG 动画
SVG是一种基于XML的图像格式,非常类似于HTML的工作方式。它为许多熟悉的几何形状定义了不同的元素,这些元素可以在标记中组合以产生二维图形。
同样高清的质地,矢量图不畏惧放大,体积小。
这里要说明一点就是,因为 SVG 中保存的是点、线、面的信息,与分辨率和图形大小无关,只是跟图像的复杂程度有关,所以图像文件所占的存储空间通常会比 png 小。
SVG动画的优势:
优化 SEO 和无障碍的利器,因为 SVG 图像是使用XML(可扩展标记语言【英语:Extensible Markup Language,简称:XML】标记指计算机所能理解的信息符号,通过此种标记,计算机之间可以处理包含各种信息的文章等)来标记构建的,浏览器通过绘制每个点和线来打印它们,而不是用预定义的像素填充某些空间。这确保 SVG 图像可以适应不同的屏幕大小和分辨率。
由于是在 XML 中定义的,SVG 图像比 JPG 或 PNG 图像更灵活,而且我们可以使用 CSS 和 JavaScript 与它们进行交互。SVG 图像设置可以包含 CSS 和 JavaScript。在 react、vue 这种数据驱动视图的框架下,对于 SVG 操作就更加如鱼得水了。(下文会跟大家分享一些小的 SVG 动画在我们项目中的实践)
在运用层面上,SVG 提供了一些图像编辑效果,比如屏蔽和剪裁、应用过滤器等等。并且 SVG 只是文本,因此可以使用 GZip 对其进行有效压缩。