# CSS 知识集锦

# 一、层叠上下文

普通元素的层叠等级优先由其所在的层叠上下文决定。

层叠等级的比较只有在当前层叠上下文元素中才有意义。不同层叠上下文中比较层叠等级是没有意义的。

# 层叠上下文创建

  • 根元素 html 创建的层叠上下文

  • postion 创建的层叠上下文

    普通元素设置 position 属性为非 static 值并设置 z-index 属性为具体数值,产生层叠上下文。

  • CSS3 创建的层叠上下文

    opacity 不为 1

    filter 不为 none

    transform 不为 none

    z-index 不为 autoflex 元素( 父元素 display : flex | inline-flex子元素z-index 为数值 )

注意

z-index: autoz-index: 0 两个数值上面是相等的, 但是意义却不一样; positionstaticz-index: 0 会产生层叠上下文, 而此时z-index: auto不产生

# 元素层叠顺序

image.png

同一层叠上下文的层叠等级才能进行比较, 同一层叠上下文中的元素还需要比较层叠顺序来确定最终的层叠等级, 层叠顺序从小到大依次为:

  • background/border最小
  • z-index 负值
  • block 元素
  • float 元素
  • inline/inline-block 元素
  • z-index=auto 或者 css3 默认的层叠上下文
  • z-index 正值

注意

当元素的层叠等级一致、层叠顺序也相同的时候,在 DOM 流中处于后面的元素会覆盖前面的元素

# 层叠等级比较

  • 首先比较两个元素是否处于同一个层叠上下文中

    1. 同一层叠上下文比较多个子元素层叠顺序

    2. 同一层叠上下文同一层叠顺序, 层叠等级大,显示在上面

    3. 不在同一层叠上下文中,请先比较他们所处的父级层叠上下文的层叠等级

  • 当两个元素层叠等级相同、层叠顺序相同时,在 DOM 结构中后面的元素层叠等级在前面元素之上。

# 二、百分比的相对性

em 相对于当前元素或当前元素继承来的字体的宽度, 对于 font-size 来说,em 相对于父元素的字体大小;line-height 中,em却相对于自身字体的大小

  • position: relative 中的百分比是相对于相对与自身的,left 相对于自己的 widthtop 相对于自己的 height

  • position: fixed 中的 的百分比是相对于视口的,left 相对于视口的 widthtop 相对于视口的 height

  • marginpadding 的百分比是相对于父元素的宽度

  • border-radius 的百分比是相对于自身宽高

  • background-size 的百分比是相对于自身宽高

  • transform: translate的百分比是相对于自身宽高

  • text-indent 的百分比是相对于父元素的宽度width

  • font-size 的百分比是相对于父元素的字体font-size数值

  • line-height 的百分比是相对于该元素的font-size数值

# 三、行高继承规则

父元素设置了line-height, 子元素应该如何继承, 主要分为以下三种情况:

  • 父元素的line-height是具体数值, 那么子元素直接继承该值

  • 父元素的line-height是比例值(如: 1.5), 则继承该比例值, 再根据子元素的font-size大小计算

  • 父元素的line-height是百分比(如: 200%), 则先通过父元素font-size计算line-height具体值后再继承

# 四、块级格式上下文

BFC(Block formatting context)直译为"块级格式化上下文"。它是一个独立的渲染区域,只有 Block-level box 参与, 它规定了内部的 Block-level Box 如何布局,并且与这个区域外部毫不相干

# BFC 的生成

CSS2.1 中规定满足下列 CSS 声明之一的元素便会生成 BFC

  • 根元素

  • float 的值不为 none

  • overflow 的值不为 visible

  • display 的值为 inline-blocktable-celltable-caption

  • position 的值为 absolutefixed

注意

display:table 也认为可以生成 BFC,其实这里的主要原因在于 Table 会默认生成一个匿名的 table-cell,正是这个匿名的 table-cell 生成了 BFC

# BFC 的约束规则

  • 内部的 Box 会在垂直方向上一个接一个的放置

  • 垂直方向上的距离由 margin 决定。(完整的说法是:属于同一个 BFC 的两个相邻 Box 的 margin 会发生重叠(塌陷),与方向无关。)

  • 每个元素的左外边距与包含块的左边界相接触(从左向右),即使浮动元素也是如此。(这说明 BFC 中子元素不会超出他的包含块,而 position 为 absolute 的元素可以超出他的包含块边界)

  • BFC 的区域不会与 float 的元素区域重叠(两栏布局应用)

  • 计算 BFC 的高度时,浮动子元素也参与计算(解决父元素高度塌陷)

  • BFC 就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面元素,反之亦然

# 五、样式表层叠规则

CSS中样式是具有层叠规则的, 通过层叠规则来判定样式的优先级, 可用来解决样式冲突, 层叠从上到下的规则为:

# 优先级

​ 作者样式表包含!import > 默认样式表包含!import > 作者样式表 > 默认样式表

# 特殊性

选择器的特殊性是由四位数字[w, x, y, z]组成

  • 第一位 w 代表是否行内样式(1 表示是, 0 表示否)

  • 第二位 x 代表ID选择器的数量和

  • 第三位 y 代表类选择器伪类选择器 属性选择器的个数和

  • 最后以为 z 代表元素选择器伪元素选择器的数量和

# 源次序

源代码中的书写顺序即为源次序, 样式表中, 如果优先级和特殊性都一致的情况下, 就看在代码中的书写先后顺序, 后写的覆盖前面

# 六、3D效果技巧

使用CSS3D效果, 可以使元素在三维空间中的动态变换, 为网页带来令人惊叹的交互体验。实现3D效果的规则主要有三条

# 嵌套HTML

首先准备三层嵌套的html结构

  • 最外层的wrapper-3d用于设置景深
  • 中间一层的container用于设置子元素处于3D平面
  • 最后一层的child用于设置3D的变换
<div class="wrapper-3d">
  <div class="container">
    <div class="item child-front">front</div>
    <div class="item child-middle">middle</div>
    <div class="item child-bottom">bottom</div>
  </div>
</div>

# 设置景深和3D平面

  • .wrapper-3d上设置视角景深perspective
  • .container元素上设置transform-style
.wrapper-3d {
  width: 200px;
  height: 200px;
  /* 设置景深, 正值贴近观察者, 负值远离观察者 */
  perspective: 600px;
}

.container {
  width: 100%;
  height: 100%;
  position: relative;
  /* 设置子元素处于3D平面 */
  transform-style: preserve-3d;
}

# 设置3D变换

给所有3D子元素上设置绝对定位和设置各个方向的3D变换(平移、旋转等等)

.item {
  position: absolute;
  width: 200px;
  height: 200px;
}
.child-front  { transform: translateZ(-100px) rotateY(   0deg); }
.child-middle { transform: translateZ(-100px) rotateX( -90deg); }
.child-bottom { transform: translateZ(-100px) rotateX(  90deg); }

总的来说, 爷爷元素设置perspective、父亲元素设置transform-style: preserve-3d、子元素们设置绝对定位和3D变换

# 七、检查溢出元素

在开发过程中,我们经常会遇到元素溢出的情况,这时候我们需要检查元素是否溢出,如果溢出了,我们还需要知道溢出的方向,这样才能更好的解决问题

那么,如何快速找到导致溢出行为的元素呢?

可以使用一个CSS属性outline, 与border所不同的是outline不会影响元素的大小和位置,它只是在元素周围绘制一条线,用于突出显示元素的边框

outline 与 border 的区别

  • 前者不占据空间,绘制于元素内容周围, 而border会占据一定的空间,可能会改变元素的大小和位置
  • 根据规范,outline 通常是矩形,但也可以是非矩形的。
/* 可以给所有元素设置一个outline */
* {
  outline: 1px solid red !important;
}

通过添加上述的CSS属性, 就可以非常直观的发现溢出元素包含块的目标元素

检查溢出元素

# 八、默认属性值

CSS中有几个属性值关键字: initialinheritunsetrevert, 用法和区别如下

  • inherit 用于继承父元素的属性值

  • initial 用于重置属性为W3C标准默认值

  • unset 清除掉浏览器的默认样式, 能使用继承值就使用继承值, 否则使用W3C标准默认值

  • revert 恢复浏览器的默认样式

# 九、动画

# 滑块动画

纯CSS实现滑块拖动动画, 利用animationdelaypaused两个关键属性

源码已经上传到作者的Demos仓库-滑块动画 (opens new window)

<style>
  .ball {
    --delay: 0s;
    width: 50px;
    height: 50px;
    border-radius: 50%;
    background-color: #f00;
    margin-bottom: 50px;
    animation: move linear forwards 1s paused;
    animation-delay: var(--delay);
  }

  @keyframes move {
    0% {
      transform: translateX(0);
    }

    100% {
      transform: translateX(100px);
    }
  }
</style>

<div class="container">
  <!-- 小球 -->
  <div class="ball"></div>
  <!-- 滑块 -->
  <input type="range" min="0" max="1" step="0.01" />
</div>

<script>
  const input = document.querySelector('input')
  const ball = document.querySelector('.ball')
  const calc = () => {
    ball.style.setProperty('--delay', `-${input.value}s`)
  }
  input.oninput = calc
  calc()
</script>

# FLIP动画

所谓FLIP动画, 也就是动画的四个步骤, FirstLastInvertPlay的缩写, 也就是先记录元素的初始状态, 然后记录元素的最终状态, 然后将最终状态反转到初始状态, 最后播放动画, 从而实现动画效果

  • First 记录要监控的元素位置
  • Last 记录元素结构变化后的位置
  • Invert 移动元素到First的位置
  • Play 使用动画还原元素到本来的位置

源码已经上传到作者的Demos仓库-FLIP动画 (opens new window)

<style>
  ul {
    padding: 20px;
    border: 1px solid #a12d02;
    width: fit-content;
  }

  li {
    list-style: none;
    height: 30px;
    width: 200px;
    border-radius: 10px;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: aquamarine;
    margin: 10px 0;
  }
</style>

<button class="first-begin">第一个元素播放FLIP</button>
<button class="full-sort-begin">全体元素排序播放FLIP</button>

<ul class="list">
  <li class="list-item" style="background:#e75723; border-color:#a12d02" order="7">7. HTML + CSS</li>
  <li class="list-item" order="6">6. Javascript</li>
  <li class="list-item" order="1">1. 网络</li>
  <li class="list-item" order="3">3. 工程化</li>
  <li class="list-item" order="5">5. 框架</li>
  <li class="list-item" order="4">4. 移动端</li>
  <li class="list-item" order="2">2. Nodejs</li>
</ul>

<script>
  // 公共元素和函数
  const list = document.querySelector('.list')

  function getLocation(elem) {
    const rect = elem.getBoundingClientRect()
    return rect.top
  }

  function raf(cb) {
    requestAnimationFrame(cb)
  }
</script>

<script>
  // 处理第一个元素的FLIP动画
  ~function handleFirstFLIP() {
    const btn = document.querySelector('.first-begin')
    const firstItem = document.querySelector('.list-item:first-child');
    const lastItem = document.querySelector('.list-item:last-child');
    const start = getLocation(firstItem)
    console.log('first', start);
    btn.onclick = () => {
      // 该行操作并不会立即绘制到浏览器, 而是等待下一个事件循环进行绘制
      list.insertBefore(firstItem, null)
      const end = getLocation(firstItem)
      console.log('end', end);

      const distance = start - end
      firstItem.style.transform = `translateY(${distance}px)`
      console.log('invert', distance)

      raf(() => {
        firstItem.style.transition = `transform 1s`
        firstItem.style.removeProperty('transform')
        console.log('play')
      })

    }
  }()

</script>

<script>
  // 实现全体元素的FLIP排序动画
  const beginBtn = document.querySelector('.full-sort-begin')
  beginBtn.onclick = () => {

    const map = Array.from(list.children).reduce((o, c, n) => {
      const index = c.getAttribute('order')
      o[index] = {
        order: index,
        el: c,
        start: getLocation(c),
        end: -1,
        dis: 0
      }
      return o
    }, {})

    const orderList = Object.values(map).sort((a, b) => b.order - a.order)
    let prev = null
    orderList.forEach(item => {
      list.insertBefore(item.el, prev)
      prev = item.el
      item.end = getLocation(item.el)
      item.dis = item.start - item.end
      item.el.style.transform = `translateY(${item.dis}px)`

      raf(() => {
        item.el.style.transition = `transform 1s`
        item.el.style.removeProperty('transform')
      })
    })
  }
</script>

# 十、案例

该部分记录一些CSS有意思的小案例

# 自动高度

有时候, 我们想在包含块的元素内部, 做一个自动高度的分界线。它的高度随着包含块的变化而自动调整,我们很容易就写出以下代码

最后发现, 作为分界线的子元素内部没有内容撑开, 导致它的实际高度为0

为了能够让该分界线能随着包含块元素撑开, 此时需要给该子元素添加min-height: inherit

::: code-group

.parent {
  width: 80%;
  min-height: 100px;
  background: rgb(128, 128, 128, 0.3);
  display: flex;
}

.left,
.right {
  height: 100%;
  width: 45%;
  padding: 4px;
  flex: 1;
}

.border {
  width: 2px;
  height: 100%; /* [!code --] */
  min-height: inherit;  /* [!code ++] */
  background: red;
}
<link rel="stylesheet" href="./style.css">
<div class="parent">
  <div class="left">
    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Enim facere iure placeat tempora
    natus quasi cumque doloremque, maxime libero porro inventore vel distinctio earum commodi sunt veniam debitis,
    doloribus labore!
  </div>

  <!-- 边界线, 无内容 -->
  <div class="border"></div>

  <div class="left">
    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Enim facere iure placeat tempora
    natus quasi cumque doloremque, maxime libero porro inventore vel distinctio earum commodi sunt veniam debitis,
    doloribus labore!
  </div>
</div>

# 十一、一些特殊属性

# transform

CSS中的transform用于旋转,缩放,倾斜或平移给定元素等变形

但值得注意的是, 并非所有的盒子都可以进行transform的转换(通常行内级元素不能进行形变)。所以,transform 对行内级非替换元素是无效的,比如spanatable中的一些元素等