2.13 说说对 CSS 预编语言的理解,以及它们之间的区别
一、是什么
Css
作为一门标记性语言,语法相对简单,对使用者的要求较低,但同时也带来一些问题
需要书写大量看似没有逻辑的代码,不方便维护及扩展,不利于复用,尤其对于非前端开发工程师来讲,往往会因为缺少 Css
编写经验而很难写出组织良好且易于维护的 Css
代码
Css
预处理器便是针对上述问题的解决方案
预处理语言
扩充了 Css
语言,增加了诸如变量、混合(mixin)、函数等功能,让 Css
更易维护、方便
本质上,预处理是Css
的超集
包含一套自定义的语法及一个解析器,根据这些语法定义自己的样式规则,这些规则最终会通过解析器,编译生成对应的 Css
文件
二、有哪些
Css
预编译语言在前端里面有三大优秀的预编处理器,分别是:
- sass
- less
- stylus
sass
2007 年诞生,最早也是最成熟的 Css
预处理器,拥有 Ruby 社区的支持和 Compass
这一最强大的 Css
框架,目前受 LESS
影响,已经进化到了全面兼容 Css
的 Scss
文件后缀名为.sass
与scss
,可以严格按照 sass 的缩进方式省去大括号和分号
less
2009年出现,受SASS
的影响较大,但又使用 Css
的语法,让大部分开发者和设计师更容易上手,在 Ruby
社区之外支持者远超过 SASS
其缺点是比起 SASS
来,可编程功能不够,不过优点是简单和兼容 Css
,反过来也影响了 SASS
演变到了 Scss
的时代
stylus
Stylus
是一个Css
的预处理框架,2010 年产生,来自 Node.js
社区,主要用来给 Node
项目进行 Css
预处理支持
所以 Stylus
是一种新型语言,可以创建健壮的、动态的、富有表现力的 Css
。比较年轻,其本质上做的事情与SASS/LESS
等类似
三、区别
虽然各种预处理器功能强大,但使用最多的,还是以下特性:
- 变量(variables)
- 作用域(scope)
- 代码混合( mixins)
- 嵌套(nested rules)
- 代码模块化(Modules)
因此,下面就展开这些方面的区别
基本使用
less和scss
.box { display: block; }
sass
.box display: block
stylus
.box display: block
stylus
.box display: block
嵌套
三者的嵌套语法都是一致的,甚至连引用父级选择器的标记 & 也相同
区别只是 Sass 和 Stylus 可以用没有大括号的方式书写
less
.a { &.b { color: red; } }
变量
变量无疑为 Css 增加了一种有效的复用方式,减少了原来在 Css 中无法避免的重复「硬编码」
less
声明的变量必须以@
开头,后面紧跟变量名和变量值,而且变量名和变量值需要使用冒号:
分隔开
@red: #c00; strong { color: @red; }
sass
声明的变量跟less
十分的相似,只是变量名前面使用$
开头
$red: #c00; strong { color: $red; } stylus`声明的变量没有任何的限定,可以使用`$`开头,结尾的分号`;`可有可无,但变量与变量值之间需要使用`=
在stylus
中我们不建议使用@
符号开头声明变量
red = #c00 strong color: red
作用域
Css
预编译器把变量赋予作用域,也就是存在生命周期。就像 js
一样,它会先从局部作用域查找变量,依次向上级作用域查找
sass
中不存在全局变量
$color: black; .scoped { $bg: blue; $color: white; color: $color; background-color:$bg; } .unscoped { color:$color; }
编译后
.scoped { color:white;/*是白色*/ background-color:blue; } .unscoped { color:white;/*白色(无全局变量概念)*/ }
所以,在sass
中最好不要定义相同的变量名
less
与stylus
的作用域跟javascript
十分的相似,首先会查找局部定义的变量,如果没有找到,会像冒泡一样,一级一级往下查找,直到根为止
@color: black; .scoped { @bg: blue; @color: white; color: @color; background-color:@bg; } .unscoped { color:@color; }
编译后:
.scoped { color:white;/*白色(调用了局部变量)*/ background-color:blue; } .unscoped { color:black;/*黑色(调用了全局变量)*/ }
混入
混入(mixin)应该说是预处理器最精髓的功能之一了,简单点来说,Mixins
可以将一部分样式抽出,作为单独定义的模块,被很多选择器重复使用
可以在Mixins
中定义变量或者默认参数
在less
中,混合的用法是指将定义好的ClassA
中引入另一个已经定义的Class
,也能使用够传递参数,参数变量为@
声明
.alert { font-weight: 700; } .highlight(@color: red) { font-size: 1.2em; color: @color; } .heads-up { .alert; .highlight(red); }
编译后
.alert { font-weight: 700; } .heads-up { font-weight: 700; font-size: 1.2em; color: red; }
Sass
声明mixins
时需要使用@mixinn
,后面紧跟mixin
的名,也可以设置参数,参数名为变量$
声明的形式
@mixin large-text { font: { family: Arial; size: 20px; weight: bold; } color: #ff0000; } .page-title { @include large-text; padding: 4px; margin-top: 10px; }
stylus
中的混合和前两款Css
预处理器语言的混合略有不同,他可以不使用任何符号,就是直接声明Mixins
名,然后在定义参数和默认值之间用等号(=)来连接
error(borderWidth= 2px) { border: borderWidth solid #F00; color: #F00; } .generic-error { padding: 20px; margin: 4px; error(); /* 调用error mixins */ } .login-error { left: 12px; position: absolute; top: 20px; error(5px); /* 调用error mixins,并将参数$borderWidth的值指定为5px */ }
代码模块化
模块化就是将Css
代码分成一个个模块
scss
、less
、stylus
三者的使用方法都如下所示
@import './common'; @import './github-markdown'; @import './mixin'; @import './variables';
2.14 仅使用 CSS 怎么实现宽高自适应的正方形?
在电商、个人博客等网站我们或多或少都可以看到css正方形的应用场景,自适应正方形布局是必须要掌握的。
正方形
这很简单,设置盒子宽高都为200px就可以了
<div class="box">小明写的</div> .box { width: 200px; height: 200px; background: pink; }
自适应正方形
青铜
提起自适应,想必都会想到rem、vw等相对单位,我们分别用这些单位来实现一下。
rem
<div class="rem">rem</div> body { font-size: 16px; } .rem { width: 10rem; height: 10rem; background: pink; } @media only screen and (max-width: 1200px) { body { font-size: 12px; } }
屏幕宽度大于1200px时,字体为16px,rem的盒子的大小为 160px * 160px;
屏幕宽度小于1200px时,字体为12px,rem的盒子的大小为 120px * 120px;
实际应用场景屏幕宽度变化会动态计算rem的值,这里只用于案例理解。
优点
在适配了rem的项目中,可以直接使用rem设置盒子宽高,即自适应正方形
缺点
在未设置rem的项目中,单独为了实现自适应正方形有点大材小用
vw
<div class="vw">vw</div> .vw { width: 10vw; height: 10vw; background: yellow; }
https://ucc.alicdn.com/images/user-upload-01/img_convert/6ce5e67584b59c06f88dbd7668152806.webp?x-oss-process=image/format,png
可以看到正方形跟随着屏幕宽度的变化自适应,这是我们想要的效果
优点
vw为内置视口单位,可直接使用。
缺点
在实际的业务场景中,将设计稿的尺寸转化为vw相对麻烦
黄金
使用 百分比+padding,这里有一个很细的知识点;当padding、margin取值为百分比时,百分比的值是以父元素的width为参考。
<div class="padding"></div> .padding { width: 20%; padding-top: 20%; /* padding-top或padding-bottom都可以 */ background: #696969; }
我们给盒子的宽度设置20%,使用的padding的纵轴将盒子高度撑开,这样便得到了一个自适应正方形。https://ucc.alicdn.com/images/user-upload-01/img_convert/c1f624ed27b54627efca7a9ee93c9fbe.webp?x-oss-process=image/format,png
细心的小伙伴会发现,现在是个自适应正方形没错了,但是高度被占满了,内容没地方放了。 是的,当前只是一个正方形;在实际业务场景中,内部不是图片就是其他内容,不止需要一个正方形的。
解决办法:再嵌套一层内容盒子,外层方形盒为相对定位,内层内容盒为绝对定位;内层盒宽高基于外层方形盒,代码如下
<div class="box-wrap"> <div class="box-content">我是内容区域</div> </div> .box-wrap { position: relative; width: 20%; padding-top: 20%; } .box-content { position: absolute; width: 100%; height: 100%; top: 0; left: 0; z-index: 1; background: burlywood; }
.box-content类为内容盒,我们可以在内容盒里为所欲为还原设计稿了。
若方形区域只需要展示图片,同理如下
<div class="box-wrap"> <img src="./img/A.webp" alt=""> </div> .box-wrap { position: relative; width: 20%; padding-top: 20%; } .box-wrap > img { position: absolute; width: 100%; height: 100%; top: 0; left: 0; z-index: 1; object-fit: cover; }
https://ucc.alicdn.com/images/user-upload-01/img_convert/18b57f8c27196d0451777418b6a6a9b1.webp?x-oss-process=image/format,png
这里我们就实现了一个自适应方形图片了。
优点
无需其他配置,设置灵活,扩展性强。
缺点
需要额外嵌套一层内容盒(个人拙见)
钻石
顾名思义,它很腻害;他就是css属性aspect-ratio
aspect-ratio,简言之就是宽高比。多说无益,上代码
<div class="box-square">我是内容</div> .box-square { aspect-ratio: 1 / 1; /* aspect-ratio: 1; 可简写 */ width: 20%; background: chocolate; }
一个属性,自适应正方形就OK啦;属性再好,也得看兼容性和浏览器支持。
优点
属性通俗易懂,无需其他配置,无需嵌套内容盒。
缺点
唯一缺点就是兼容性了,2023年了该支持的基本都支持上了,此事古难全!
活学活用
至此,知识点应该都掌握了吧;让我们来实现一个真实的业务需求,如图
实现这个布局,假设总宽1000px,商品卡片左右间距12px,布局要求自适应+响应式。
百分比+padding实现
<main> <div class="shop"> <div class="shop-img"><img src="./img/A.webp" alt=""></div> <div class="shop-title">这是小黎测试的文字区域,实际运用一下百分比+padding实现自适应方形布局。</div> </div> <div class="shop"> <div class="shop-img"><img src="./img/A.webp" alt=""></div> <div class="shop-title">这是小黎测试的文字区域,实际运用一下百分比+padding实现自适应方形布局。</div> </div> <div class="shop"> <div class="shop-img"><img src="./img/A.webp" alt=""></div> <div class="shop-title">这是小黎测试的文字区域,实际运用一下百分比+padding实现自适应方形布局。</div> </div> <div class="shop"> <div class="shop-img"><img src="./img/A.webp" alt=""></div> <div class="shop-title">这是小黎测试的文字区域,实际运用一下百分比+padding实现自适应方形布局。</div> </div> </main> main { max-width: 1000px; margin: 0 auto; padding: 0 12px; display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; .shop { width: calc(25% - 9px); box-shadow: 0 0 8px #0002; border-radius: 4px; overflow: hidden; .shop-img { position: relative; width: 100%; padding-top: 100%; > img { position: absolute; width: 100%; height: 100%; top: 0; left: 0; z-index: 1; object-fit: cover; } } .shop-title { padding: 8px; font-size: 14px; color: #232323; } } } @media only screen and (max-width: 768px) { main { .shop { width: calc(50% - 6px); margin-bottom: 12px; } } }
aspect-ratio实现
使用aspect-ratio了属性,我们可以少嵌套一个外层方形盒
<main> <div class="shop"> <img src="./img/A.webp" alt=""> <div class="shop-title">这是小黎测试的文字区域,实际运用aspect-ratio实现自适应方形布局。</div> </div> <div class="shop"> <img src="./img/A.webp" alt=""> <div class="shop-title">这是小黎测试的文字区域,实际运用aspect-ratio实现自适应方形布局。</div> </div> <div class="shop"> <img src="./img/A.webp" alt=""> <div class="shop-title">这是小黎测试的文字区域,实际运用aspect-ratio实现自适应方形布局。</div> </div> <div class="shop"> <img src="./img/A.webp" alt=""> <div class="shop-title">这是小黎测试的文字区域,实际运用aspect-ratio实现自适应方形布局。</div> </div> </main>
样式基本不变,改一下图片相关的部分。
main { max-width: 1000px; margin: 0 auto; padding: 0 12px; display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; .shop { width: calc(25% - 9px); box-shadow: 0 0 8px #0002; border-radius: 4px; overflow: hidden; > img { width: 100%; aspect-ratio: 1; object-fit: cover; } .shop-title { padding: 8px; font-size: 14px; color: #232323; } } } @media only screen and (max-width: 768px) { main { .shop { width: calc(50% - 6px); margin-bottom: 12px; } } }
扩展
在实际业务中,应用到的布局不止正方形,也有长方形、圆形等。
我们再来看个需求——UI规定图片区域宽高比为4:3,给的图片也是4:3。
一般情况我们的方案是:设置好图片宽度,高度自适应就OK了。这样做相对简便,但也会存在一些问题;比如图片宽高比并不是4:3,或多或少,又或者传的图不规整;这样会导致我们的页面布局不可控,所谓失之毫厘谬以千里。
这时候就牵扯到了代码健壮性和可扩展性,作为前端开发个人觉得页面应该有良好的用户体验,而不是调教用户。接下来我分别用百分比+padding、aspect-ratio等方案来实现该业务场景。
借用方形示例,这里我就只贴关键代码
百分比+padding实现
main { .shop { .shop-img { width: 100%; padding-top: 75%; } } }
aspect-ratio实现
main { .shop { > img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; } } }
2.15 怎么理解回流跟重绘?什么场景下会触发?
一、是什么
在HTML
中,每个元素都可以理解成一个盒子,在浏览器解析过程中,会涉及到回流与重绘:
- 回流:布局引擎会根据各种样式计算每个盒子在页面上的大小与位置
- 重绘:当计算好盒模型的位置、大小及其他属性后,浏览器根据每个盒子特性进行绘制
具体的浏览器解析渲染机制如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dfKKrtqV-1683893248605)(null)]
解析HTML,生成DOM树,解析CSS,生成CSSOM树
将DOM树和CSSOM树结合,生成渲染树(Render Tree)
Layout(回流):根据生成的渲染树,进行回流(Layout),得到节点的几何信息(位置,大小)
Painting(重绘):根据渲染树以及回流得到的几何信息,得到节点的绝对像素
Display:将像素发送给GPU,展示在页面上
在页面初始渲染阶段,回流不可避免的触发,可以理解成页面一开始是空白的元素,后面添加了新的元素使页面布局发生改变
当我们对 DOM 的修改引发了 DOM 几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性,然后再将计算的结果绘制出来
当我们对 DOM 的修改导致了样式的变化(color或background-color),却并未影响其几何属性时,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式,这里就仅仅触发了重绘
二、如何触发
要想减少回流和重绘的次数,首先要了解回流和重绘是如何触发的
回流触发时机
回流这一阶段主要是计算节点的位置和几何信息,那么当页面布局和几何信息发生变化的时候,就需要回流,如下面情况:
添加或删除可见的DOM元素
元素的位置发生变化
元素的尺寸发生变化(包括外边距、内边框、边框大小、高度和宽度等)
内容发生变化,比如文本变化或图片被另一个不同尺寸的图片所替代
页面一开始渲染的时候(这避免不了)
浏览器的窗口尺寸变化(因为回流是根据视口的大小来计算元素的位置和大小的)
还有一些容易被忽略的操作:获取一些特定属性的值
offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight
这些属性有一个共性,就是需要通过即时计算得到。因此浏览器为了获取这些值,也会进行回流
除此还包括getComputedStyle 方法,原理是一样的
重绘触发时机
触发回流一定会触发重绘
可以把页面理解为一个黑板,黑板上有一朵画好的小花。现在我们要把这朵从左边移到了右边,那我们要先确定好右边的具体位置,画好形状(回流),再画上它原有的颜色(重绘)
除此之外还有一些其他引起重绘行为:
- 颜色的修改
- 文本方向的修改
- 阴影的修改
浏览器优化机制
由于每次重排都会造成额外的计算消耗,因此大多数浏览器都会通过队列化修改并批量执行来优化重排过程。浏览器会将修改操作放入到队列里,直到过了一段时间或者操作达到了一个阈值,才清空队列
当你获取布局信息的操作的时候,会强制队列刷新,包括前面讲到的offsetTop
等方法都会返回最新的数据
因此浏览器不得不清空队列,触发回流重绘来返回正确的值
三、如何减少
我们了解了如何触发回流和重绘的场景,下面给出避免回流的经验:
如果想设定元素的样式,通过改变元素的 class 类名 (尽可能在 DOM 树的最里层)
避免设置多项内联样式
应用元素的动画,使用 position 属性的 fixed 值或 absolute 值(如前文示例所提)
避免使用 table 布局,table 中每个元素的大小以及内容的改动,都会导致整个 table 的重新计算
对于那些复杂的动画,对其设置 position: fixed/absolute,尽可能地使元素脱离文档流,从而减少对其他元素的影响
使用css3硬件加速,可以让transform、opacity、filters这些动画不会引起回流重绘
避免使用 CSS 的 JavaScript 表达式
在使用 JavaScript 动态插入多个节点时, 可以使用DocumentFragment. 创建后一次插入. 就能避免多次的渲染性能
但有时候,我们会无可避免地进行回流或者重绘,我们可以更好使用它们
例如,多次修改一个把元素布局的时候,我们很可能会如下操作
const el = document.getElementById('el') for(let i=0;i<10;i++) { el.style.top = el.offsetTop + 10 + "px"; el.style.left = el.offsetLeft + 10 + "px"; }
每次循环都需要获取多次offset
属性,比较糟糕,可以使用变量的形式缓存起来,待计算完毕再提交给浏览器发出重计算请求
// 缓存offsetLeft与offsetTop的值 const el = document.getElementById('el') let offLeft = el.offsetLeft, offTop = el.offsetTop // 在JS层面进行计算 for(let i=0;i<10;i++) { offLeft += 10 offTop += 10 } // 一次性将计算结果应用到DOM上 el.style.left = offLeft + "px" el.style.top = offTop + "px"
我们还可避免改变样式,使用类名去合并样式
const container = document.getElementById('container') container.style.width = '100px' container.style.height = '200px' container.style.border = '10px solid red' container.style.color = 'red'
使用类名去合并样式
<style> .basic_style { width: 100px; height: 200px; border: 10px solid red; color: red; } </style> <script> const container = document.getElementById('container') container.classList.add('basic_style') </script>
前者每次单独操作,都去触发一次渲染树更改(新浏览器不会),
都去触发一次渲染树更改,从而导致相应的回流与重绘过程
合并之后,等于我们将所有的更改一次性发出
我们还可以通过通过设置元素属性display: none,将其从页面上去掉,然后再进行后续操作,这些后续操作也不会触发回流与重绘,这个过程称为离线操作
const container = document.getElementById('container') container.style.width = '100px' container.style.height = '200px' container.style.border = '10px solid red' container.style.color = 'red'
离线操作后
let container = document.getElementById('container') container.style.display = 'none' container.style.width = '100px' container.style.height = '200px' container.style.border = '10px solid red' container.style.color = 'red' ...(省略了许多类似的后续操作) container.style.display = 'block'
2.16 z-index属性在什么情况下会失效?
通常 z-index 的使用是在有两个重叠的标签,在一定的情况下控制其中一个在另一个的上方或者下方出现。z-index值越大就越是在上层。z-index元素的position属性需要是relative,absolute或是fixed。
z-index属性在下列情况下会失效:
父元素position为relative时,子元素的z-index失效。解决:父元素position改为absolute或static;
元素没有设置position属性为非static属性。解决:设置该元素的position属性为relative,absolute或是fixed中的一种;
元素在设置z-index的同时还设置了float浮动。解决:float去除,改为display:inline-block;
20230116,有小伙伴补充:
在手机端 iOS 13 系统中,-webkit-overflow-scrolling:touch 也会使 z-index 失效,将 touch 换成 unset
具体原因可参考这篇文章: 为什么我的 z-index 又不生效了?
2.17 使用原生js实现以下效果:点击容器内的图标,图标边框变成border:1px solid red,点击空白处重置
const box = document.getElementById('box'); function isIcon(target) { return target.className.includes('icon'); } box.onclick = function(e) { e.stopPropagation(); const target = e.target; if (isIcon(target)) { target.style.border = '1px solid red'; } } const doc = document; doc.onclick = function(e) { const children = box.children; for(let i = 0; i < children.length; i++) { if (isIcon(children[i])) { children[i].style.border = 'none'; } } }
2.18 position: fixed 一定是相对于浏览器窗口进行定位吗?
不一定。
position:fixed;的元素会被移出正常文档流,并不为元素预留空间,而是通过指定元素相对于屏幕视口(viewport)的位置来指定元素位置,元素的位置在屏幕滚动时不会改变。fixed 属性会创建新的层叠上下文。
当元素祖先的 transform, perspective 或 filter 属性非 none 时,容器由视口改为该祖先。