目录
二、什么是物理像素,逻辑像素和像素密度,为什么在移动端开发时需要用到@3x, @2x 这种图片?
五、display:inline-block 什么时候会显示间隙?
十三、为什么有时候⽤ translate 来改变位置⽽不是定位?
十四、li 与 li 之间有看不见的空白间隔是什么原因引起的?如何解决?
十七、::before 和 :after 的双冒号和单冒号有什么区别?
二十、对 requestAnimationframe 的理解
一、transition 和 animation 的区别
- transition 是过度属性,强调过度,它的实现需要触发一个事件(比如鼠标移动上去,焦点,点击等)才执行动画。它类似于 flash 的补间动画,设置一个开始关键帧,一个结束关键帧。
- animation 是动画属性,它的实现不需要触发事件,设定好时间之后可以自己执行,且可以循环一个动画。它也类似于 flash 的补间动画,但是它可以设置多个关键帧(用@keyframe 定义)完成动画。
二、什么是物理像素,逻辑像素和像素密度,为什么在移动端开发时需要用到@3x, @2x 这种图片?
以 iPhone XS 为例,当写 CSS 代码时,针对于单位 px,其宽度为 414px & 896px,也就是说当赋予一个 DIV 元素宽度为 414px,这个 DIV 就会填满手机的宽度。
而如果有一把尺子来实际测量这部手机的物理像素,实际为 1242\*2688 物理像素;经过计算可知,1242/414=3,也就是说,在单边上,一个逻辑像素=3 个物理像素,就说这个屏幕的像素密度为 3,也就是常说的 3 倍屏。
对于图片来说,为了保证其不失真,1 个图片像素至少要对应一个物理像素,假如原始图片是 500300 像素,那么在 3 倍屏上就要放一个 1500900 像素的图片才能保证 1 个物理像素至少对应一个图片像素,才能不失真。
当然,也可以针对所有屏幕,都只提供最高清图片。虽然低密度屏幕用不到那么多图片像素,而且会因为下载多余的像素造成带宽浪费和下载延迟,但从结果上说能保证图片在所有屏幕上都不会失真。
还可以使用 CSS 媒体查询来判断不同的像素密度,从而选择不同的图片:
my-image {
background: (low.png);
}
@media only screen and (min-device-pixel-ratio: 1.5) {
#my-image { background: (high.png); }
}
三、margin 和 padding 的使用场景
- 需要在 border 外侧添加空白,且空白处不需要背景(色)时,使用 margin
- 需要在 border 内测添加空白,且空白处需要背景(色)时,使用 padding
四、CSS 优化和提高性能的方法有哪些?
加载性能:
- css 压缩:将写好的 css 进行打包压缩,可以减小文件体积。
- css 单一样式:当需要下边距和左边距的时候,很多时候会选择使用 margin:top 0 bottom 0;但 margin-bottom:bottom;margin-left:left;执行效率会更高。
- 减少使用@import,建议使用 link,因为后者在页面加载时一起加载,前者是等待页面加载完成之后再进行加载。
选择器性能:
- 关键选择器(key selector)。选择器的最后面的部分为关键选择器(即用来匹配目标元素的部分)。CSS 选择符是从右到左进行匹配的。当使用后代选择器的时候,浏览器会遍历所有子元素来确定是否是指定的元素等等。
- 如果规则拥有 ID 选择器作为其关键选择器,则不要为规则增加标签。过滤掉无关的规则(这样样式系统就不会浪费时间去匹配它们了)。
- 避免使用通配规则,如\*{}计算次数惊人,只对需要用到的元素进行选择。
- 尽量少的去对标签进行选择,而是用 class。
- 尽量少的去使用后代选择器,降低选择器的权重值。后代选择器的开销是最高的,尽量将选择器的深度降到最低,最高不要超过三层,更多的使用类来关联每一个标签元素。
- 了解哪些属性是可以通过继承而来的,然后避免对这些属性重复指定规则。
渲染性能:
- 慎重使用高性能属性:浮动、定位。
- 尽量减少页面重排、重绘。
- 去除空规则:{}。空规则的产生原因一般来说是为了预留样式。去除这些空规则无疑能减少 css 文档体积。
- 属性值为 0 时,不加单位。
- 属性值为浮动小数 0.\*\*,可以省略小数点之前的 0。
- 标准化各种浏览器前缀:带浏览器前缀的在前。标准属性在后。
- 不使用@import 前缀,它会影响 css 的加载速度。
- 选择器优化嵌套,尽量避免层级过深。
- css 雪碧图,同一页面相近部分的小图标,方便使用,减少页面的请求次数,但是同时图片本身会变大,使用时,优劣考虑清楚,再使用。
- 正确使用 display 的属性,由于 display 的作用,某些样式组合会无效,徒增样式体积的同时也影响解析性能。
- 不滥用 web 字体。对于中文网站来说 WebFonts 可能很陌生,国外却很流行。web fonts 通常体积庞大,而且一些浏览器在下载 web fonts 时会阻塞页面渲染损伤性能。
可维护性、健壮性:
- 将具有相同属性的样式抽离出来,整合并通过 class 在页面中进行使用,提高 css 的可维护性。
- 样式与内容分离:将 css 代码定义到外部 css 中。
五、display:inline-block 什么时候会显示间隙?
- 有空格时会有间隙,可以删除空格解决;
- margin 正值时,可以让 margin 使用负值解决;
- 使用 font-size 时,可通过设置 font-size: 0;、letter-spacing、word-spacing 解决;
六、对 CSS 工程化的理解
CSS 工程化是为了解决以下问题:
- 宏观设计:CSS 代码如何组织、如何拆分、模块结构怎样设计?
- 编码优化:怎样写出更好的 CSS?
- 构建:如何处理我的 CSS,才能让它的打包结果最优?
- 可维护性:代码写完了,如何最小化它后续的变更成本?如何确保任何一个同事都能轻松接手?
以下三个方向都是时下比较流行的、普适性非常好的 CSS 工程化实践:
- 预处理器:Less、 Sass 等
- 重要的工程化插件: PostCss
- Webpack loader 等
基于这三个方向,可以衍生出一些具有典型意义的子问题,这里我们逐个来看:
(1)预处理器:为什么要用预处理器?它的出现是为了解决什么问题?
预处理器,其实就是 CSS 世界的“轮子”。预处理器支持我们写一种类似 CSS、但实际并不是 CSS 的语言,然后把它编译成 CSS 代码:
那为什么写 CSS 代码写得好好的,偏偏要转去写 "类 CSS" 呢?这就和本来用 JS 也可以实现所有功能,但最后却写 React 的 jsx 或者 Vue 的模板语法一样——为了爽!要想知道有了预处理器有多爽,首先要知道的是传统 CSS 有多不爽。
随着前端业务复杂度的提高,前端工程中对 CSS 提出了以下的诉求:
- 宏观设计上:我们希望能优化 CSS 文件的目录结构,对现有的 CSS 文件实现复用
- 编码优化上:我们希望能写出结构清晰、简明易懂的 CSS,需要它具有一目了然的嵌套层级关系,而不是无差别的一铺到底写法;我们希望它具有变量特征、计算能力、循环能力等等更强的可编程性,这样我们可以少写一些无用的代码
- 可维护性上:更强的可编程性意味着更优质的代码结构,实现复用意味着更简单的目录结构和更强的拓展能力,这两点如果能做到,自然会带来更强的可维护性
这三点是传统 CSS 所做不到的,也正是预处理器所解决掉的问题。预处理器普遍会具备这样的特性:
- 嵌套代码的能力,通过嵌套来反映不同 css 属性之间的层级关系
- 支持定义 css 变量
- 提供计算函数
- 允许对代码片段进行 extend 和 mixin
- 支持循环语句的使用
- 支持将 CSS 文件模块化,实现复用
(2)PostCss:PostCss 是如何工作的?我们在什么场景下会使用 PostCss?
PostCss 仍然是一个对 CSS 进行解析和处理的工具,它会对 CSS 做这样的事情:
它和预处理器的不同就在于,预处理器处理的是 类 CSS,而 PostCss 处理的就是 CSS 本身。Babel 可以将高版本的 JS 代码转换为低版本的 JS 代码。PostCss 做的是类似的事情:它可以编译尚未被浏览器广泛支持的先进的 CSS 语法,还可以自动为一些需要额外兼容的语法增加前缀。更强的是,由于 PostCss 有着强大的插件机制,支持各种各样的扩展,极大地强化了 CSS 的能力。
PostCss 在业务中的使用场景非常多:
- 提高 CSS 代码的可读性:PostCss 其实可以做类似预处理器能做的工作;
- 当我们的 CSS 代码需要适配低版本浏览器时,PostCss 的 [Autoprefixer](https://github.com/postcss/autoprefixer) 插件可以帮助我们自动增加浏览器前缀;
- 允许我们编写面向未来的 CSS:PostCss 能够帮助我们编译 CSS next 代码;
(3)Webpack 能处理 CSS 吗?如何实现?
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 是无法理解该代码,它会无情报错。
七、如何判断元素是否到达可视区域
以图片显示为例:
- window.innerHeight 是浏览器可视区的高度;
- document.body.scrollTop || document.documentElement.scrollTop 是浏览器滚动的过的距离;
- imgs.offsetTop 是元素顶部距离文档顶部的高度(包括滚动条的距离);
- 内容达到显示区域的:img.offsetTop < window.innerHeight + document.body.scrollTop;
八、如何根据设计稿进行移动端适配?
移动端适配主要有两个维度:
- 适配不同像素密度,针对不同的像素密度,使用 CSS 媒体查询,选择不同精度的图片,以保证图片不会失真;
- 适配不同屏幕大小,由于不同的屏幕有着不同的逻辑像素大小,所以如果直接使用 px 作为开发单位,会使得开发的页面在某一款手机上可以准确显示,但是在另一款手机上就会失真。为了适配不同屏幕的大小,应按照比例来还原设计稿的内容。
为了能让页面的尺寸自适应,可以使用 rem,em,vw,vh 等相对单位。
九、响应式设计的概念及基本原理
响应式网站设计(Responsive Web design)是一个网站能够兼容多个终端,而不是为每一个终端做一个特定的版本。
关于原理: 基本原理是通过媒体查询(@media)查询检测不同的设备屏幕尺寸做处理。
关于兼容: 页面头部必须有 meta 声明的 viewport。
<meta name="viewport" content=width="device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
十、使用 clear 属性清除浮动的原理?
使用 clear 属性清除浮动,其语法如下:
clear: none|left|right|both;
如果单看字面意思,clear:left 是清除左浮动,clear:right 是清除右浮动,实际上,这种解释是有问题的,因为浮动一直还在,并没有清除。
官方对 clear 属性解释:元素盒子的边不能和前面的浮动元素相邻,对元素设置 clear 属性是为了避免浮动元素对该元素的影响,而不是清除掉浮动。
还需要注意 clear 属性指的是元素盒子的边不能和前面的浮动元素相邻,注意这里前面的3 个字,也就是 clear 属性对后面的浮动元素是不闻不问的。考虑到 float 属性要么是 left,要么是 right,不可能同时存在,同时由于 clear 属性对后面的浮动元素不闻不问,因此,当 clear:left 有效的时候,clear:right 必定无效,也就是此时 clear:left 等同于设置 clear:both;同样地,clear:right 如果有效也是等同于设置 clear:both。由此可见,clear:left 和 clear:right 这两个声明就没有任何使用的价值,至少在 CSS 世界中是如此,直接使用 clear:both 吧。
一般使用伪元素的方式清除浮动:
.clear::after {
content:'';
display: block;
clear:both;
}
clear 属性只有块级元素才有效的,而::after 等伪元素默认都是内联水平,这就是借助伪元素清除浮动影响时需要设置 display 属性值的原因。
十一、absolute 与 fixed 共同点与不同点
共同点:
- 改变行内元素的呈现方式,将 display 置为 inline-block
- 使元素脱离普通文档流,不再占据文档物理空间
- 覆盖非定位文档元素
不同点:
- absolute 与 fixed 的根元素不同,absolute 的根元素可以设置,fixed 根元素是浏览器。
- 在有滚动条的页面中,absolute 会跟着父元素进行移动,fixed 固定在页面的具体位置。
十二、实现一个宽高自适应的正方形
- 利用 vw 来实现:
.square {
width: 10%;
height: 10vw;
background: tomato;
}
- 利用元素的 margin/padding 百分比是相对父元素 width 的性质来实现:
.square {
width: 20%;
height: 0;
padding-top: 20%;
background: orange;
}
- 利用子元素的 margin-top 的值来实现:
.square {
width: 30%;
overflow: hidden;
background: yellow;
}
.square::after {
content: '';
display: block;
margin-top: 100%;
}
十三、为什么有时候⽤ translate 来改变位置⽽不是定位?
translate 是 transform 属性的⼀个值。改变 transform 或 opacity 不会触发浏览器重新布局(reflow)或重绘(repaint),只会触发复合(compositions)。⽽改变绝对定位会触发重新布局,进⽽触发重绘和复合。transform 使浏览器为元素创建⼀个 GPU 图层,但改变绝对定位会使⽤到 CPU。 因此 translate()更⾼效,可以缩短平滑动画的绘制时间。 ⽽ translate 改变位置时,元素依然会占据其原始空间,绝对定位就不会发⽣这种情况。
十四、li 与 li 之间有看不见的空白间隔是什么原因引起的?如何解决?
浏览器会把 inline 内联元素间的空白字符(空格、换行、Tab 等)渲染成一个空格。为了美观,通常是一个<li>放在一行,这导致<li>换行后产生换行字符,它变成一个空格,占用了一个字符的宽度。
解决办法:
(1)为<li>设置 float:left;。不足:有些容器是不能设置浮动,如左右切换的焦点图等。
(2)将所有<li>写在同一行。不足:代码不美观。
(3)将<ul>内的字符尺寸直接设为 0,即 font-size:0;。不足:<ul>中的其他字符尺寸也被设为 0,需要额外重新设定其他字符尺寸,且在 Safari 浏览器依然会出现空白间隔。
(4)消除<ul>的字符间隔 letter-spacing:-8px;,不足:这也设置了<li>内的字符间隔,因此需要将<li>内的字符间隔设为默认 letter-spacing:normal;。
十五、替换元素的概念及计算规则---不需要看
通过修改某个属性值呈现的内容就可以被替换的元素就称为“替换元素”。
替换元素除了内容可替换这一特性以外,还有以下特性:
- 内容的外观不受页面上的 CSS 的影响:用专业的话讲就是在样式表现在 CSS 作用域之外。如何更改替换元素本身的外观需要类似 appearance 属性,或者浏览器自身暴露的一些样式接口。
- 有自己的尺寸:在 Web 中,很多替换元素在没有明确尺寸设定的情况下,其默认的尺寸(不包括边框)是 300 像素 ×150 像素。
- 在很多 CSS 属性上有自己的一套表现规则:比较具有代表性的就是 vertical-align 属性,对于替换元素和非替换元素,vertical-align 属性值的解释是不一样的。比方说 vertical-align 的默认值的 baseline,很简单的属性值,基线之意,被定义为字符 x 的下边缘,而替换元素的基线却被硬生生定义成了元素的下边缘。
- 所有的替换元素都是内联水平元素:也就是替换元素和替换元素、替换元素和文字都是可以在一行显示的。但是,替换元素默认的 display 值却是不一样的,有的是 inline,有的是 inline-block。
替换元素的尺寸从内而外分为三类:
- 固有尺寸:指的是替换内容原本的尺寸。例如,图片、视频作为一个独立文件存在的时候,都是有着自己的宽度和高度的。
- HTML 尺寸:只能通过 HTML 原生属性改变,这些 HTML 原生属性包括的 width 和 height 属性、的 size 属性。
- CSS 尺寸:特指可以通过 CSS 的 width 和 height 或者 max-width/min-width 和 max-height/min-height 设置的尺寸,对应盒尺寸中的 content box。
这三层结构的计算规则具体如下:
(1)如果没有 CSS 尺寸和 HTML 尺寸,则使用固有尺寸作为最终的宽高。
(2)如果没有 CSS 尺寸,则使用 HTML 尺寸作为最终的宽高。
(3)如果有 CSS 尺寸,则最终尺寸由 CSS 属性决定。
(4)如果“固有尺寸”含有固有的宽高比例,同时仅设置了宽度或仅设置了高度,则元素依然按照固有的宽高比例显示。
(5)如果上面的条件都不符合,则最终宽度表现为 300 像素,高度为 150 像素。
(6)内联替换元素和块级替换元素使用上面同一套尺寸计算规则。
十六、对line-height 的理解及其赋值方式
(1)line-height 的概念:
- line-height 指一行文本的高度,包含了字间距,实际上是下一行基线到上一行基线距离;
- 如果一个标签没有定义 height 属性,那么其最终表现的高度由 line-height 决定;
- 一个容器没有设置高度,那么撑开容器高度的是 line-height,而不是容器内的文本内容;
- 把 line-height 值设置为 height 一样大小的值可以实现单行文字的垂直居中;
- line-height 和 height 都能撑开一个高度;
(2)line-height 的赋值方式:
- 带单位:px 是固定值,而 em 会参考父元素 font-size 值计算自身的行高
- 纯数字:会把比例传递给后代。例如,父级行高为 1.5,子元素字体为 18px,则子元素行高为 1.5 \* 18 = 27px
- 百分比:将计算后的值传递给后代
十七、::before 和 :after 的双冒号和单冒号有什么区别?
(1)冒号(:)用于CSS3伪类,双冒号(::)用于CSS3伪元素。
(2)::before就是以一个子元素的存在,定义在元素主体内容之前的一个伪元素。并不存在于dom之中,只存在在页面之中。
注意::before 和 :after 这两个伪元素,是在CSS2.1里新出现的。起初,伪元素的前缀使用的是单冒号语法,但随着Web的进化,在CSS3的规范里,伪元素的语法被修改成使用双冒号,成为::before、::after。
十八、元素的层叠顺序
层叠顺序,英文称作 stacking order,表示元素发生层叠时有着特定的垂直显示顺序。下面是盒模型的层叠规则:
对于上图,由上到下分别是:
(1)背景和边框:建立当前层叠上下文元素的背景和边框。
(2)负的 z-index:当前层叠上下文中,z-index 属性值为负的元素。
(3)块级盒:文档流内非行内级非定位后代元素。
(4)浮动盒:非定位浮动元素。
(5)行内盒:文档流内行内级非定位后代元素。
(6)z-index:0:层叠级数为 0 的定位元素。
(7)正 z-index:z-index 属性值为正的定位元素。
注意:当定位元素 z-index:auto;,生成盒在当前层叠上下文中的层级为 0,不会建立新的层叠上下文,除非是根元素。
十九、对 sticky 定位的理解
sticky 英文字面意思是粘贴,所以可以把它称之为粘性定位。语法:position: sticky; 基于用户的滚动位置来定位。
粘性定位的元素是依赖于用户的滚动,在 position:relative; 与 position:fixed; 定位之间切换。它的行为就像 position:relative; 而当页面滚动超出目标区域时,它的表现就像 position:fixed;,它会固定在目标位置。元素定位表现为在跨越特定阈值前为相对定位,之后为固定定位。这个特定阈值指的是 top, right, bottom 或 left 之一,换言之,指定 top, right, bottom 或 left 四个阈值其中之一,才可使粘性定位生效。否则其行为与相对定位相同。
二十、对 requestAnimationframe 的理解
实现动画效果的方法比较多,Javascript 中可以通过定时器 setTimeout 来实现,CSS3 中可以使用 transition 和 animation 来实现,HTML5 中的 canvas 也可以实现。除此之外,HTML5 提供一个专门用于请求动画的 API,那就是 requestAnimationFrame,顾名思义就是请求动画帧。
MDN 对该方法的描述:
- window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。
语法:window.requestAnimationFrame(callback); 其中,callback 是下一次重绘之前更新动画帧所调用的函数(即上面所说的回调函数)。该回调函数会被传入 DOMHighResTimeStamp 参数,它表示 requestAnimationFrame() 开始去执行回调函数的时刻。该方法属于宏任务,所以会在执行完微任务之后再去执行。
取消动画:使用 cancelAnimationFrame()来取消执行动画,该方法接收一个参数——requestAnimationFrame 默认返回的 id,只需要传入这个 id 就可以取消动画了。
优势:
- CPU 节能:使用 SetTinterval 实现的动画,当页面被隐藏或最小化时,SetTinterval 仍然在后台执行动画任务,由于此时页面处于不可见或不可用状态,刷新动画是没有意义的,完全是浪费 CPU 资源。而 RequestAnimationFrame 则完全不同,当页面处理未激活的状态下,该页面的屏幕刷新任务也会被系统暂停,因此跟着系统走的 RequestAnimationFrame 也会停止渲染,当页面被激活时,动画就从上次停留的地方继续执行,有效节省了 CPU 开销。
- 函数节流:在高频率事件( resize, scroll 等)中,为了防止在一个刷新间隔内发生多次函数执行,RequestAnimationFrame 可保证每个刷新间隔内,函数只被执行一次,这样既能保证流畅性,也能更好的节省函数执行的开销,一个刷新间隔内函数执行多次时没有意义的,因为多数显示器每 16.7ms 刷新一次,多次绘制并不会在屏幕上体现出来。
- 减少 DOM 操作:requestAnimationFrame 会把每一帧中的所有 DOM 操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说,这个频率为每秒 60 帧。
- setTimeout 执行动画的缺点:它通过设定间隔时间来不断改变图像位置,达到动画效果。但是容易出现卡顿、抖动的现象;原因是:settimeout 任务被放入异步队列,只有当主线程任务执行完后才会执行队列中的任务,因此实际执行时间总是比设定时间要晚;settimeout 的固定时间间隔不一定与屏幕刷新间隔时间相同,会引起丢帧。