整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

1.5 万字 CSS 基础拾遗(核心知识、常见需求)

1.5 万字 CSS 基础拾遗(核心知识、常见需求)

篇文章围绕了 CSS 的核心知识点和项目中常见的需求来展开。虽然行文偏长,但较基础,适合初级中级前端阅读,阅读的时候请适当跳过已经掌握的部分。

这篇文章断断续续写了比较久,也参考了许多优秀的文章,但或许文章里还是存在不好或不对的地方,请多多指教,可以评论里直接提出来哈。

小tip:后续内容更精彩哦。

核心概念和知识点

语法

CSS 的核心功能是将 CSS 属性设定为特定的值。一个属性与值的键值对被称为声明(declaration)。

css复制代码color: red;

而如果将一个或者多个声明用 {} 包裹起来后,那就组成了一个声明块(declaration block)。

css复制代码{
    color: red;
    text-align: center;
}

声明块如果需要作用到对应的 HTML 元素,那还需要加上选择器。选择器和声明块组成了CSS 规则集(CSS ruleset),常简称为 CSS 规则。

css复制代码span {
    color: red;
    text-align: center;
}

规则集中最后一条声明可以省略分号,但是并不建议这么做,因为容易出错。

CSS 中的注释

css复制代码/* 单行注释 */

/*
    多行
    注释
*/

在 CSS 文件中,除了注释、CSS 规则集以及 @规则 外,定义的一些别的东西都将被浏览器忽略。

@规则

CSS 规则是样式表的主体,通常样式表会包括大量的规则列表。但有时候也需要在样式表中包括其他的一些信息,比如字符集,导入其它的外部样式表,字体等,这些需要专门的语句表示。

而 @规则 就是这样的语句。CSS 里包含了以下 @规则:

  • @namespace 告诉 CSS 引擎必须考虑XML命名空间。
  • @media, 如果满足媒体查询的条件则条件规则组里的规则生效。
  • @page, 描述打印文档时布局的变化.
  • @font-face, 描述将下载的外部的字体。
  • @keyframes, 描述 CSS 动画的关键帧。
  • @document, 如果文档样式表满足给定条件则条件规则组里的规则生效。 (推延至 CSS Level 4 规范)

除了以上这几个之外,下面还将对几个比较生涩的 @规则 进行介绍。

@charset

@charset 用于定义样式表使用的字符集。它必须是样式表中的第一个元素。如果有多个 @charset 被声明,只有第一个会被使用,而且不能在HTML元素或HTML页面的 <style> 元素内使用。

注意:值必须是双引号包裹,且和

css复制代码@charset "UTF-8";

平时写样式文件都没写 @charset 规则,那这个 CSS 文件到底是用的什么字符编码的呢?

某个样式表文件到底用的是什么字符编码,浏览器有一套识别顺序(优先级由高到低):

  • 文件开头的 Byte order mark 字符值,不过一般编辑器并不能看到文件头里的 BOM 值;
  • HTTP 响应头里的 content-type 字段包含的 charset 所指定的值,比如:
  • css复制代码
  • Content-Type: text/css; charset=utf-8
  • CSS 文件头里定义的 @charset 规则里指定的字符编码;
  • <link> 标签里的 charset 属性,该条已在 HTML5 中废除;
  • 默认是 UTF-8。

@import

@import 用于告诉 CSS 引擎引入一个外部样式表。

link 和 @import 都能导入一个样式文件,它们有什么区别嘛?

  • link 是 HTML 标签,除了能导入 CSS 外,还能导入别的资源,比如图片、脚本和字体等;而 @import 是 CSS 的语法,只能用来导入 CSS;
  • link 导入的样式会在页面加载时同时加载,@import 导入的样式需等页面加载完成后再加载;
  • link 没有兼容性问题,@import 不兼容 ie5 以下;
  • link 可以通过 JS 操作 DOM 动态引入样式表改变样式,而@import不可以。

@supports

@supports 用于查询特定的 CSS 是否生效,可以结合 not、and 和 or 操作符进行后续的操作。

css复制代码/* 如果支持自定义属性,则把 body 颜色设置为变量 varName 指定的颜色 */
@supports (--foo: green) {
    body {
        color: var(--varName);
    }
}

层叠性

层叠样式表,这里的层叠怎么理解呢?其实它是 CSS 中的核心特性之一,用于合并来自多个源的属性值的算法。比如说针对某个 HTML 标签,有许多的 CSS 声明都能作用到的时候,那最后谁应该起作用呢?层叠性说的大概就是这个。

针对不同源的样式,将按照如下的顺序进行层叠,越往下优先级越高:

  • 用户代理样式表中的声明(例如,浏览器的默认样式,在没有设置其他样式时使用)。
  • 用户样式表中的常规声明(由用户设置的自定义样式。由于 Chrome 在很早的时候就放弃了用户样式表的功能,所以这里将不再考虑它的排序。)
  • 作者样式表中的常规声明(这些是我们 Web 开发人员设置的样式)。
  • 作者样式表中的 !important 声明。
  • 用户样式表中的 !important 声明S

理解层叠性的时候需要结合 CSS 选择器的优先级以及继承性来理解。比如针对同一个选择器,定义在后面的声明会覆盖前面的;作者定义的样式会比默认继承的样式优先级更高。

选择器

CSS 选择器无疑是其核心之一,对于基础选择器以及一些常用伪类必须掌握。下面列出了常用的选择器。 想要获取更多选择器的用法可以看 MDN CSS Selectors。

基础选择器

  • 标签选择器:h1
  • 类选择器:.checked
  • ID 选择器:#picker
  • 通配选择器:*

属性选择器

  • [attr]:指定属性的元素;
  • [attr=val]:属性等于指定值的元素;
  • [attr*=val]:属性包含指定值的元素;
  • [attr^=val] :属性以指定值开头的元素;
  • [attr$=val]:属性以指定值结尾的元素;
  • [attr~=val]:属性包含指定值(完整单词)的元素(不推荐使用);
  • [attr|=val]:属性以指定值(完整单词)开头的元素(不推荐使用);

组合选择器

  • 相邻兄弟选择器:A + B
  • 普通兄弟选择器:A ~ B
  • 子选择器:A > B
  • 后代选择器:A B

伪类

条件伪类

  • :lang():基于元素语言来匹配页面元素;
  • :dir():匹配特定文字书写方向的元素;
  • :has():匹配包含指定元素的元素;
  • :is():匹配指定选择器列表里的元素;
  • :not():用来匹配不符合一组选择器的元素;

行为伪类

  • :active:鼠标激活的元素;
  • :hover: 鼠标悬浮的元素;
  • ::selection:鼠标选中的元素;

状态伪类

  • :target:当前锚点的元素;
  • :link:未访问的链接元素;
  • :visited:已访问的链接元素;
  • :focus:输入聚焦的表单元素;
  • :required:输入必填的表单元素;
  • :valid:输入合法的表单元素;
  • :invalid:输入非法的表单元素;
  • :in-range:输入范围以内的表单元素;
  • :out-of-range:输入范围以外的表单元素;
  • :checked:选项选中的表单元素;
  • :optional:选项可选的表单元素;
  • :enabled:事件启用的表单元素;
  • :disabled:事件禁用的表单元素;
  • :read-only:只读的表单元素;
  • :read-write:可读可写的表单元素;
  • :blank:输入为空的表单元素;
  • :current():浏览中的元素;
  • :past():已浏览的元素;
  • :future():未浏览的元素;

结构伪类

  • :root:文档的根元素;
  • :empty:无子元素的元素;
  • :first-letter:元素的首字母;
  • :first-line:元素的首行;
  • :nth-child(n):元素中指定顺序索引的元素;
  • :nth-last-child(n):元素中指定逆序索引的元素;;
  • :first-child :元素中为首的元素;
  • :last-child :元素中为尾的元素;
  • :only-child:父元素仅有该元素的元素;
  • :nth-of-type(n) :标签中指定顺序索引的标签;
  • :nth-last-of-type(n):标签中指定逆序索引的标签;
  • :first-of-type :标签中为首的标签;
  • :last-of-type:标签中为尾标签;
  • :only-of-type:父元素仅有该标签的标签;

伪元素

  • ::before:在元素前插入内容;
  • ::after:在元素后插入内容;

优先级

优先级就是分配给指定的 CSS 声明的一个权重,它由匹配的选择器中的每一种选择器类型的数值决定。为了记忆,可以把权重分成如下几个等级,数值越大的权重越高:

  • 10000:!important;
  • 01000:内联样式;
  • 00100:ID 选择器;
  • 00010:类选择器、伪类选择器、属性选择器;
  • 00001:元素选择器、伪元素选择器;
  • 00000:通配选择器、后代选择器、兄弟选择器;

可以看到内联样式(通过元素中 style 属性定义的样式)的优先级大于任何选择器;而给属性值加上 !important 又可以把优先级提至最高,就是因为它的优先级最高,所以需要谨慎使用它,以下有些使用注意事项:

  • 一定要优先考虑使用样式规则的优先级来解决问题而不是 !important;
  • 只有在需要覆盖全站或外部 CSS 的特定页面中使用 !important;
  • 永远不要在你的插件中使用 !important;
  • 永远不要在全站范围的 CSS 代码中使用 !important;

继承性

在 CSS 中有一个很重要的特性就是子元素会继承父元素对应属性计算后的值。比如页面根元素 html 的文本颜色默认是黑色的,页面中的所有其他元素都将继承这个颜色,当申明了如下样式后,H1 文本将变成橙色。

css复制代码body {
    color: orange;
}
h1 {
    color: inherit;
}

设想一下,如果 CSS 中不存在继承性,那么我们就需要为不同文本的标签都设置一下 color,这样一来的后果就是 CSS 的文件大小就会无限增大。

CSS 属性很多,但并不是所有的属性默认都是能继承父元素对应属性的,那哪些属性存在默认继承的行为呢?一定是那些不会影响到页面布局的属性,可以分为如下几类:

  • 字体相关:font-family、font-style、font-size、font-weight 等;
  • 文本相关:text-align、text-indent、text-decoration、text-shadow、letter-spacing、word-spacing、white-space、line-height、color 等;
  • 列表相关:list-style、list-style-image、list-style-type、list-style-position 等;
  • 其他属性:visibility、cursor 等;

对于其他默认不继承的属性也可以通过以下几个属性值来控制继承行为:

  • inherit:继承父元素对应属性的计算值;
  • initial:应用该属性的默认值,比如 color 的默认值是 #000;
  • unset:如果属性是默认可以继承的,则取 inherit 的效果,否则同 initial;
  • revert:效果等同于 unset,兼容性差。

文档流

在 CSS 的世界中,会把内容按照从左到右、从上到下的顺序进行排列显示。正常情况下会把页面分割成一行一行的显示,而每行又可能由多列组成,所以从视觉上看起来就是从上到下从左到右,而这就是 CSS 中的流式布局,又叫文档流。文档流就像水一样,能够自适应所在的容器,一般它有如下几个特性:

  • 块级元素默认会占满整行,所以多个块级盒子之间是从上到下排列的;
  • 内联元素默认会在一行里一列一列的排布,当一行放不下的时候,会自动切换到下一行继续按照列排布;

如何脱离文档流呢?

脱流文档流指节点脱流正常文档流后,在正常文档流中的其他节点将忽略该节点并填补其原先空间。文档一旦脱流,计算其父节点高度时不会将其高度纳入,脱流节点不占据空间。有两种方式可以让元素脱离文档流:浮动和定位。

  • 使用浮动(float)会将元素脱离文档流,移动到容器左/右侧边界或者是另一个浮动元素旁边,该浮动元素之前占用的空间将被别的元素填补,另外浮动之后所占用的区域不会和别的元素之间发生重叠;
  • 使用绝对定位(position: absolute;)或者固定定位(position: fixed;)也会使得元素脱离文档流,且空出来的位置将自动被后续节点填补。

盒模型

在 CSS 中任何元素都可以看成是一个盒子,而一个盒子是由 4 部分组成的:内容(content)、内边距(padding)、边框(border)和外边距(margin)。

盒模型有 2 种:标准盒模型和 IE 盒模型,本别是由 W3C 和 IExplore 制定的标准。

如果给某个元素设置如下样式:

css复制代码.box {
    width: 200px;
    height: 200px;
    padding: 10px;
    border: 1px solid #eee;
    margin: 10px;
}

标准盒模型认为:盒子的实际尺寸=内容(设置的宽/高) + 内边距 + 边框

所以 .box 元素内容的宽度就为 200px,而实际的宽度则是 width + padding-left + padding-right + border-left-width + border-right-width=200 + 10 + 10 + 1 + 1=222。

IE 盒模型认为:盒子的实际尺寸=设置的宽/高=内容 + 内边距 + 边框

.box 元素所占用的实际宽度为 200px,而内容的真实宽度则是 width - padding-left - padding-right - border-left-width - border-right-width=200 - 10 - 10 - 1 - 1=178。

现在高版本的浏览器基本上默认都是使用标准盒模型,而像 IE6 这种老古董才是默认使用 IE 盒模型的。

在 CSS3 中新增了一个属性 box-sizing,允许开发者来指定盒子使用什么标准,它有 2 个值:

  • content-box:标准盒模型;
  • border-box:IE 盒模型;

视觉格式化模型

视觉格式化模型(Visual formatting model)是用来处理和在视觉媒体上显示文档时使用的计算规则。CSS 中一切皆盒子,而视觉格式化模型简单来理解就是规定这些盒子应该怎么样放置到页面中去,这个模型在计算的时候会依赖到很多的因素,比如:盒子尺寸、盒子类型、定位方案(是浮动还是定位)、兄弟元素或者子元素以及一些别的因素。

从上图中可以看到视觉格式化模型涉及到的内容很多,有兴趣深入研究的可以结合上图看这个 W3C 的文档 Visual formatting model。所以这里就简单介绍下盒子类型。

盒子类型由 display 决定,同时给一个元素设置 display 后,将会决定这个盒子的 2 个显示类型(display type):

  • outer display type(对外显示):决定了该元素本身是如何布局的,即参与何种格式化上下文;
  • inner display type(对内显示):其实就相当于把该元素当成了容器,规定了其内部子元素是如何布局的,参与何种格式化上下文;

outer display type

对外显示方面,盒子类型可以分成 2 类:block-level box(块级盒子) 和 inline-level box(行内级盒子)。

依据上图可以列出都有哪些块级和行内级盒子:

  • 块级盒子:display 为 block、list-item、table、flex、grid、flow-root 等;
  • 行内级盒子:display 为 inline、inline-block、inline-table 等;

所有块级盒子都会参与 BFC,呈现垂直排列;而所有行内级盒子都参会 IFC,呈现水平排列。

除此之外,block、inline 和 inline-block 还有什么更具体的区别呢?

block

  • 占满一行,默认继承父元素的宽度;多个块元素将从上到下进行排列;
  • 设置 width/height 将会生效;
  • 设置 padding 和 margin 将会生效;

inline

  • 不会占满一行,宽度随着内容而变化;多个 inline 元素将按照从左到右的顺序在一行里排列显示,如果一行显示不下,则自动换行;
  • 设置 width/height 将不会生效;
  • 设置竖直方向上的 padding 和 margin 将不会生效;

inline-block

  • 是行内块元素,不单独占满一行,可以看成是能够在一行里进行左右排列的块元素;
  • 设置 width/height 将会生效;
  • 设置 padding 和 margin 将会生效;

inner display type

对内方面,其实就是把元素当成了容器,里面包裹着文本或者其他子元素。container box 的类型依据 display 的值不同,分为 4 种:

  • block container:建立 BFC 或者 IFC;
  • flex container:建立 FFC;
  • grid container:建立 GFC;
  • ruby container:接触不多,不做介绍。

值得一提的是如果把 img 这种替换元素(replaced element)申明为 block 是不会产生 container box 的,因为替换元素比如 img 设计的初衷就仅仅是通过 src 把内容替换成图片,完全没考虑过会把它当成容器。

参考:

  • CSS 原理 - 你所不知道的 display
  • 格式化上下文

格式化上下文

格式化上下文(Formatting Context)是 CSS2.1 规范中的一个概念,大概说的是页面中的一块渲染区域,规定了渲染区域内部的子元素是如何排版以及相互作用的。

不同类型的盒子有不同格式化上下文,大概有这 4 类:

  • BFC (Block Formatting Context) 块级格式化上下文;
  • IFC (Inline Formatting Context) 行内格式化上下文;
  • FFC (Flex Formatting Context) 弹性格式化上下文;
  • GFC (Grid Formatting Context) 格栅格式化上下文;

其中 BFC 和 IFC 在 CSS 中扮演着非常重要的角色,因为它们直接影响了网页布局,所以需要深入理解其原理。

BFC

块格式化上下文,它是一个独立的渲染区域,只有块级盒子参与,它规定了内部的块级盒子如何布局,并且与这个区域外部毫不相干。

BFC 渲染规则

  • 内部的盒子会在垂直方向,一个接一个地放置;
  • 盒子垂直方向的距离由 margin 决定,属于同一个 BFC 的两个相邻盒子的 margin 会发生重叠;
  • 每个元素的 margin 的左边,与包含块 border 的左边相接触(对于从左往右的格式化,否则相反),即使存在浮动也是如此;
  • BFC 的区域不会与 float 盒子重叠;
  • BFC 就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。
  • 计算 BFC 的高度时,浮动元素也参与计算。

如何创建 BFC?

  • 根元素:html
  • 非溢出的可见元素:overflow 不为 visible
  • 设置浮动:float 属性不为 none
  • 设置定位:position 为 absolute 或 fixed
  • 定义成块级的非块级元素:display: inline-block/table-cell/table-caption/flex/inline-flex/grid/inline-grid

BFC 应用场景

1、 自适应两栏布局

应用原理:BFC 的区域不会和浮动区域重叠,所以就可以把侧边栏固定宽度且左浮动,而对右侧内容触发 BFC,使得它的宽度自适应该行剩余宽度。

html复制代码<div class="layout">
    <div class="aside">aside</div>
    <div class="main">main</div>
</div>
css复制代码.aside {
    float: left;
    width: 100px;
}
.main {
    <!-- 触发 BFC -->
    overflow: auto;
}

2、清除内部浮动

浮动造成的问题就是父元素高度坍塌,所以清除浮动需要解决的问题就是让父元素的高度恢复正常。而用 BFC 清除浮动的原理就是:计算 BFC 的高度时,浮动元素也参与计算。只要触发父元素的 BFC 即可。

css复制代码.parent {
    overflow: hidden;
}

3、 防止垂直 margin 合并

BFC 渲染原理之一:同一个 BFC 下的垂直 margin 会发生合并。所以如果让 2 个元素不在同一个 BFC 中即可阻止垂直 margin 合并。那如何让 2 个相邻的兄弟元素不在同一个 BFC 中呢?可以给其中一个元素外面包裹一层,然后触发其包裹层的 BFC,这样一来 2 个元素就不会在同一个 BFC 中了。

html复制代码<div class="layout">
    <div class="a">a</div>
    <div class="contain-b">
        <div class="b">b</div>
    </div>
</div>
css复制代码.demo3 .a,
.demo3 .b {
    border: 1px solid #999;
    margin: 10px;
}
.contain-b {
    overflow: hidden;
}

针对以上 3 个 示例 ,可以结合这个 BFC 应用示例 配合观看更佳。

参考:CSS 原理 - Formatting Context

IFC

IFC 的形成条件非常简单,块级元素中仅包含内联级别元素,需要注意的是当IFC中有块级元素插入时,会产生两个匿名块将父元素分割开来,产生两个 IFC。

IFC 渲染规则

  • 子元素在水平方向上一个接一个排列,在垂直方向上将以容器顶部开始向下排列;
  • 节点无法声明宽高,其中 margin 和 padding 在水平方向有效在垂直方向无效;
  • 节点在垂直方向上以不同形式对齐;
  • 能把在一行上的框都完全包含进去的一个矩形区域,被称为该行的线盒(line box)。线盒的宽度是由包含块(containing box)和与其中的浮动来决定;
  • IFC 中的 line box 一般左右边贴紧其包含块,但 float 元素会优先排列。
  • IFC 中的 line box 高度由 line-height 计算规则来确定,同个 IFC 下的多个 line box 高度可能会不同;
  • 当内联级盒子的总宽度少于包含它们的 line box 时,其水平渲染规则由 text-align 属性值来决定;
  • 当一个内联盒子超过父元素的宽度时,它会被分割成多盒子,这些盒子分布在多个 line box 中。如果子元素未设置强制换行的情况下,inline box 将不可被分割,将会溢出父元素。

针对如上的 IFC 渲染规则,你是不是可以分析下下面这段代码的 IFC 环境是怎么样的呢?

html复制代码<p>It can get <strong>very complicated</storng> once you start looking into it.</p>

对应上面这样一串 HTML 分析如下:

  • p 标签是一个 block container,对内将产生一个 IFC;
  • 由于一行没办法显示完全,所以产生了 2 个线盒(line box);线盒的宽度就继承了 p 的宽度;高度是由里面的内联盒子的 line-height 决定;
  • It can get:匿名的内联盒子;
  • very complicated:strong 标签产生的内联盒子;
  • once you start:匿名的内联盒子;
  • looking into it.:匿名的内联盒子。

参考:Inline formatting contexts

IFC 应用场景

  • 水平居中:当一个块要在环境中水平居中时,设置其为 inline-block 则会在外层产生 IFC,通过 text-align 则可以使其水平居中。
  • 垂直居中:创建一个 IFC,用其中一个元素撑开父元素的高度,然后设置其 vertical-align: middle,其他行内元素则可以在此父元素下垂直居中。

偷个懒,demo 和图我就不做了。

层叠上下文

在电脑显示屏幕上的显示的页面其实是一个三维的空间,水平方向是 X 轴,竖直方向是 Y 轴,而屏幕到眼睛的方向可以看成是 Z 轴。众 HTML 元素依据自己定义的属性的优先级在 Z 轴上按照一定的顺序排开,而这其实就是层叠上下文所要描述的东西。

我们对层叠上下文的第一印象可能要来源于 z-index,认为它的值越大,距离屏幕观察者就越近,那么层叠等级就越高,事实确实是这样的,但层叠上下文的内容远非仅仅如此:

  • z-index 能够在层叠上下文中对元素的堆叠顺序其作用是必须配合定位才可以;
  • 除了 z-index 之外,一个元素在 Z 轴上的显示顺序还受层叠等级和层叠顺序影响;

在看层叠等级和层叠顺序之前,我们先来看下如何产生一个层叠上下文,特定的 HTML 元素或者 CSS 属性产生层叠上下文,MDN 中给出了这么一个列表,符合以下任一条件的元素都会产生层叠上下文:

  • html 文档根元素
  • 声明 position: absolute/relative 且 z-index 值不为 auto 的元素;
  • 声明 position: fixed/sticky 的元素;
  • flex 容器的子元素,且 z-index 值不为 auto;
  • grid 容器的子元素,且 z-index 值不为 auto;
  • opacity 属性值小于 1 的元素;
  • mix-blend-mode 属性值不为 normal 的元素;
  • 以下任意属性值不为 none 的元素: transform filter perspective clip-path mask / mask-image / mask-border
  • isolation 属性值为 isolate 的元素;
  • -webkit-overflow-scrolling 属性值为 touch 的元素;
  • will-change 值设定了任一属性而该属性在 non-initial 值时会创建层叠上下文的元素;
  • contain 属性值为 layout、paint 或包含它们其中之一的合成值(比如 contain: strict、contain: content)的元素。

层叠等级

层叠等级指节点在三维空间 Z 轴上的上下顺序。它分两种情况:

  • 在同一个层叠上下文中,它描述定义的是该层叠上下文中的层叠上下文元素在 Z 轴上的上下顺序;
  • 在其他普通元素中,它描述定义的是这些普通元素在 Z 轴上的上下顺序;

普通节点的层叠等级优先由其所在的层叠上下文决定,层叠等级的比较只有在当前层叠上下文中才有意义,脱离当前层叠上下文的比较就变得无意义了。

层叠顺序

在同一个层叠上下文中如果有多个元素,那么他们之间的层叠顺序是怎么样的呢?

以下这个列表越往下层叠优先级越高,视觉上的效果就是越容易被用户看到(不会被其他元素覆盖):

  • 层叠上下文的 border 和 background
  • z-index < 0 的子节点
  • 标准流内块级非定位的子节点
  • 浮动非定位的子节点
  • 标准流内行内非定位的子节点
  • z-index: auto/0 的子节点
  • z-index > 0的子节点

如何比较两个元素的层叠等级?

  • 在同一个层叠上下文中,比较两个元素就是按照上图的介绍的层叠顺序进行比较。
  • 如果不在同一个层叠上下文中的时候,那就需要比较两个元素分别所处的层叠上下文的等级。
  • 如果两个元素都在同一个层叠上下文,且层叠顺序相同,则在 HTML 中定义越后面的层叠等级越高。

参考:彻底搞懂CSS层叠上下文、层叠等级、层叠顺序、z-index

值和单位

CSS 的声明是由属性和值组成的,而值的类型有许多种:

  • 数值:长度值 ,用于指定例如元素 width、border-width、font-size 等属性的值;
  • 百分比:可以用于指定尺寸或长度,例如取决于父容器的 width、height 或默认的 font-size;
  • 颜色:用于指定 background-color、color 等;
  • 坐标位置:以屏幕的左上角为坐标原点定位元素的位置,比如常见的 background-position、top、right、bottom 和 left 等属性;
  • 函数:用于指定资源路径或背景图片的渐变,比如 url()、linear-gradient() 等;

而还有些值是需要带单位的,比如 width: 100px,这里的 px 就是表示长度的单位,长度单位除了 px 外,比较常用的还有 em、rem、vw/vh 等。那他们有什么区别呢?又应该在什么时候使用它们呢?

px

屏幕分辨率是指在屏幕的横纵方向上的像素点数量,比如分辨率 1920×1080 意味着水平方向含有 1920 个像素数,垂直方向含有 1080 个像素数。

而 px 表示的是 CSS 中的像素,在 CSS 中它是绝对的长度单位,也是最基础的单位,其他长度单位会自动被浏览器换算成 px。但是对于设备而言,它其实又是相对的长度单位,比如宽高都为 2px,在正常的屏幕下,其实就是 4 个像素点,而在设备像素比(devicePixelRatio) 为 2 的 Retina 屏幕下,它就有 16 个像素点。所以屏幕尺寸一致的情况下,屏幕分辨率越高,显示效果就越细腻。

讲到这里,还有一些相关的概念需要理清下:

设备像素(Device pixels)

设备屏幕的物理像素,表示的是屏幕的横纵有多少像素点;和屏幕分辨率是差不多的意思。

设备像素比(DPR)

设备像素比表示 1 个 CSS 像素等于几个物理像素。

计算公式:DPR=物理像素数 / 逻辑像素数;

在浏览器中可以通过 window.devicePixelRatio 来获取当前屏幕的 DPR。

像素密度(DPI/PPI)

像素密度也叫显示密度或者屏幕密度,缩写为 DPI(Dots Per Inch) 或者 PPI(Pixel Per Inch)。从技术角度说,PPI 只存在于计算机显示领域,而 DPI 只出现于打印或印刷领域。

计算公式:像素密度=屏幕对角线的像素尺寸 / 物理尺寸

比如,对于分辨率为 750 * 1334 的 iPhone 6 来说,它的像素密度为:

js复制代码Math.sqrt(750 * 750 + 1334 * 1334) / 4.7=326ppi

设备独立像素(DIP)

DIP 是特别针对 Android设备而衍生出来的,原因是安卓屏幕的尺寸繁多,因此为了显示能尽量和设备无关,而提出的这个概念。它是基于屏幕密度而计算的,认为当屏幕密度是 160 的时候,px=DIP。

计算公式:dip=px * 160 / dpi

em

em 是 CSS 中的相对长度单位中的一个。居然是相对的,那它到底是相对的谁呢?它有 2 层意思:

  • 在 font-size 中使用是相对于父元素的 font-size 大小,比如父元素 font-size: 16px,当给子元素指定 font-size: 2em 的时候,经过计算后它的字体大小会是 32px;
  • 在其他属性中使用是相对于自身的字体大小,如 width/height/padding/margin 等;

我们都知道每个浏览器都会给 HTML 根元素 html 设置一个默认的 font-size,而这个值通常是 16px。这也就是为什么 1em=16px 的原因所在了。

em 在计算的时候是会层层计算的,比如:

html复制代码<div>
    <p></p>
</div>
css复制代码div { font-size: 2em; }
p { font-size: 2em; }

对于如上一个结构的 HTML,由于根元素 html 的字体大小是 16px,所以 p 标签最终计算出来后的字体大小会是 16 * 2 * 2=64px

rem

rem(root em) 和 em 一样,也是一个相对长度单位,不过 rem 相对的是 HTML 的根元素 html。

rem 由于是基于 html 的 font-size 来计算,所以通常用于自适应网站或者 H5 中。

比如在做 H5 的时候,前端通常会让 UI 给 750px 宽的设计图,而在开发的时候可以基于 iPhone X 的尺寸 375px * 812px 来写页面,这样一来的话,就可以用下面的 JS 依据当前页面的视口宽度自动计算出根元素 html 的基准 font-size 是多少。

js复制代码(function (doc, win) {
    var docEl=doc.documentElement,
        resizeEvt='orientationchange' in window ? 'orientationchange' : 'resize',
        psdWidth=750,  // 设计图宽度
        recalc=function () {
            var clientWidth=docEl.clientWidth;
            if ( !clientWidth ) return;
            if ( clientWidth >=640 ) {
                docEl.style.fontSize=200 * ( 640 / psdWidth ) + 'px';
            } else {
                docEl.style.fontSize=200 * ( clientWidth / psdWidth ) + 'px';
            }
        };

    if ( !doc.addEventListener ) return;
    // 绑定事件的时候最好配合防抖函数
    win.addEventListener( resizeEvt, debounce(recalc, 1000), false );
    doc.addEventListener( 'DOMContentLoaded', recalc, false );
    
    function debounce(func, wait) {
        var timeout;
        return function () {
            var context=this;
            var args=arguments;
            clearTimeout(timeout)
            timeout=setTimeout(function(){
                func.apply(context, args)
            }, wait);
        }
    }
})(document, window);

比如当视口是 375px 的时候,经过计算 html 的 font-size 会是 100px,这样有什么好处呢?好处就是方便写样式,比如从设计图量出来的 header 高度是 50px 的,那我们写样式的时候就可以直接写:

css复制代码header {
    height: 0.5rem;
}

每个从设计图量出来的尺寸只要除于 100 即可得到当前元素的 rem 值,都不用经过计算,非常方便。偷偷告诉你,如果你把上面那串计算 html 标签 font-size 的 JS 代码中的 200 替换成 2,那在计算 rem 的时候就不需要除于 100 了,从设计图量出多大 px,就直接写多少个 rem。

vw/vh

vw 和 vh 分别是相对于屏幕视口宽度和高度而言的长度单位:

  • 1vw=视口宽度均分成 100 份中 1 份的长度;
  • 1vh=视口高度均分成 100 份中 1 份的长度;

在 JS 中 100vw=window.innerWidth,100vh=window.innerHeight。

vw/vh 的出现使得多了一种写自适应布局的方案,开发者不再局限于 rem 了。

相对视口的单位,除了 vw/vh 外,还有 vmin 和 vmax:

  • vmin:取 vw 和 vh 中值较小的;
  • vmax:取 vw 和 vh 中值较大的;

颜色体系

CSS 中用于表示颜色的值种类繁多,足够构成一个体系,所以这里就专门拿出一个小节来讲解它。

根据 CSS 颜色草案 中提到的颜色值类型,大概可以把它们分为这几类:

  • 颜色关键字
  • transparent 关键字
  • currentColor 关键字
  • RGB 颜色
  • HSL 颜色

颜色关键字

颜色关键字(color keywords)是不区分大小写的标识符,它表示一个具体的颜色,比如 white(白),黑(black)等;

可接受的关键字列表在CSS的演变过程中发生了改变:

  • CSS 标准 1 只接受 16 个基本颜色,称为 VGA 颜色,因为它们来源于 VGA 显卡所显示的颜色集合而被称为 VGA colors (视频图形阵列色彩)。
  • CSS 标准 2 增加了 orange 关键字。
  • 从一开始,浏览器接受其它的颜色,由于一些早期浏览器是 X11 应用程序,这些颜色大多数是 X11 命名的颜色列表,虽然有一点不同。SVG 1.0 是首个正式定义这些关键字的标准;CSS 色彩标准 3 也正式定义了这些关键字。它们经常被称作扩展的颜色关键字, X11 颜色或 SVG 颜色 。
  • CSS 颜色标准 4 添加可 rebeccapurple 关键字来纪念 web 先锋 Eric Meyer。

如下这张图是 16 个基础色,又叫 VGA 颜色。截止到目前为止 CSS 颜色关键字总共有 146 个,这里可以查看 完整的色彩关键字列表。

需要注意的是如果声明的时候的颜色关键字是错误的,浏览器会忽略它。

transparent 关键字

transparent 关键字表示一个完全透明的颜色,即该颜色看上去将是背景色。从技术上说,它是带有 alpha 通道为最小值的黑色,是 rgba(0,0,0,0) 的简写。

透明关键字有什么应用场景呢?

实现三角形

下面这个图是用 4 条边框填充的正方形,看懂了它你大概就知道该如何用 CSS 写三角形了。

css复制代码div {
    border-top-color: #ffc107;
    border-right-color: #00bcd4;
    border-bottom-color: #e26b6b;
    border-left-color: #cc7cda;
    border-width: 50px;
    border-style: solid;
}

用 transparent 实现三角形的原理:

  • 首先宽高必须是 0px,通过边框的粗细来填充内容;
  • 那条边需要就要加上颜色,而不需要的边则用 transparent;
  • 想要什么样姿势的三角形,完全由上下左右 4 条边的中有颜色的边和透明的边的位置决定;
  • 等腰三角形:设置一条边有颜色,然后紧挨着的 2 边是透明,且宽度是有颜色边的一半;直角三角形:设置一条边有颜色,然后紧挨着的任何一边透明即可。

看下示例:

增大点击区域

常常在移动端的时候点击的按钮的区域特别小,但是由于现实效果又不太好把它做大,所以常用的一个手段就是通过透明的边框来增大按钮的点击区域:

css复制代码.btn {
    border: 5px solid transparent;
}

currentColor 关键字

currentColor 会取当前元素继承父级元素的文本颜色值或声明的文本颜色值,即 computed 后的 color 值。

比如,对于如下 CSS,该元素的边框颜色会是 red:

css复制代码.btn {
    color: red;
    border: 1px solid currentColor;
}

RGB[A] 颜色

RGB[A] 颜色是由 R(red)-G(green)-B(blue)-A(alpha) 组成的色彩空间。

在 CSS 中,它有两种表示形式:

  • 十六进制符号;
  • 函数符;

十六进制符号

RGB 中的每种颜色的值范围是 00~ff,值越大表示颜色越深。所以一个颜色正常是 6 个十六进制字符加上 # 组成,比如红色就是 #ff0000。

如果 RGB 颜色需要加上不透明度,那就需要加上 alpha 通道的值,它的范围也是 00~ff,比如一个带不透明度为 67% 的红色可以这样写 #ff0000aa。

使用十六进制符号表示颜色的时候,都是用 2 个十六进制表示一个颜色,如果这 2 个字符相同,还可以缩减成只写 1 个,比如,红色 #f00;带 67% 不透明度的红色 #f00a。

函数符

当 RGB 用函数表示的时候,每个值的范围是 0~255 或者 0%~100%,所以红色是 rgb(255, 0, 0), 或者 rgb(100%, 0, 0)。

如果需要使用函数来表示带不透明度的颜色值,值的范围是 0~1 及其之间的小数或者 0%~100%,比如带 67% 不透明度的红色是 rgba(255, 0, 0, 0.67) 或者 rgba(100%, 0%, 0%, 67%)

需要注意的是 RGB 这 3 个颜色值需要保持一致的写法,要嘛用数字要嘛用百分比,而不透明度的值的可以不用和 RGB 保持一致写法。比如 rgb(100%, 0, 0) 这个写法是无效的;而 rgb(100%, 0%, 0%, 0.67) 是有效的。

在第 4 代 CSS 颜色标准中,新增了一种新的函数写法,即可以把 RGB 中值的分隔逗号改成空格,而把 RGB 和 alpha 中的逗号改成 /,比如带 67% 不透明度的红色可以这样写 rgba(255 0 0 / 0.67)。另外还把 rgba 的写法合并到 rgb 函数中了,即 rgb 可以直接写带不透明度的颜色。

HSL[A] 颜色

HSL[A] 颜色是由色相(hue)-饱和度(saturation)-亮度(lightness)-不透明度组成的颜色体系。

  • 色相(H)是色彩的基本属性,值范围是 0360 或者 0deg360deg, 0 (或 360) 为红色, 120 为绿色, 240 为蓝色;
  • 饱和度(S)是指色彩的纯度,越高色彩越纯,低则逐渐变灰,取 0~100% 的数值;0% 为灰色, 100% 全色;
  • 亮度(L),取 0~100%,0% 为暗,100% 为白;
  • 不透明度(A),取 0100%,或者01及之间的小数;

写法上可以参考 RGB 的写法,只是参数的值不一样。

给一个按钮设置不透明度为 67% 的红色的 color 的写法,以下全部写法效果一致:

css复制代码button {
    color: #ff0000aa;
    color: #f00a;
    color: rgba(255, 0, 0, 0.67);
    color: rgb(100% 0% 0% / 67%);
    color: hsla(0, 100%, 50%, 67%);
    color: hsl(0deg 100% 50% / 67%);
}

小提示:在 Chrome DevTools 中可以按住 shift + 鼠标左键可以切换颜色的表示方式。

媒体查询

媒体查询是指针对不同的设备、特定的设备特征或者参数进行定制化的修改网站的样式。

你可以通过给 <link> 加上 media 属性来指定该样式文件只能对什么设备生效,不指定的话默认是 all,即对所有设备都生效:

html复制代码<link rel="stylesheet" src="styles.css" media="screen" />
<link rel="stylesheet" src="styles.css" media="print" />

都支持哪些设备类型?

  • all:适用于所有设备;
  • print:适用于在打印预览模式下在屏幕上查看的分页材料和文档;
  • screen:主要用于屏幕;
  • speech:主要用于语音合成器。

需要注意的是:通过 media 指定的 资源尽管不匹配它的设备类型,但是浏览器依然会加载它。

除了通过 <link> 让指定设备生效外,还可以通过 @media 让 CSS 规则在特定的条件下才能生效。响应式页面就是使用了 @media 才让一个页面能够同时适配 PC、Pad 和手机端。

css复制代码@media (min-width: 1000px) {}

媒体查询支持逻辑操作符:

  • and:查询条件都满足的时候才生效;
  • not:查询条件取反;
  • only:整个查询匹配的时候才生效,常用语兼容旧浏览器,使用时候必须指定媒体类型;
  • 逗号或者 or:查询条件满足一项即可匹配;

媒体查询还支持众多的媒体特性,使得它可以写出很复杂的查询条件:

css复制代码/* 用户设备的最小高度为680px或为纵向模式的屏幕设备 */
@media (min-height: 680px), screen and (orientation: portrait) {}

常见需求

自定义属性

之前我们通常是在预处理器里才可以使用变量,而现在 CSS 里也支持了变量的用法。通过自定义属性就可以在想要使用的地方引用它。

自定义属性也和普通属性一样具有级联性,申明在 :root 下的时候,在全文档范围内可用,而如果是在某个元素下申明自定义属性,则只能在它及它的子元素下才可以使用。

自定义属性必须通过 --x 的格式申明,比如:--theme-color: red; 使用自定义属性的时候,需要用 var 函数。比如:

css复制代码<!-- 定义自定义属性 -->
:root {
    --theme-color: red;
}

<!-- 使用变量 -->
h1 {
    color: var(--theme-color);
}

上图这个是使用 CSS 自定义属性配合 JS 实现的动态调整元素的 box-shadow,具体可以看这个 codepen demo。

1px 边框解决方案

Retina 显示屏比普通的屏幕有着更高的分辨率,所以在移动端的 1px 边框就会看起来比较粗,为了美观通常需要把这个线条细化处理。这里有篇文章列举了 7 中方案可以参考一下:7种方法解决移动端Retina屏幕1px边框问题

而这里附上最后一种通过伪类和 transform 实现的相对完美的解决方案:

只设置单条底部边框:

css复制代码.scale-1px-bottom {
    position: relative;
    border:none;
}
.scale-1px-bottom::after {
    content: '';
    position: absolute;
    left: 0;
    bottom: 0;
    background: #000;
    width: 100%;
    height: 1px;
    -webkit-transform: scaleY(0.5);
    transform: scaleY(0.5);
    -webkit-transform-origin: 0 0;
    transform-origin: 0 0;
}

同时设置 4 条边框:

css复制代码.scale-1px {
    position: relative;
    margin-bottom: 20px;
    border:none;
}
.scale-1px::after {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    border: 1px solid #000;
    -webkit-box-sizing: border-box;
    box-sizing: border-box;
    width: 200%;
    height: 200%;
    -webkit-transform: scale(0.5);
    transform: scale(0.5);
    -webkit-transform-origin: left top;
    transform-origin: left top;
}

清除浮动

什么是浮动:浮动元素会脱离文档流并向左/向右浮动,直到碰到父元素或者另一个浮动元素。

为什么要清楚浮动,它造成了什么问题?

因为浮动元素会脱离正常的文档流,并不会占据文档流的位置,所以如果一个父元素下面都是浮动元素,那么这个父元素就无法被浮动元素所撑开,这样一来父元素就丢失了高度,这就是所谓的浮动造成的父元素高度坍塌问题。

父元素高度一旦坍塌将对后面的元素布局造成影响,为了解决这个问题,所以需要清除浮动,让父元素恢复高度,那该如何做呢?

这里介绍两种方法:通过 BFC 来清除、通过 clear 来清除。

BFC 清除浮动

前面介绍 BFC 的时候提到过,计算 BFC 高度的时候浮动子元素的高度也将计算在内,利用这条规则就可以清楚浮动。

假设一个父元素 parent 内部只有 2 个子元素 child,且它们都是左浮动的,这个时候 parent 如果没有设置高度的话,因为浮动造成了高度坍塌,所以 parent 的高度会是 0,此时只要给 parent 创造一个 BFC,那它的高度就能恢复了。

而产生 BFC 的方式很多,我们可以给父元素设置overflow: auto 来简单的实现 BFC 清除浮动,但是为了兼容 IE 最好用 overflow: hidden。

css复制代码.parent {
    overflow: hidden;
}

通过 overflow: hidden 来清除浮动并不完美,当元素有阴影或存在下拉菜单的时候会被截断,所以该方法使用比较局限。

通过 clear 清除浮动

我先把结论贴出来:

css复制代码.clearfix {
    zoom: 1;
}
.clearfix::after {
    content: "";
    display: block;
    clear: both;
}

这种写法的核心原理就是通过 ::after 伪元素为在父元素的最后一个子元素后面生成一个内容为空的块级元素,然后通过 clear 将这个伪元素移动到所有它之前的浮动元素的后面,画个图来理解一下。

可以结合这个 codepen demo 一起理解上图的 clear 清楚浮动原理。

上面这个 demo 或者图里为了展示需要所以给伪元素的内容设置为了 ::after,实际使用的时候需要设置为空字符串,让它的高度为 0,从而父元素的高度都是由实际的子元素撑开。

该方式基本上是现在人人都在用的清除浮动的方案,非常通用。

参考:CSS中的浮动和清除浮动,梳理一下

消除浏览器默认样式

针对同一个类型的 HTML 标签,不同的浏览器往往有不同的表现,所以在网站制作的时候,开发者通常都是需要将这些浏览器的默认样式清除,让网页在不同的浏览器上能够保持一致。

针对清除浏览器默认样式这件事,在很早之前 CSS 大师 Eric A. Meyer 就干过。它就是写一堆通用的样式用来重置浏览器默认样式,这些样式通常会放到一个命名为 reset.css 文件中。比如大师的 reset.css 是这么写的:

css复制代码html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed, 
figure, figcaption, footer, header, hgroup, 
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
    margin: 0;
    padding: 0;
    border: 0;
    font-size: 100%;
    font: inherit;
    vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure, 
footer, header, hgroup, menu, nav, section {
    display: block;
}
body {
    line-height: 1;
}
ol, ul {
    list-style: none;
}
blockquote, q {
    quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
    content: '';
    content: none;
}
table {
    border-collapse: collapse;
    border-spacing: 0;
}

他的这份 reset.css 据说是被使用最广泛的重设样式的方案了。

除了 reset.css 外,后来又出现了 Normalize.css 。关于 Normalize.css, 其作者 necolas 专门写了一篇文章介绍了它,并谈到了它和 reset.css 的区别。这个是他写那篇文章的翻译版:让我们谈一谈 Normalize.css。

文章介绍到:Normalize.css 只是一个很小的CSS文件,但它在默认的 HTML 元素样式上提供了跨浏览器的高度一致性。相比于传统的 CSS reset,Normalize.css 是一种现代的、为 HTML5 准备的优质替代方案,现在已经有很多知名的框架和网站在使用它了。

Normalize.css 的具体样式可以看这里 Normalize.css

区别于 reset.css,Normalize.css 有如下特点:

  • reset.css 几乎为所有标签都设置了默认样式,而 Normalize.css 则是有选择性的保护了部分有价值的默认值;
  • 修复了很多浏览器的 bug,而这是 reset.css 没做到的;
  • 不会让你的调试工具变的杂乱,相反 reset.css 由于设置了很多默认值,所以在浏览器调试工具中往往会看到一大堆的继承样式,显得很杂乱;
  • Normalize.css 是模块化的,所以可以选择性的去掉永远不会用到的部分,比如表单的一般化;
  • Normalize.css 有详细的说明文档;

长文本处理

默认:字符太长溢出了容器

字符超出部分换行

字符超出位置使用连字符

单行文本超出省略

多行文本超出省略

查看以上这些方案的示例: codepen demo

有意思的是刚好前两天看到 chokcoco 针对文本溢出也写了一篇文章,主要突出的是对整块的文本溢出处理。啥叫整块文本?比如,下面这种技术标签就是属于整块文本:

另外他还对 iOS/Safari 做了兼容处理,感兴趣的可以去阅读下:CSS 整块文本溢出省略特性探究。

水平垂直居中

让元素在父元素中呈现出水平垂直居中的形态,无非就 2 种情况:

  • 单行的文本、inline 或者 inline-block 元素;
  • 固定宽高的块级盒子;
  • 不固定宽高的块级盒子;

以下列到的所有水平垂直居中方案这里写了个 codepen demo,配合示例阅读效果更佳。

单行的文本、inline 或 inline-block 元素

水平居中

此类元素需要水平居中,则父级元素必须是块级元素(block level),且父级元素上需要这样设置样式:

css复制代码.parent {
    text-align: center;
}

垂直居中

方法一:通过设置上下内间距一致达到垂直居中的效果:

css复制代码.single-line {
    padding-top: 10px;
    padding-bottom: 10px;
}

方法二:通过设置 height 和 line-height 一致达到垂直居中:

css复制代码.single-line {
    height: 100px;
    line-height: 100px;
}

固定宽高的块级盒子

方法一:absolute + 负 margin

方法二:absolute + margin auto

方法三:absolute + calc

不固定宽高的块级盒子

这里列了 6 种方法,参考了颜海镜 写的文章 ,其中的两种 line-height 和 writing-mode 方案看后让我惊呼:还有这种操作?学到了学到了。

方法一:absolute + transform

方法二:line-height + vertical-align

方法三:writing-mode

方法四:table-cell

方法五:flex

方法六:grid

常用布局

两栏布局(边栏定宽主栏自适应)

针对以下这些方案写了几个示例: codepen demo

方法一:float + overflow(BFC 原理)

方法二:float + margin

方法三:flex

方法四:grid

三栏布局(两侧栏定宽主栏自适应)

针对以下这些方案写了几个示例: codepen demo

方法一:圣杯布局

方法二:双飞翼布局

方法三:float + overflow(BFC 原理)

方法四:flex

方法五:grid

多列等高布局

结合示例阅读更佳:codepen demo

方法一:padding + 负margin

方法二:设置父级背景图片

三行布局(头尾定高主栏自适应)

列了 4 种方法,都是基于如下的 HTML 和 CSS 的,结合示例阅读效果更佳:codepen demo

html复制代码<div class="layout">
    <header></header>
    <main>
        <div class="inner"></div>
    </main>
    <footer></footer>
</div>
css复制代码html,
body,
.layout {
    height: 100%;
}
body {
    margin: 0;
}
header, 
footer {
    height: 50px;
}
main {
    overflow-y: auto;
}

方法一:calc

方法二:absolute

方法三:flex

方法四:grid

结了个尾

这是我断断续续写了 2 周完成的文章,算是自己对 CSS 的一个总结,虽然写得很长,但不足以覆盖所有 CSS 的知识,比如动画和一些 CSS3 的新特性就完全没涉及,因为这要写下来估计得有大几万字(其实就是懒 )。

码字作图不易,如果喜欢或者对你有丝毫帮助的话,帮忙点个 哈,点赞就是我的动力。同时也希望自己能坚持认真的写下去,因为在总结提升自己的同时如果也能帮助更多的前端er,那将会让我感觉很开心。


作者:大海我来了
链接:https://juejin.cn/post/6941206439624966152

. 前言

1.1 前端安全攻防的意义

前端安全攻防的意义主要体现在以下几个方面:

  1. 用户隐私保护:前端作为用户最先接触到的界面,需要保障用户私密信息(例如密码、身份证号等)的安全,避免泄露或被窃取。
  2. 商业机密保护:页面结构、设计、业务流程等方面的保密性也同样需要被保护,防止敏感信息被恶意利用。
  3. 防范攻击:前端攻击面广、攻击方式多样,XSS、CSRF、Clickjacking等攻击手段层出不穷,因此需要从前端层面考虑各种攻击手段,提升系统安全性。
  4. 维护业务正常运行:前端代码的安全性不仅仅涉及到网页安全,还会对业务运行产生影响。比如,前端的RxJS代码被篡改,会导致业务数据异常或丢失;前端的路由方式被窃取,会对系统性能造成不良影响。

综上所述,前端安全攻防的意义非常重要,必须要引起开发者的足够重视与关注,从前端层面保障用户和业务的安全。

1.2 概述前端安全攻防的范畴和流程

前端安全攻防的范畴主要包括 JavaScript、CSS、HTML 等前端代码,以及网络通信协议和构建过程等多个层面。攻击手段目前主要涵盖 XSS、CSRF、Clickjacking 等方式。攻防流程主要包括:安全评估、漏洞挖掘、修复漏洞和安全培训等环节。

具体而言,前端安全攻防的流程包括以下步骤:

  1. 安全评估:了解前端代码的攻击面,评估系统安全性,确定攻击面的重要性以及安全防范措施的缺陷等。
  2. 漏洞挖掘:主要包括源码审计、代码分析、渗透测试、模拟攻击等方式,检测系统漏洞并寻找风险点。
  3. 修复漏洞:对找到的漏洞逐一修复,并针对攻击面提出相应的防范措施,如过滤、加密、验证等。
  4. 安全培训:加强对团队成员的安全教育和意识培养、加强安全管理和监督,避免重复或类似风险再次发生。
  5. 持续更新:由于攻击手段可能会不断变化和升级,前端开发人员需要及时了解安全最新动向,对代码规范、协议以及工具进行不断更新。

综上所述,前端安全攻防是一个协作的攻防流程,需要多个部门协作完成攻防任务,保障系统和用户的安全。

2. 攻击技术

2.1 XSS攻击

2.1.1 原理和类型

XSS攻击(Cross-Site Scripting攻击)是一种利用Web应用程序中存在的漏洞,向客户端注入恶意代码,使之在用户的浏览器上执行的攻击方式。

XSS攻击主要是利用Web应用程序没有对用户输入的数据进行充分的过滤和验证,使攻击者得以在页面上注入恶意代码,从而在用户访问Web应用程序时,植入恶意代码实现攻击。

XSS攻击可以分为三个主要的类型:反射型 XSS、存储型 XSS、DOM-based XSS

具体的分类如下:

  1. 反射型 XSS:攻击者通过构造一些恶意的URL,将包含攻击代码的URL发送给目标用户,使得用户点击该URL后,可以在用户的浏览器上执行恶意代码,从而达到攻击的效果。
  2. 存储型 XSS:攻击者将恶意代码存储在Web应用程序的服务器上,然后等待用户访问页面,获取存储的恶意代码并在用户的浏览器上执行。
  3. DOM-based XSS:攻击者注入的恶意代码会直接被页面中的JavaScript解释和执行,绕过了服务器的编码和过滤。

综上所述,XSS攻击是通过对Web应用程序的注入攻击达到攻击效果的一种方式,攻击者主要通过利用Web应用程序未能充分过滤和验证用户的输入,将恶意代码注入目标页面进行攻击,对于开发者来说需要注重应用中用户输入数据的过滤和验证,防范XSS攻击。

2.1.2 预防和防御

预防和防御 XSS 攻击的方法包括以下几个方面:

  1. 数据过滤和验证:服务器端对输入的数据进行过滤和验证,确保其是合法、安全的数据,包括数据类型、长度、格式等。可以使用现成的过滤函数或库,也可以自定义开发数据过滤和验证模块。
  2. 防止代码注入:尽量避免在页面或JS代码中出现用户输入的数据,对于必须输入的情况,可以使用转义或加密等方式处理输入的数据,避免代码被注入。
  3. HttpOnly:在设置 Cookies 时,使用 HttpOnly 属性设置 CookiesHttpOnly 标记。这样可以防止前端 JavaScript 读取Cookies,因为Cookies中包含了一些敏感信息,如Session ID 等,防止 XSS 攻击者利用CookiesXSS 攻击。
  4. CSP:使用Content Security Policy(CSP)防止XSS攻击。CSP规定了哪些资源可以被加载到页面中,可以通过HTTP请求头设置CSP。
  5. 安全编码:在开发过程中,需要将安全编码和安全设计作为考虑的重点,以便不断提高对攻击的抵御能力。
  6. 增强用户意识:XSS攻击大多针对用户随意点击链接或输入信息进行攻击,因此对用户进行安全意识的教育是预防和防御XSS攻击的重要环节之一,如不随意泄露个人信息和密码等。

综上所述,对于开发人员来说,需要注意加强对XSS攻击的防范,提高自身编码水平,对用户输入的数据进行充分过滤和验证,使用CSP配置, 安全编码等措施加强代码安全性。另外也需要注意与用户的安全教育,提高用户信息安全意识,避免被攻击者利用和误导。

2.2 CSRF攻击

2.2.1 原理和类型

CSRF攻击(Cross-Site Request Forgery 攻击)是一种利用用户在已登录的Web应用程序中的身份识别信息,通过从受害用户处获取的Cookie,伪造发起跨站请求的攻击方式

攻击者通过构造一个包含攻击代码的恶意链接,诱导用户点击,从而在用户不知情的情况下将恶意代码提交到目标网站,引发一系列渗透攻击和数据窃取。

CSRF攻击可以分为两种类型:GET型和POST型。

具体而言

  • GET型 CSRF 攻击可以通过构造链接的方式,通常用于改变目标网站的状态或进行轻量级的数据窃取;
  • POST型 CSRF 攻击则需要伪造表单,恶意代码通过 JavaScript 提交恶意表单信息到目标网站。

综上所述,CSRF攻击利用用户的登录状态伪造请求,从而越过了目标网站对用户权限的防护。

为了有效防御CSRF攻击,业界提出了以下几种预防和防御的策略:

  1. Token验证:在表单提交中添加一个Token字段,通过生成唯一的Token,附加到提交的请求中,目标服务器会对Token和当前用户的会话状态进行验证, 防止攻击者伪造请求。
  2. Referer验证:验证请求中的Referer字段,判断是否来源于信任的网页,如果不是则认定为非法请求。
  3. 双重 Cookie:在发送数据时,除了使用Session Cookies外,还可以通过直接在浏览器中设置一个隐藏字段带有随机值,之后在发送请求的时候将该随机值同时发送到服务器端,然后服务器端再验证该值是否正确,避免CSRF攻击。
  4. 防止登录跨站点:当用户输入密码进行登录时,要进行多次确认,确保用户意识到当前已经进入了目标站点。

综上所述,防御和预防CSRF攻击需要综合考虑多个方面,包括后端措施和前端措施,如在代码实现中增加Token验证、Referer验证、使用双重 Cookies、登录跨站点确认等措施,结合用户的安全教育,加强安全意识,提高应对CSRF攻击能力。

2.2.2 预防和防御

预防和防御 CSRF 攻击主要包括以下几点:

  1. Token验证:对于每个表单提交都增加一个Token值,并在服务器端验证,来区分是合法的请求还是恶意的请求。
  2. Referer头检查:检查HTTP请求头中的Referer字段,如果该字段的值与当前请求的域名不一致,则可能是恶意请求。
  3. SameSite属性设置:对于cookie使用SameSite属性,在请求跨站时不会发送cookie,可以防止 CSRF 攻击。
  4. 双重验证:比如在操作重要数据时,可以设置一个二步验证,如短信或者邮件通知等。
  5. 防止自动提交:通过禁止自动提交表单、在请求中添加特殊的标识符、延迟提交等方式来防止恶意请求。
  6. 避免使用Cookie存储敏感信息:当Cookie被盗取时,如果存储敏感信息,可能会导致用户的重要信息泄露。
  7. 加强安全教育:对于用户,需要加强安全意识,提醒用户保护好自己的账户,建议使用密码管理器、不重复使用密码等方式来避免CSRF攻击。

综上所述,预防和防御CSRF攻击需要在多个方面上下功夫。开发人员需要在代码实现过程中增加Token验证、Referer验证、设置SameSite属性、双重验证等措施,同时在用户使用过程中,需要加强安全意识,比如不向陌生人泄露账号信息等,从而避免攻击者利用CSRF攻击,保障用户的安全和数据的保密性。

3. 代码层次

3.1 JavaScript代码安全

3.1.1 客户端JavaScript安全

客户端JavaScript安全主要包括以下几个方面:

  1. 防止跨站脚本攻击(XSS):在前端代码中,要充分对用户的输入进行过滤和验证,将用户输入的内容转义或进行编码,以避免恶意脚本的注入或执行。对于动态生成的内容,也要谨慎检查其来源和有效性,防止未知的脚本被执行。
  2. 防止跨站请求伪造攻击(CSRF):在Ajax请求中,要设置跨站请求伪造保护,例如添加 Token 或者使用 SameSite 属性等方式来防止 CSRF 攻击。
  3. 加密数据传输:在数据传输时,应使用 HTTPS 来加密数据,防止敏感数据被拦截和篡改。
  4. 保护敏感数据:将用户的敏感信息,如身份证号、信用卡号等,加密保存,并使用适当的存储安全措施,如使用加密算法进行传输、存储加密等方式来保护用户数据的安全。
  5. 防止代码混淆和反编译:使用 JavaScript 混淆工具将前端代码进行混淆,避免恶意脚本攻击。同时,也要注意保护代码的发布环境和防止反编译等反制措施。
  6. 加强用户教育:提高用户安全意识,避免被社交工程等方式利用,从而保障用户信息的安全和私有性。

综上所述,客户端JavaScript安全是Web应用开发中必须要非常注重的一个方面,主要需要开发人员从多个方面思考和考虑,包括充分过滤和验证用户的输入数据,使用 HTTPS 加密数据传输,使用 JavaScript 混淆工具等方式来保护前端代码安全,同时也要针对用户加强安全教育,提高其安全意识和预防风险的能力。

3.1.2 服务器端JavaScript安全

服务器端JavaScript安全主要包括以下几个方面:

  1. 控制代码执行权限:对于服务器端执行的 JavaScript 代码,需要通过代码控制和限制代码执行的权限,例如只允许执行具体的一些函数,或者只允许执行某些特定的操作等,从而避免恶意代码的执行和危害。
  2. 防止注入攻击:在编写代码时,需要重点考虑数据的输入和输出,避免注入攻击窃取敏感数据或者进行其他恶意操作。常用方法包括数据过滤、输入验证、参数化查询等。
  3. 安全的存储:对于存储敏感数据,需要使用合适的加密算法进行加密存储,从而保证数据的安全性和私密性。
  4. 防止拒绝服务攻击:针对有恶意攻击者可能发起的 DDOS 攻击等拒绝服务攻击,需要严格限制每个请求的处理时间,同时使用流量监控、攻击防护等方式缓解攻击。
  5. 安全合规检查:需要对服务器的安全进行定期的检查和测试,发现和修复漏洞、提高安全意识,确保服务器端的代码和系统的安全合规性。

综上所述,服务器端JavaScript安全是Web应用安全的重要环节之一,在服务端代码的开发过程中必须要注重安全,充分考虑并控制代码执行的权限,防止注入攻击和拒绝服务攻击,使用合适的加密算法进行数据保护以及进行定期检测测试,保证服务器端代码的安全性和稳定性。

3.1.3 JavaScript代码审计

JavaScript 代码审计是一种从代码层面来分析、检查和评估应用程序的安全性的方法,对于 Web 应用的开发和维护非常必要,可以有效地发现潜在的安全风险和漏洞,避免因漏洞造成的数据泄露、拒绝服务等安全问题。

在 JavaScript 代码审计的过程中,需要注意以下几个方面:

  1. 检查输入过滤:输入过滤是防止注入攻击和 XSS 攻击的关键。应该审计所有的输入,包括 URL、表单提交、Cookie 等,并进行适当的过滤和校验,以避免攻击者通过恶意输入造成的安全漏洞。
  2. 分析数据流和控制流:在审计代码时需要细致地分析代码的数据流和控制流,了解变量和数据的来源,并考虑代码中的判断和分支。这样有助于找到所有未被处理的输入和输出,以及代码中存在的逻辑漏洞。
  3. 考虑客户端和服务器端的安全性:在审计 JavaScript 代码时,需要同时考虑客户端和服务器端的安全性。因为 JavaScript 代码通常会在客户端和服务器端同时执行,只有对两端都进行审计,才能得到完整的安全评估结果。
  4. 寻找框架和库的安全问题:因为开发人员通常会使用各种框架和库来简化代码的开发,审计 JavaScript 代码时需要特别关注这些框架和库的安全漏洞,注意版本更新和安全更新。
  5. 多种手段结合:审计 JavaScript 代码不应仅仅通过手工阅读代码来进行。可以使用静态分析工具、动态测试工具、模糊测试工具等多种手段进行结合使用。

综上所述,JavaScript 代码审计是一项非常重要的安全工作,主要围绕输入过滤、数据流和控制流、客户端和服务器端的安全性、框架和库的安全问题以及多种手段的结合等方面来进行。只有从多个维度和角度进行审计,才能发现更多的潜在漏洞和安全风险,从而提高应用程序的安全性和稳定性。

3.2 CSS安全

3.2.1 CSS注入攻击

CSS 注入攻击是一种利用CSS样式表的漏洞来进行攻击的方式。攻击者利用恶意 CSS 代码,将样式表中的一些属性值改变,使网页显示出不正常的样式。

CSS注入攻击的危害主要包括以下几点:

  1. 篡改网页内容:攻击者可以通过 CSS 注入,改变网页的颜色、布局,混淆网页,或者篡改网页内容,诱骗用户进行恶意操作,例如点击有害链接或者提供敏感信息等。
  2. 数据窃取:攻击者可以通过 CSS 注入,对隐藏元素进行控制,达到窃取用户敏感信息的目的。
  3. 拒绝服务攻击:如果恶意CSS样式表代码导致网页中的渲染和显示过程出现错误,则会导致网站服务无法正常工作,如网站崩溃、页面加载缓慢等。

3.2.2 预防和防御

下面是一些防范 CSS 注入攻击的方法:

  1. 避免接受来自非信任源的 CSS 代码,比如从网络上任意下载的CSS代码,需要先进行安全检测之后再使用。
  2. 对于用户提交的内容,进行严格过滤和检查,不接受来自未授权的 CSS 字符串。
  3. 防止 CSS 注入有关的操作,如限制远程锚点链接的访问,防止手动输入 URL 等。
  4. 在开发代码过程中,需要规范化使用 CSS,不使用能够导致注入漏洞的动态 CSS 函数。
  5. 安全更新用户输入的最终结果,即用户最终的 HTML 输出结果,将用户的输入内容进行过滤和转义,确保用户的输入只作为数据不被作为代码执行。

综上所述,开发者需要重视 CSS 注入攻击的预防和防御,并采取相应的措施,以避免安全事件的发生和用户数据的泄露,从而保障网站的安全性和稳定性。

3.3 HTML安全

3.3.1 HTML注入攻击

HTML 注入攻击是一种利用HTML页面的漏洞,往网站中插入恶意 HTML 代码,从而导致用户隐私数据被窃取的攻击方式。

常见的 HTML 注入攻击主要包括以下几种:

  1. XSS 攻击:攻击者通过恶意代码,包括脚本和其他 HTML 代码,注入到网站中,从而实现窃取用户隐私数据、盗取身份验证信息等损害行为。
  2. HTML 邮件注入攻击:攻击者通过电子邮件等方式,将包含恶意代码的 HTML 电子邮件注入到网站中,达到篡改网站的目的。
  3. 对 HTML 表单的注入:攻击者通过利用网站 HTML 表单的漏洞,为自己的目的在网站上插入恶意的 HTML 代码,从而让网站的提交数据带有恶意代码。

3.3.2 预防和防御

防止 HTML 注入攻击需要采取以下措施:

  1. 过滤输入数据:抵御 HTML 注入攻击最基本的方式是将所有输入的数据进行过滤和校验。此外,还需注意对 HTML 标记进行过滤和处理,比如替换 “<” 和 “>” 标记,使其无法被执行。
  2. 限制所允许的 HTML 标记:可以在代码中进行限制,只允许某些特定的 HTML 标记,从而防止注入恶意代码。
  3. 对表单中的数据进行处理:在输入表单中,需要对数据进行适当的处理。例如,对于文本框中的文本,需要去除首尾空格、HTML 标记和特殊字符等。
  4. 防范 XSS 攻击:使用 HTTP Only Cookie 设置,避免将 Cookie 数据暴露给脚本。此外,建议使用 CSP(Content-Security-Policy)等其他工具来防范 XSS 攻击。

综上所述,为了防止 HTML 注入攻击,需要重视输入数据校验和过滤、限制 HTML 标记、对表单数据进行处理和防范 XSS 攻击等方面,从多个角度对 HTML 注入攻击进行预防和防御。

3.4 安全框架

3.4.1 功能和优点

安全框架(Security Framework)是用来保证应用程序安全性的软件框架,也称之为安全开发框架(Secure Development Framework)。

主要包含以下几个方面的功能:

  1. 认证与授权:安全框架提供了认证和授权的机制,可以实现用户身份验证、访问控制和权限管理等功能,从而确保应用程序中的不同用户只能访问其授权的资源和操作。
  2. 输入验证与过滤:安全框架提供输入验证和过滤的功能,能够防止 SQL 注入、XSS 跨站脚本注入、CSRF 跨站请求伪造等攻击方式,从而保证应用程序输入数据的安全性。
  3. 加密与解密:安全框架提供了数据加密和解密的功能,可以加密数据库中的敏感数据、代码和配置等信息,保护应用程序的机密信息被不法分子盗取。
  4. 日志记录与审计:安全框架提供了日志记录和审计的功能,可以记录用户的操作行为、访问过程、错误信息等,从而便于开发人员进行漏洞排查和故障修复。

安全框架的优点有以下几点:

  1. 减少安全问题:安全框架提供了一套安全标准和最佳实践,可以有效的预防安全问题,减少潜在的安全漏洞和攻击。
  2. 提高效率:使用安全框架可以让开发人员专注于业务逻辑和实现,同时也减轻了安全问题的开发负担和时间成本,提高了开发效率。
  3. 方便集成:安全框架是基于编程框架或者编程语言的,可以方便地集成到已有的项目和代码,避免了重复的工作,降低了集成难度。
  4. 提升用户信任度:安全框架可以提高应用程序和系统的安全性,减少被攻击或遭到数据泄露的几率,从而提升用户的信任度,保护企业信息资产价值。

总之,安全框架是一种提高应用程序安全性的有效手段,能够保护用户的隐私数据、避免安全漏洞和攻击等,从而避免因安全问题引起的业务风险和数据泄露等事件。

3.4.2 常见的安全框架

常见的安全框架有:

  1. Spring SecuritySpring SecuritySpring框架中常用的安全框架,它提供了认证(Authentication)和授权(Authorization)的标准化实现,并且可以与其他Spring组件进行整合。
  2. Apache ShiroApache Shiro是一个灵活性极高的安全框架,其核心功能包括认证、授权、加密、会话管理和访问控制等。
  3. Laravel SecurityLaravel SecurityLaravel框架中常用的安全框架,它提供了CSRF保护、XSS保护、加密和解密等功能,同时也可以与模板引擎、缓存、和最新版本Laravel框架进行整合。
  4. .NET Framework:.NET Framework提供了很多安全编程框架,包括 ASP.NET Core Identity、ASP.NET Web Forms、ASP.NET MVC 和ASP.NET Web API等,这些框架用于Web应用程序和服务器端的应用程序的安全防护。
  5. OWASPOWASP是一个开放的Web应用安全项目,它提供了一套开源的安全框架和工具,帮助开发人员发现和解决Web应用程序中的安全漏洞。

以上是一些常见的安全框架,各有特点和应用场景,可以根据不同项目的需求和特点选择合适的安全框架。同时,为了防止安全漏洞和攻击,开发人员应该始终保持警惕,采取最佳实践和标准来处理敏感数据和用户隐私信息。

4. 网络通信层次

4.1 HTTPS传输协议

4.1.1 安全传输协议的意义

HTTPS是通过加密通道实现HTTP协议通信的协议,通过使用加密技术确保数据在客户端和服务器之间传输时不被窃取、篡改或伪造。HTTPS的意义有以下几点:

  1. 提供了数据传输的机密性:通过使用SSLTLS等安全技术,通信双方通过加密传输数据,传输过程中数据被加密,避免了数据被窃取、篡改甚至伪造的风险。
  2. 增加了数据传输的完整性:HTTPS中使用的数字签名技术,保证数据传输过程中不被篡改,确保数据的完整性。如果有人试图在传输过程中篡改数据,SSL会检测到,从而可以避免数据泄漏、被篡改的风险。
  3. 身份认证:本来HTTP协议没有身份认证的机制,会存在冒充他人进行通信的问题。而HTTPS使用证书机制对客户端和服务器进行了身份认证,确保了通信双方的真实性和安全。
  4. 增强了用户体验和信任:HTTPS提供了数据加密、身份认证、数据完整性和数据保密性等多种安全机制,增加了用户对网站的信任感,提高了用户体验。

综上所述,使用HTTPS安全传输协议可以保护用户的隐私、防止网络攻击和黑客攻击,同时增强了用户对网站的信任感,提高了网站的安全性和品牌声誉。目前,许多网站和应用程序因为安全问题加密了它们的通信协议,所以使用HTTPS协议已经成为了 Web 安全的最佳实践。

4.1.2 HTTPS工作原理

HTTPS(Hypertext Transfer Protocol Secure)是通过加密通道实现HTTP协议通信的协议,工作原理如下:

  1. 客户端发出HTTPS请求:当用户访问需要使用HTTPS的网站时,客户端会与Web服务器建立TCP连接,使用443端口发出HTTPS请求。
  2. 服务端发送SSL证书:Web服务器需要向客户端发送证书,证书中包含了服务器公钥、证书颁发机构(CA)的信息以及证书的有效期等信息。
  3. 客户端验证SSL证书:客户端从Web服务器接收到证书后,会验证证书是否合法和有效,如果验证不通过,则会弹出警告提示是否继续访问。
  4. 客户端生成随机数并加密:如果证书验证通过,客户端随机生成一个用于加密数据的对称密钥,并将其用服务器公钥加密,发送给服务器。这个过程称为客户端与服务端协商密钥。
  5. 服务端使用密钥对数据进行加密: Web服务器使用私钥解密,得到了客户端发来的对称密钥,然后使用该对称密钥对加密数据进行加密处理,并将加密后的数据发送给客户端。
  6. 客户端使用密钥对数据进行解密:客户端使用刚才生成的对称密钥解密服务器发来的加密数据,得到了HTTP请求中的内容。
  7. 客户端和服务端进行加密通信:自此以后,客户端和服务端就可以使用对称密钥进行加密通信,从而保证数据传输的安全性和机密性。

综上所述,HTTPS的加密技术主要是基于公钥和对称密钥机制实现。首先在握手阶段通过公钥加密方法协商生成会话密钥,然后使用会话密钥来对数据进行加密和解密,保证了网络传输的数据安全性。

4.1.3 HTTP劫持攻击和HTTPS劫持攻击

HTTP劫持攻击(HTTP Hijacking)是指黑客截获了用户的HTTP请求,并在用户不知情的情况下修改了HTTP请求或返回的结果。攻击者可以读取和修改HTTP报文中的内容,篡改HTTP请求的参数,获取用户敏感信息等。HTTP协议采用明文传输方式,容易被黑客进行窃听和篡改,因此容易受到HTTP劫持攻击。

HTTPS劫持攻击(HTTPS Hijacking)是指黑客通过欺骗或者伪造证书等方式,将HTTPS报文的加密通信解密,篡改或修改用户的HTTPS请求或响应信息的一种攻击方式。HTTPS采用的是加密传输方式,提高了数据的安全性,但只要攻击者掌握合法的证书,即可欺骗受害者,使其误认为通信双方是正确的,而进行对话,同时,黑客则可以在通信过程中篡改或窃取受害者的信息。

在HTTP劫持攻击中,攻击者可以直接读取和修改HTTP报文中的内容,而在HTTPS劫持攻击中,攻击者需要解密HTTPS加密通信才能对通信进行攻击,难度更大,但一旦攻击成功,造成的影响也会更加具有破坏性。

为避免HTTP劫持攻击和HTTPS劫持攻击,我们应该使用安全的通信协议,如HTTPS协议,以确保通信过程的安全和机密性。此外,了解安全威胁和冷静应对攻击也是非常重要的。

4.1.4 预防和防御

为了预防和防御HTTP劫持攻击或HTTPS劫持攻击,我们可以采取以下措施:

  1. 使用加密的通信协议:如使用HTTPS协议来加密通信,避免数据泄露和篡改。此外,也可以采用VPN等安全通信协议,防止黑客对数据进行窃听和篡改。
  2. 使用强密码并定期更新:使用强密码来加强账户安全性,同时也要定期更新密码以防止黑客将账户密码窃取,从而攻击账户或篡改账户信息。
  3. 安全的网站访问:必要时使用访问控制或软件层面的防护机制对网站或服务器进行保护,如限制IP访问、设置防火墙、禁用ROOT账号等。
  4. 防止网络钓鱼攻击:黑客通过伪装成合法机构或个人掩盖其身份,以获取或竞争用户的敏感信息。如果您收到了这样的邮件或链接,请务必不要轻易点击和填写信息,对发件人或链接进行验证。
  5. 教育和培训员工:教育和培训员工安全意识和隐私保护方面的知识,提高认识,防范机构内部的黑客攻击。
  6. 使用安全DNS服务:有助于监视网络流量和预防网络钓鱼攻击。这些服务会监视黑名单和恶意软件,并提供自动防御措施以保持网络的稳定性和安全性。

综上所述,能够预防和防御HTTP劫持攻击和HTTPS劫持攻击需要采取多重措施,包括使用加密通信协议、加强安全意识培训、设置访问控制、使用安全DNS服务等。但是有一点我们需要明白,完全的安全是不存在的,只能不断提升安全性,减少被攻击概率。

4.2 同源策略

4.2.1 定义和作用

同源策略(Same-Origin Policy)是一种安全机制,主要用于控制Web浏览器如何访问来自不同源(即不同协议、主机名或端口)的网站资源。同源策略的作用是限制某个文档或者脚本,仅在与其来源相同的环境中执行,从而防止恶意代码通过跨站点脚本攻击(XSS攻击)等方式窃取用户的敏感数据或者执行恶意操作。

同源策略要求在同一个域名、端口和协议下的网页脚本才能相互访问,而不同源的网页脚本之间是相互隔离的。同源策略可以防止恶意网站将各种资源注入到其它网站并获取数据,从而保护用户的安全和隐私。

比如,对于JavaScript脚本而言,如果一个页面中加载了另一个来自不同源的网站的脚本,即使这个脚本是来自于一个已经标记为危险的站点,也不能直接访问页面的DOM(文档对象模型),从而避免了XSS攻击。

同源策略虽然提高了Web应用程序的安全性,但也带来了一些限制,为了实现跨域的交互,需要使用一些跨域技术,如JSONP、CORS等实现跨域消息传递。

总之,同源策略可以防止Web应用程序中的安全漏洞,避免恶意网站窃取用户的敏感信息并限制对互联网信息资源的访问。

4.2.2 绕过同源策略的攻击技术

绕过同源策略的攻击技术主要有以下几种:

  1. 使用 JSONP:JSONP 是 JSON with padding 的缩写,可以通过 script 标签的跨域特性来获取外部数据。攻击者可以通过恶意站点将自己的 JavaScript 代码注入到目标站点中,通过使用 JSONP 技术来窃取数据。
  2. 利用跨域资源共享(CORS):攻击者可以通过在目标网站中添加恶意 JavaScript 或 HTML 代码来实现跨域攻击。CORS 规定了网站将哪些资源暴露给外部站点。恶意站点可以利用该机制窃取用户数据。
  3. 使用 DNS 重绑定:攻击者可以通过 DNS 重绑定技术将恶意网站伪装成受信任的网站。当用户访问受信任网站时,实际上会访问到恶意网站,攻击者可以通过这种方式来窃取用户信息。
  4. 利用 iframe:攻击者可以通过 iframe 标签将其网站嵌入到目标网站,从而窃取目标网站中的数据。攻击者可以在 iframe 中注入恶意代码,从而实现对用户数据的窃取。

请注意这些都是用于学术和研究目的,严禁用于不道德和违法用途。

5. 总结

5.1 前端安全攻防总结

前端安全攻防可以从以下几个方面进行总结:

1. XSS 攻击

跨站脚本攻击(XSS)是指攻击者通过注入恶意脚本,在受害者的浏览器中运行,以获取受害者敏感信息或实现其他攻击行为。前端防御 XSS 攻击可采取如下措施:

  • 输入过滤和校验:过滤掉用户输入的恶意脚本、特殊字符等。
  • 对敏感输出进行转义:将输出的特殊字符替换为 HTML 实体,对于不应该被解析成 HTML 的内容,使用textContent 或者innerText 输出。
  • 设置 CSP:Content Security Policy(内容安全策略)是定义浏览器如何执行内容的策略,可以指定允许执行的脚本来源,有效地防止 XSS 攻击。

2. CSRF 攻击

跨站请求伪造(CSRF)是指攻击者发起伪造请求,利用受害者的权限完成某些操作,例如转账、修改密码等。前端防御 CSRF 攻击可采取如下措施:

  • 利用 SameSite cookie 属性:SameSite 限制了服务器只能在与当前网站同域的情况下才能向该网站设置 Cookie,从而使攻击者无法伪造包含当前网站 Cookie 的请求。
  • 在请求中添加 CSRF Token:服务器在向客户端发送 HTML、JS 或图片等资源的同时也会随机生成一个与之对应的 Token,每次发起请求的时候,需要将 Token 附加在请求体中,从而有效地防御 CSRF 攻击。

3. 点击劫持攻击

点击劫持(Clickjacking)是利用透明 iframe 将可点击的目标伪装成看似无关的按钮或链接,使受害者在不知情的情况下执行了一些恶意操作。前端防御点击劫持攻击可采取如下措施:

  • 设置 X-Frame-Options 响应头:X-Frame-Options 是一个 HTTP 响应头,只有在浏览器判断请求来自同源站点时,才允许页面在 iframe 中加载。
  • 使用 JavaScript 防范:通过检查当前页面中的 frame 或 top 属性,以及 location.href 是否等于 top.location.href,判断当前页面是否被嵌套在 iframe 中。

最后,需要强调的是,前端安全防御不能仅仅局限于前端,还需要结合后端、数据库等其他方面进行综合防御,从而确保整个 web 应用系统的安全。

5.2 在实际项目中如何预防和处理安全问题

在实际项目中,为了预防和处理安全问题,可以采取以下措施:

  1. 加强用户输入校验:输入数据时进行及时和有效的校验,过滤非法内容,包括特殊字符、SQL 注入语句、JavaScript 脚本等。
  2. 合理使用 HTTPS: 对重要数据采取 HTTPS 加密传输,确保数据传输安全。
  3. 前后端分离:将前端与后端分离,将后端对用户输入进行校验并进行最终的处理,从而防止代码注入和其他攻击。
  4. 服务器层面的安全:加强服务器的安全,包括加强服务器的访问授权、配置防火墙等技术手段。
  5. 不要使用过时的库和框架:过时的库和框架容易存在相应的漏洞,需要时刻更新核心库和框架。
  6. 应用程序日志记录:记录应用程序日志,及时发现异常行为,集中监测。
  7. 定期进行安全漏洞扫描:定期使用安全工具对应用程序进行及时安全漏洞扫描,并进行及时修补。
  8. 建立安全审计制度:建立安全审计制度,检查和发现安全漏洞,及时处理问题。

总之,针对安全问题,项目需要采取多种措施综合防范和处理,确保系统的安全性。同时,需要时刻关注最新的安全问题和漏洞,并采取相应的措施予以解决。

做项目的过程中,使用正则表达式来匹配一段文本中的特定种类字符,是比较常用的一种方式,下面是对常用的正则匹配做了一个归纳整理。

1、匹配中文:[\u4e00-\u9fa5]

2、英文字母:[a-zA-Z]

3、数字:[0-9]

4、匹配中文,英文字母和数字及下划线:^[\u4e00-\u9fa5_a-zA-Z0-9]+$

同时判断输入长度:

[\u4e00-\u9fa5_a-zA-Z0-9_]{4,10}

5、

(?!_) 不能以_开头

(?!.*?_$) 不能以_结尾

[a-zA-Z0-9_\u4e00-\u9fa5]+ 至少一个汉字、数字、字母、下划线

$ 与字符串结束的地方匹配

6、只含有汉字、数字、字母、下划线,下划线位置不限:

^[a-zA-Z0-9_\u4e00-\u9fa5]+$

7、由数字、26个英文字母或者下划线组成的字符串

^\w+$

8、2~4个汉字

"^[\u4E00-\u9FA5]{2,4}$";

9、最长不得超过7个汉字,或14个字节(数字,字母和下划线)正则表达式

^[\u4e00-\u9fa5]{1,7}$|^[\dA-Za-z_]{1,14}$

10、匹配双字节字符(包括汉字在内):[^x00-xff]

评注:可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1)

11、匹配空白行的正则表达式:ns*r

评注:可以用来删除空白行

12、匹配HTML标记的正则表达式:<(S*?)[^>]*>.*?|<.*? />

评注:网上流传的版本太糟糕,上面这个也仅仅能匹配部分,对于复杂的嵌套标记依旧无能为力

13、匹配首尾空白字符的正则表达式:^s*|s*$

评注:可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式

14、匹配Email地址的正则表达式:^[a-zA-Z0-9][\w\.-]*[a-zA-Z0-9]@[a-zA-Z0-9][\w\.-]*[a-zA-Z0-9]\.[a-zA-Z][a-zA-Z\.]*[a-zA-Z]$

评注:表单验证时很实用

15、手机号:^((13[0-9])|(14[0-9])|(15[0-9])|(17[0-9])|(18[0-9]))\d{8}$

16、身份证:(^\d{15}$)|(^\d{17}([0-9]|X|x)$)

17、匹配网址URL的正则表达式:[a-zA-z]+://[^s]*

评注:网上流传的版本功能很有限,上面这个基本可以满足需求

18、匹配帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$

评注:表单验证时很实用

19、匹配国内电话号码:d{3}-d{8}|d{4}-d{7}

评注:匹配形式如 0511-4405222 或 021-87888822

20、匹配腾讯QQ号:[1-9][0-9]{4,}

评注:腾讯QQ号从10000开始

21、匹配中国邮政编码:[1-9]d{5}(?!d)

评注:中国邮政编码为6位数字

22、匹配身份证:d{15}|d{18}

评注:中国的身份证为15位或18位

23、匹配ip地址:d+.d+.d+.d+

评注:提取ip地址时有用

24、匹配特定数字:

^[1-9]d*$ //匹配正整数

^-[1-9]d*$ //匹配负整数

^-?[1-9]d*$ //匹配整数

^[1-9]d*|0$ //匹配非负整数(正整数 + 0)

^-[1-9]d*|0$ //匹配非正整数(负整数 + 0)

^[1-9]d*.d*|0.d*[1-9]d*$ //匹配正浮点数

^-([1-9]d*.d*|0.d*[1-9]d*)$ //匹配负浮点数

^-?([1-9]d*.d*|0.d*[1-9]d*|0?.0+|0)$ //匹配浮点数

^[1-9]d*.d*|0.d*[1-9]d*|0?.0+|0$ //匹配非负浮点数(正浮点数 + 0)

^(-([1-9]d*.d*|0.d*[1-9]d*))|0?.0+|0$ //匹配非正浮点数(负浮点数 + 0)

评注:处理大量数据时有用,具体应用时注意修正

25、匹配特定字符串:

^[A-Za-z]+$ //匹配由26个英文字母组成的字符串

^[A-Z]+$ //匹配由26个英文字母的大写组成的字符串

^[a-z]+$ //匹配由26个英文字母的小写组成的字符串

^[A-Za-z0-9]+$ //匹配由数字和26个英文字母组成的字符串

^w+$ //匹配由数字、26个英文字母或者下划线组成的字符串

26、

在使用RegularExpressionValidator验证控件时的验证功能及其验证表达式介绍如下:

只能输入数字:“^[0-9]*$”

只能输入n位的数字:“^d{n}$”

只能输入至少n位数字:“^d{n,}$”

只能输入m-n位的数字:“^d{m,n}$”

只能输入零和非零开头的数字:“^(0|[1-9][0-9]*)$”

只能输入有两位小数的正实数:“^[0-9]+(.[0-9]{2})?$”

只能输入有1-3位小数的正实数:“^[0-9]+(.[0-9]{1,3})?$”

只能输入非零的正整数:“^+?[1-9][0-9]*$”

只能输入非零的负整数:“^-[1-9][0-9]*$”

只能输入长度为3的字符:“^.{3}$”

只能输入由26个英文字母组成的字符串:“^[A-Za-z]+$”

只能输入由26个大写英文字母组成的字符串:“^[A-Z]+$”

只能输入由26个小写英文字母组成的字符串:“^[a-z]+$”

只能输入由数字和26个英文字母组成的字符串:“^[A-Za-z0-9]+$”

只能输入由数字、26个英文字母或者下划线组成的字符串:“^w+$”

验证用户密码:“^[a-zA-Z]w{5,17}$”正确格式为:以字母开头,长度在6-18之间,

只能包含字符、数字和下划线。

验证是否含有^%&',;=?$"等字符:“[^%&',;=?$x22]+”

只能输入汉字:“^[u4e00-u9fa5],{0,}$”

验证Email地址:“^w+[-+.]w+)*@w+([-.]w+)*.w+([-.]w+)*$”

验证InternetURL:“^http://([w-]+.)+[w-]+(/[w-./?%&=]*)?$”

验证身份证号(15位或18位数字):“^d{15}|d{}18$”

验证一年的12个月:“^(0?[1-9]|1[0-2])$”正确格式为:“01”-“09”和“1”“12”

验证一个月的31天:“^((0?[1-9])|((1|2)[0-9])|30|31)$”

正确格式为:“01”“09”和“1”“31”。

匹配中文字符的正则表达式: [u4e00-u9fa5]

匹配双字节字符(包括汉字在内):[^x00-xff]

匹配空行的正则表达式:n[s| ]*r

匹配HTML标记的正则表达式:/<(.*)>.*|<(.*) />/

匹配首尾空格的正则表达式:(^s*)|(s*$)

匹配Email地址的正则表达式:w+([-+.]w+)*@w+([-.]w+)*.w+([-.]w+)*

匹配网址URL的正则表达式:http://([w-]+.)+[w-]+(/[w- ./?%&=]*)?