跳转到内容

CSS 基础类题目

content-box:标准盒模型,width只包含content

border-box:怪异盒模型,width包含contentborderpadding

有的时候我们在使用padding时候会导致容器被左右撑开,这时候修改下box-sizing就行了。

CSS的选择器可以分如下几类:

  1. 基础选择器:最常用的核心选择器。

    • 元素选择器:如p{},选中所有<p>标签。

    • 类选择器 (Class):如 .nav{},选中所有class="nav"的元素。

    • ID选择器:如#header{},选中id="header"的元素。

    • 通配选择器:如*{},选中页面所有元素。

  2. 组合选择器:用于表达元素间的特定关系。

    • 后代选择器 (空格):如div p{},选中<div>内部所有的<p>

    • 子选择器 (>): 如ul > li{},只选中作为<ul>直接子元素的<li>

    • 相邻兄弟选择器 (+):如h2 + p{},选中紧跟在<h2>后面的第一个<p>

    • 通用兄弟选择器 (~):如h2 ~ p{},选中跟<h2>同级的所有后续<p>

  3. 属性选择器:通过元素的属性及属性值来匹配元素。

    • [attr]:选中带有attr属性的元素。

    • [attr="value"]:选中属性值等于value的元素。

    • [attr^="value"]:选中属性值以value开头的元素。

  4. 伪类与伪元素选择器:用于选择元素的特定状态或虚拟元素。

    • 伪类 (:): 定义元素的特殊状态,如 :hover(鼠标悬停)、:focus(获得焦点)、:first-child(第一个子元素)。

    • 伪元素 (::):用于创建并样式化不在文档树中的元素,如::before::after用于创建生成内容,::first-line样式化首行文本。

CSS优先级的计算基于选择器的特异性(Specificity),它决定当多个规则作用于同一元素时,哪个规则会生效。优先级由四个等级(a, b, c, d)的数值来表示,比较时从左到右(从a到d)逐级比较,级高者胜出,而不是看总和。

其计算公式为:(a, b, c, d)

  1. 内联样式 (a = 1):写在元素 style 属性中的样式,优先级最高。

    例如:<p style="color: red;"> → 特异性为 (1, 0, 0, 0)

  2. ID选择器 (b):每使用一个ID选择器,b 值+1。

    例如:#header → 特异性为 (0, 1, 0, 0)

  3. 类、伪类、属性选择器 (c):类选择器 (如 .container)、类选择器 (如:hover, :focus)、属性选择器 (如[type="text"]),每使用一个,c 值+1。

  4. 元素选择器、伪元素选择器 (d):元素(标签)选择器 (如 div, p)、伪元素选择器 (如::before, ::after),每使用一个,d 值+1。

可以参考下面示例的计算结果:

选择器示例特异性计算值以 (a,b,c,d) 表示
style="..."1,0,0,0(1,0,0,0)
#main .content p0,1,2,1(0,1,2,1)
ul#nav li.active a0,1,1,3(0,1,1,3)
h1 + p::first-letter0,0,0,3(0,0,0,3)
li:hover0,0,1,1(0,0,1,1)
div0,0,0,1(0,0,0,1)

比较时从左到右比较,值大者胜,如:

  • (0,1,0,0) 高于 (0,0,2,3) (因为 b=1 > b=0)

  • (0,1,1,0) 高于 (0,1,0,5) (因为 c=1 > c=0)

例外:在样式声明后加上!important会覆盖任何其他声明(包括内联样式),拥有最高优先级。

层叠上下文的定义可参考MDN - 层叠上下文

层叠上下文的计算往往是根据父元素来的,即在同一个父元素中进行z-index的比较,值较大者在上层。这种比较不会跨层级进行。

当渲染树中的一部分因为元素的规模、尺寸、布局、隐藏等改变而需要重新构建的过程,称为重排。这相当于浏览器需要重新计算所有元素的位置和几何信息。

重排的成本非常高,因为它是一种阻塞行为。浏览器需要从当前重排的元素开始,递归地重新计算所有子节点和后续同级节点的尺寸和位置,然后更新渲染树。这个过程会占用主线程,导致后续的JS执行和渲染被延迟,如果频繁发生,会造成页面卡顿、掉帧。

会引发重排的一些场景:

  • 页面首次渲染。

  • 添加或删除可见的DOM元素。

  • 元素尺寸、位置、内容发生改变(如width, height, padding, margin, left, top等)。

  • 浏览器窗口大小改变(resize事件)。

  • 激活CSS伪类(如:hover可能导致布局变化)。

  • JavaScript频繁地读取和修改DOM样式,引发布局抖动

当元素发生的改变只影响其外观风格(如颜色、背景色、边框颜色、可见性等),而不影响其布局时,浏览器不需要重新计算几何属性,只需要根据元素的新样式重新绘制它到屏幕上,这个过程称为重绘。

重绘跳过了布局计算和树构建阶段,直接进入绘制阶段。相较于重排,重绘的开销要小得多。

引发重绘的一些场景:修改color, background-color, border-color, visibility等样式。

布局抖动是指浏览器在短时间内被迫进行多次连续的布局(Layout) 或重排(Reflow) 操作。这是一种性能问题,会导致页面卡顿、响应缓慢,严重影响用户体验。

浏览器有渲染队列机制,连续的读/写操作会被合并。但如果在一个写操作后立即进行一个读操作(如读取offsetTop, scrollHeight, getComputedStyle等),为了得到精确的值,浏览器会立即强制清空队列并进行重排。

下面是一个反例:

// 一个非常糟糕的、会导致严重布局抖动的例子
function resizeAllParagraphsToMatchBlockWidth() {
// 获取所有段落元素
const paragraphs = document.querySelectorAll('p');
// 可怕的“读写循环”
for (let i = 0; i < paragraphs.length; i++) {
// 1. 【读】获取元素的宽度 (offsetWidth)
// 浏览器:“哦!你要读布局信息,我必须先计算一下当前的布局才能给你准确值!”
// -> 触发一次强制同步布局!
const currentWidth = paragraphs[i].offsetWidth;
// 2. 【写】修改元素的宽度 (会影响布局)
// 这个修改并不会立即触发布局,浏览器本可以稍后批量处理。
paragraphs[i].style.width = currentWidth + 'px';
}
// 循环结束后,浏览器可能还会再进行一次布局来计算所有修改后的结果。
}

在上面的例子中,每一次循环都先读 (offsetWidth),然后立即写 (style.width)。下一次循环的读操作,迫使浏览器必须处理上一次写操作造成的布局变更,以确保数据的准确性。这就导致了 N 次(元素数量)强制同步布局,性能急剧下降。

为了规避上述问题,在开发过程中需要注意:避免在修改布局之后又立即去读取布局信息,遵循“批量读写”的原则。优化后的代码如下:

function resizeAllParagraphsToMatchBlockWidth() {
const paragraphs = document.querySelectorAll('p');
// 第一阶段:批量【读】
const allWidths = [];
for (let i = 0; i < paragraphs.length; i++) {
// 一次性读取所有需要的布局信息
allWidths.push(paragraphs[i].offsetWidth);
}
// 第二阶段:批量【写】
for (let i = 0; i < paragraphs.length; i++) {
// 使用第一阶段读取的数据进行写入
paragraphs[i].style.width = allWidths[i] + 'px';
}
// 现在总共只触发了 2 次布局(1次用于批量读,1次用于处理批量写),而不是 N 次。
}

当我们要隐藏一个元素时,可以采用如下方式:

  • display: none:不占空间,完全消失,触发重绘和重排

  • opacity: 0:本质就是变透明,依旧占空间

  • visibility: hidden:占空间,但是在页面上不可见,仅触发重绘

    顺带一提,当设置元素  visibility: collapse  后,一般的元素的表现与  visibility: hidden  一样,也即其会占用空间。但如果该元素是与 table 相关的元素,其表现却跟  display: none  一样,也即其占用的空间会释放。

父容器:

  • display: flex

  • justify-contentjustify-itemsalign-itemsalign-content

  • 排列方向:flex-direction

  • 换行:flex-wrap

子容器:

  • flex-shrinkflex-basisflex-growflex

  • 排列顺序:order

  • align-self