CSS基础 - 样式与布局

CSS 基础 - 样式与布局

上一篇 HTML 基础 - 标签语法 搭出了页面骨架, 但那个页面是黑字白底的”素”页面。这一篇解决”让页面好看”的问题: CSS 怎么引入、怎么选中元素、盒模型是什么、用 Flex 做布局、怎么适配手机屏幕。读之前只需要会上一篇的 HTML。

CSS 是什么

CSS 全称 Cascading Style Sheets, 层叠样式表。人话版: 一堆”选中谁, 把它改成什么样”的规则。
浏览器先按 HTML 画出结构, 再把 CSS 规则逐条套上去: 所有 h1 变蓝色, class 是 box 的元素加个边框。“层叠”的意思是多条规则可以同时作用到一个元素上, 冲突时按优先级定胜负, 后面有一节专门讲。

一条规则长这样:

代码块CSS · 4 行收起展开
选择器 {
  属性: 值;
  属性: 值;
}

比如:

代码块CSS · 5 行收起展开
p {
  color: red;        /* 文字颜色 */
  font-size: 16px;   /* 字号, px 是像素 */
  margin: 10px;      /* 外边距, 盒模型一节细讲 */
}

三种引入方式

CSS 可以写在三个地方, 平时几乎只用第一种。

外部样式表

样式单独放一个 .css 文件, 在 HTML 的 head 里用 <link> 标签引进来:

<head>
  <link rel="stylesheet" href="style.css">
</head>
/* style.css */
h1 {
  color: blue;
}

结构和样式分离, 多个页面还能共用同一份样式文件。感觉上很像 Java 里把配置从代码抽出去: 改样式不用碰 HTML。

内部样式表

样式直接写在 HTML 的 <style> 标签里, 适合单文件的小 demo:

代码块HTML · 5 行收起展开
<head>
  <style>
    h1 { color: blue; }
  </style>
</head>

内联样式

直接写在标签的 style 属性上, 只影响这一个元素:

代码块HTML · 1 行收起展开
<h1 style="color: blue;">标题</h1>

相当于把配置硬编码进代码, 想改得挨个元素找, 平时少用。但它优先级最高, 讲优先级时会再遇到它。

选择器: 告诉浏览器你要改谁

写 CSS 有一大半功夫花在”精确选中目标元素”上。选择器有点像 SQL 里的 where 条件: 从整棵页面树里筛出符合条件的元素, 然后批量改样式。

基础选择器

代码块CSS · 11 行收起展开
/* 标签选择器: 所有 p 元素 */
p { color: black; }

/* 类选择器: 所有 class="container" 的元素, 用得最多 */
.container { width: 100%; }

/* ID 选择器: id="header" 的那一个元素 */
#header { background: white; }

/* 通配符: 页面上所有元素 */
* { margin: 0; }

class 前面加点, id 前面加井号, 这两个符号一开始容易记混, 写几次就熟了。日常九成场景用类选择器: HTML 里给元素起好 class, CSS 里按 class 选。

组合选择器

选择器可以组合起来, 表达”谁里面的谁”:

代码块CSS · 5 行收起展开
/* 后代选择器 (空格): div 里面所有层级的 p */
div p { color: blue; }

/* 子选择器 (>): 只选 div 的直接子级 p, 孙子辈不算 */
div > p { color: red; }

还有相邻兄弟 h1 + p、通用兄弟 h1 ~ p 这类写法, 见到能认出来就行, 用得不多。

伪类: 按状态选中

同一个元素在不同状态下可以长得不一样。状态用冒号接在选择器后面, 这类选择器叫伪类:

代码块CSS · 7 行收起展开
a:hover { color: red; }              /* 鼠标悬停在链接上时 */
input:focus { border-color: blue; }  /* 输入框获得光标时 */
input:disabled { opacity: 0.5; }     /* 禁用状态半透明 */

/* 结构伪类: 按元素在父级里的位置选 */
li:first-child { font-weight: bold; }       /* 第一个 li */
li:nth-child(odd) { background: #f0f0f0; }  /* 奇数行做斑马纹; # 开头是十六进制颜色值, 这个是浅灰 */

hover 是最常用的一个, 网页上按钮”摸上去变色”的效果全靠它。

另有一类双冒号的”伪元素”, 比如 p::first-letter 选中段落首字母、.quote::before 在元素前面插入内容。知道有这东西即可, 需要时再查。

优先级: 规则打架听谁的

多条规则命中同一个元素时的胜负逻辑分两层。

第一层看来源, 从弱到强: 浏览器默认样式、外部/内部样式表、内联样式、带 !important 标记的规则。这条覆盖链和 Spring Boot 配置的优先级一个思路: 默认配置最弱, application.yml 盖过默认值, 命令行参数盖过一切, 越具体的来源越强。

第二层, 同在样式表里的规则比选择器权重: ID 选择器算 100 分, 类/伪类算 10 分, 标签选择器算 1 分, 加总分高者胜, 同分时写在后面的赢:

代码块CSS · 4 行收起展开
div { color: red; }        /* 权重 1 */
.box { color: blue; }      /* 权重 10 */
div.box { color: purple; } /* 权重 11, 比 .box 高 */
#header { color: green; }  /* 权重 100, 带 id 基本稳赢 */

!important 写在值后面可以强行获胜, 但它会把整个优先级体系搅乱, 之后排查样式问题非常头疼, 尽量别用。

盒模型: 每个元素都是一个盒子

这是 CSS 最核心的概念。在浏览器眼里, 页面上每个元素都是一个矩形盒子, 从里到外分四层:

CSS 盒模型:从里到外 content、padding、border、margin 四层嵌套

content 是内容区, 文字图片待的地方; padding 是内边距, 内容和边框之间的留白; border 是边框; margin 是外边距, 这个盒子和别的盒子之间的透明距离。

代码块CSS · 7 行收起展开
.box {
  width: 200px;             /* 内容区宽度 */
  height: 100px;
  padding: 10px;            /* 四边各 10px 内边距 */
  border: 1px solid #ccc;   /* 1px 宽的灰色实线边框 */
  margin: 20px;             /* 四边各 20px 外边距 */
}

这里有个新手必踩的坑: 默认规则下 width 只算内容区, 上面这个盒子实际占的宽度是 200 + 102 + 12 = 222px。
我第一次并排摆两个宽 50% 的盒子, 加了 padding 之后总宽超过 100%, 第二个直接被挤到下一行, 找了半天才明白原因。解决办法是改用 border-box 计法:

代码块CSS · 3 行收起展开
* {
  box-sizing: border-box;  /* 让 width 直接等于 content+padding+border 的总宽 */
}

几乎所有项目开头都会写这么一条, 让宽度变成”所见即所得”。

简写与居中

padding 和 margin 支持一行写多边, 顺序固定为顺时针 (上右下左):

代码块CSS · 5 行收起展开
.box {
  padding: 10px 20px;           /* 上下 10, 左右 20 */
  padding: 10px 20px 15px 5px;  /* 上 右 下 左 */
  margin: 0 auto;               /* 上下 0, 左右自动平分: 定宽块水平居中的老牌写法 */
}

display: 块与行内

盒子在页面上怎么排队, 由 display 决定。最基本的几种:

代码块CSS · 6 行收起展开
.element {
  display: block;         /* 块级: 独占一行, 可设宽高。div/p/h1 默认如此 */
  display: inline;        /* 行内: 嵌在文本流里, 设宽高无效。span/a 默认如此 */
  display: inline-block;  /* 行内排列, 但能设宽高 */
  display: none;          /* 从页面上彻底消失, 位置也不占 */
}

隐藏元素还有两个相近写法: visibility: hidden 隐藏但原位置还占着; opacity: 0 完全透明但人还在原地, 甚至还能被点到。三者的区别在调页面时经常用到。

下面要讲的 flex 和 grid, 其实也是 display 的取值。

Flex: 现在的主流布局方式

先说痛点。早年 CSS 里想”把几个盒子横着排、对齐、居中”要靠 float (一个本来给图文环绕用的老属性) 加各种奇技淫巧, 垂直居中更是老大难问题。Flexbox 出现后这些全变成一两行代码的事, 现在做布局默认就用它。

用法: 给父容器设 display: flex, 它的直接子元素就变成”弹性项目”, 默认横向排成一排。容器上主要就调这几个属性:

代码块CSS · 8 行收起展开
.container {
  display: flex;
  flex-direction: row;      /* 主轴方向: row 横排 (默认), column 竖排 */
  justify-content: center;  /* 子元素沿主轴方向怎么分布 */
  align-items: center;      /* 沿交叉轴 (垂直于主轴) 怎么对齐 */
  gap: 10px;                /* 子元素之间的间距 */
  flex-wrap: wrap;          /* 放不下时允许换行 */
}

主轴、交叉轴听着抽象, 人话版: 横排时主轴是水平方向, justify-content 管水平分布, align-items 管垂直对齐; 改成竖排 (column) 时两者职责对调。

justify-content 常用值: flex-start 靠头、center 居中、flex-end 靠尾、space-between 两端顶满中间平分。align-items 常用 center (居中) 和 stretch (拉伸填满, 默认值)。

子元素上最常用的是 flex: 1, 意思是”把剩余空间占满”:

代码块CSS · 4 行收起展开
.item {
  flex: 1;          /* 吃掉剩余空间; 多个 item 都写 flex: 1 就是均分 */
  flex-shrink: 0;   /* 空间不够时也不许压缩我 */
}

可以直接背下来的布局配方

水平垂直居中, 曾经的老大难, 现在四行:

代码块CSS · 6 行收起展开
.center {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;   /* vh 是视口高度单位, 100vh 即整屏高, 响应式一节细讲 */
}

侧边栏加主内容的两栏布局:

代码块CSS · 6 行收起展开
.container { display: flex; }
.sidebar {
  width: 200px;
  flex-shrink: 0;   /* 侧边栏定宽, 不许被压缩 */
}
.main { flex: 1; }  /* 主内容占满剩下的宽度 */

页头、内容、页脚三段式, 内容不够高时页脚也钉在屏幕底部:

代码块CSS · 7 行收起展开
body {
  display: flex;
  flex-direction: column;  /* 改成竖排 */
  min-height: 100vh;       /* 至少一屏高 */
}
header, footer { flex-shrink: 0; }
main { flex: 1; }          /* 中间内容吃掉多余高度, 把 footer 顶到底 */

Grid: 知道有这东西就行

Flex 是一维布局, 一次管一行或一列; Grid 是二维的网格布局, 适合”几行几列”的场景, 比如相册、仪表盘:

代码块CSS · 5 行收起展开
.container {
  display: grid;
  grid-template-columns: 1fr 2fr 1fr;  /* 三列, 宽度按 1:2:1 分, fr 是比例单位 */
  gap: 20px;
}

现阶段 Flex 能覆盖绝大多数需求, Grid 等真遇到复杂网格再回来学, 这里先混个脸熟。

position: 把元素钉在某个位置

大部分元素乖乖排在文档流里 (元素默认从上到下依次排队占位置的那套秩序), 但有些场景需要”钉住”, 比如固定在顶部的导航栏、右下角的回到顶部按钮。position 的常用取值:

代码块CSS · 10 行收起展开
.element {
  position: relative;  /* 相对自己原位置偏移, 原来的坑还占着 */
  position: absolute;  /* 脱离文档流, 相对最近的设了 position 的祖先定位 */
  position: fixed;     /* 相对浏览器窗口定位, 页面滚动它不动, 导航栏常用 */
  position: sticky;    /* 平时正常排, 滚动到阈值后吸附, 做吸顶效果 */

  top: 10px;           /* 定位后用方位属性微调 */
  left: 20px;
  z-index: 10;         /* 元素重叠时谁压谁, 数字大的在上面 */
}

absolute 有个固定搭配: 父元素设 position: relative 当参照物, 子元素用 absolute 在它内部精确摆位。

响应式入门: 一份页面适配多种屏幕

手机和电脑的屏幕宽度差好几倍, 同一个页面要都能看。核心工具是媒体查询: 按屏幕宽度应用不同的样式。

代码块CSS · 15 行收起展开
/* 默认样式, 给窄屏 (手机) 用 */
.container { width: 100%; }

/* 屏幕宽度达到 768px (平板) 以上时覆盖 */
@media (min-width: 768px) {
  .container {
    width: 750px;
    margin: 0 auto;
  }
}

/* 达到 1024px (桌面) 以上再覆盖一次 */
@media (min-width: 1024px) {
  .container { width: 960px; }
}

习惯上以 768px 和 1024px 作为手机、平板、桌面的分界线。这种”默认样式给手机, 大屏幕用 min-width 逐级覆盖”的写法叫移动优先, 是目前的主流做法。

相对单位

px 是绝对像素, 响应式场景更常用相对单位:

代码块CSS · 7 行收起展开
.element {
  width: 50%;         /* 父元素宽度的一半 */
  font-size: 1.2rem;  /* 根元素 (html) 字号的 1.2 倍, 调根字号可全站缩放 */
  font-size: 1.2em;   /* 父元素字号的 1.2 倍, 嵌套时层层放大, 少用 */
  width: 50vw;        /* 视口宽度的 50%, vw 即 viewport width */
  height: 100vh;      /* 视口高度的 100%, 正好一整屏 */
}

常用外观属性速查

写页面时高频用到的属性, 放这里备查, 不用背。

文本

代码块CSS · 9 行收起展开
.text {
  color: #333;                     /* 文字颜色, #333 是深灰 */
  font-size: 16px;
  font-weight: bold;               /* 粗细: normal / bold / 100~900 */
  font-family: Arial, sans-serif;  /* 字体, 逗号后面是备选 */
  line-height: 1.5;                /* 行高为字号的 1.5 倍, 中文正文常用 */
  text-align: center;              /* 水平对齐: left / center / right */
  text-decoration: none;           /* 去掉下划线, 常用在链接上 */
}

背景与边框

代码块CSS · 10 行收起展开
.box {
  background-color: #f0f0f0;
  background-image: url('bg.jpg');
  background-size: cover;       /* 图片铺满盒子, 多余部分裁掉 */
  background-position: center;
  background-repeat: no-repeat;

  border: 1px solid #ccc;
  border-radius: 5px;           /* 圆角; 设 50% 能把正方形变成圆 */
}

几个救急技巧

项目开头的清除默认样式。浏览器给很多标签自带 margin 和 padding, 而且各家浏览器还不一致, 先清零图个干净:

代码块CSS · 5 行收起展开
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

文字太长时显示省略号:

代码块CSS · 5 行收起展开
.text {
  overflow: hidden;        /* 超出部分藏起来 */
  text-overflow: ellipsis; /* 藏掉的部分显示成省略号 */
  white-space: nowrap;     /* 禁止换行。三行合称单行省略三件套 */
}

锁定宽高比, 视频封面 16:9 之类的场景常用:

代码块CSS · 3 行收起展开
.box {
  aspect-ratio: 16 / 9;  /* 宽度变化时高度按比例跟着变 */
}

下一步

到这里, 页面已经能做到结构清楚、样式好看, 但它还是死的: 点按钮没反应, 数据不会变。让页面动起来靠 JavaScript, 也是整条前端路线里分量最重的一篇: JavaScript 基础 - 语法与 DOM

延伸阅读