[编程开发] 如何精通CSS? 或者说如何有效地提高编写CSS 的能力?

[复制链接]
sdwgw 发表于 2023-12-8 14:58:03|来自:中国 | 显示全部楼层 |阅读模式
如何精通CSS? 或者说如何有效地提高编写CSS 的能力?
全部回复5 显示全部楼层
刘德华摸周杰伦 发表于 2023-12-8 14:58:26|来自:中国 | 显示全部楼层
我已经从事Web前端开发多年了,CSS 平时用起来也挺熟练的,但是总觉得自己对它理解得又不够透彻,所以一直想再系统的学习总结一遍,所以本文的重点并是不堆砌基础知识,在很多基础的地方会有省略、一笔带过。
至于重点是什么,我觉得应该就是那些我踩过的坑,我没有理解透彻的东西,平时用的时候需要反复调试,去试验才能调出来的东西,亦或是一些我一直说不清楚的概念吧。
盒模型

基础概念

注:IE 以及淘汰了,故不再讨论 IE 的盒模型。
所有 HTML 元素都可以看成盒子,盒子的构成可以用一个盒模型来描述。
盒模型的组成:外边距(margin)、边框(border)、内边距(padding)、内容(content)。如下图所示:


举个例子:
css复制代码.test {
     width: 200px;
     height: 200px;
     margin: 50px;
     padding: 50px;
     border: 50px solid black;
     background-color: brown;
}
html复制代码<body>
     <div class="test"></div>
</body>界面效果:


控制台查看其盒模型效果:


元素的 width 和 height 指的就是其中间的 content 的大小,元素的总宽高是由 content 的大小以及 margin、padding、border 共同决定的。
background-color 默认会影响 content 与 padding 的背景颜色,除非改变 background-clip 的配置。
作为一名前端开发者,我深知科技革命的推动离不开技术精英们的不断探索和努力。在我们的领域中,不断学习和掌握新知识也是我们不断进步的关键,而阅读是一个非常重要的途径,尤其是阅读关于前端开发的优秀书籍和文献,这些内容会成为我们成长路上的良师益友。
我个人认为,阅读是前端开发者中最具效益的个人成长方式之一。尽管书籍价格不高,但它们的内容和深度却可以帮助我们深入了解前端开发的各种技术和概念,这些知识也是我们在工作中不断迭代、改进和创新的基础。
因此,我强烈建议前端开发者在职业发展过程中注重阅读。如果你希望在前端开发领域获得更深入的发展,我建议你挑选一些有深度、有价值的书籍,并在实践中不断运用和总结,这将会帮助你不断提高自己的技术水平和能力。
以下这份包含103本前端经典书籍的书单(其中32本是豆瓣评分8分+),是我花费一个月时间整理的:








书单我已经帮大家打包好了,点击下方链接直接获取:
2023年前端全套学习资料(视频+配套资料+源码)免费领取103本前端开发电子书(含代码)轮廓(Outline)

轮廓(outline)是绘制于元素周围的一条线,位于边框边缘的外围,可起到突出元素的作用。他的位置如下图所示,outline 不会占用元素的宽高等,即使空间不够用他也不会对元素的位置产生影响。


举例:
css复制代码.test {
     width: 200px;
     height: 200px;
     margin: 50px;
     padding: 50px;
     border: 50px solid black;
     outline: 20px dotted green;
     background-color: brown;
}

BFC

BFC (Block Formatting Context) 即块格式化上下文,它是一个独立的布局环境,其中的元素不受外界的影响。
要搞清楚 BFC 的作用,我们先来看下面几个问题。
margin 坍塌问题

同级元素垂直方向上的坍塌(水平方向上不会坍塌),比如:上面的元素设置了 margin-bottom,下面的元素设置了 margin-top,这种情况下,会按照较大者去显示。
把基础概念那个例子的 html 修改一下:
html复制代码<body>
     <div class="test"></div>
     <div class="test"></div>
</body>显示效果如下:


修改代码解决问题:
css复制代码.container {
   overflow: hidden;
}
html复制代码<div class="container">
   <div class="test"></div>
</div>
<div class="container">
   <div class="test"></div>
</div>父子元素之间的坍塌,也是垂直方向上会坍塌,水平方向上不会坍塌。比如:父元素设置了 margin-top,子元素也设置了 margin-top,这种情况下,会按照较大者去显示,或者只设置子元素的 margin-top,那么父元素会随子元素一起掉下来。
例子如下:
css复制代码.father {
   width: 300px;
   height: 300px;
   margin-top: 50px;
   margin-left: 50px;
   background-color: brown;
}

.son {
   width: 100px;
   height: 100px;
   margin-top: 60px;
   margin-left: 60px;
   background-color: black;
}
html复制代码<body>
     <div class="father">
         <div class="son"></div>
     </div>
</body>显示效果如下:


修改代码解决问题:
css复制代码.container {
   overflow: hidden;
}
html复制代码<div class="father">
   <div class="container">
     <div class="son"></div>
   </div>
</div>浮动塌陷问题

元素设置了 float 之后会,会脱离文档流,原本父元素会包裹住子元素,但是子元素脱离了文档流,所以子元素就会变成未被父元素包裹的样子,父元素本应该被撑开的高度也会坍塌。
css复制代码.container {
   border: 1px solid red;
}

.test {
   width: 100px;
   height: 100px;
   background-color: blue;
   float: left;
}
html复制代码<div class="container">
   <div class="test"></div>
</div>显示效果如下图所示:


修改代码解决问题:
.container {
   border: 1px solid red;
+ overflow: hidden;
}浮动元素覆盖问题

一个元素设置了浮动,另一个元素未设置浮动,浮动元素会覆盖未添加浮动的元素。
css复制代码.test1 {
   width: 100px;
   height: 100px;
   background-color: blue;
   float: left;
}

.test2 {
   width: 200px;
   height: 200px;
   background-color: red;
}
html复制代码<div class="test1"></div>
<div class="test2"></div>显示效果如下图所示:


修改代码解决问题:
diff复制代码.test2 {
  width: 200px;
  height: 200px;
  background-color: red;
+ overflow: hidden;
}
总结

BFC 的作用:

  • 解决 margin 塌陷问题
  • 清除浮动
  • 阻止元素被浮动元素覆盖
BFC 如何创建:

  • 根元素(<html>)
  • 浮动元素(元素的 float 不是 none)
  • 绝对定位元素(元素的 position 为 absolute 或 fixed)
  • display 为 inline-block、table-cell、table-caption、table、table-row、table-row-group、table-header-group、table-footer-group、inline-table、flow-root、flex 或 inline-flex 或 inline-grid
  • overflow 值不为 visible 的块元素
  • contain 值为 layout、content 或 paint 的元素
  • 多列容器(元素的 column-count 或 column-width 不为 auto,包括 column-count 为 1)
物理属性与逻辑属性

书写模式

书写模式是为了支持不同语言的书写习惯而生的,比如:中文、英文、法语等都是从左到右书写的,而文言文是从上到下书写的,但是阿拉伯语则是从右往左书写的。
下面是一个例子:
html复制代码<body>
  <div style="width: 100px;">Hello World 1234我就是一段普普通通的文字,默认效果</div>
  <br />
  <div style="width: 100px; writing-mode: horizontal-tb;">Hello World 1234我就是一段普普通通的文字,从左往右从上到下水平书写的</div>
  <br />
  <div style="height: 100px; writing-mode: vertical-lr;">Hello World 1234我就是一段普普通通的文字,从上到下从左到右垂直书写的</div>
  <br />
  <div style="height: 100px; writing-mode: vertical-rl;">Hello World 1234我就是一段普普通通的文字,从上到下从右到左垂直书写的</div>
</body>效果图如下:



配合 HTML 的 dir 属性和 CSS 的 direction 属性还可以改变元素从左到右排还是从右到左排,他们在配合 writing-mode 使用之后会有不同的效果。
html复制代码<body dir="rtl">
  <div>Hello World 1234我就是一段普普通通的文字</div>
  <br />
  <div style="direction: ltr;">Hello World 1234我就是一段普普通通的文字</div>
</body>效果:


物理属性

物理属性即含有 left、right、top 和 bottom 物理位置关键字的属性,它们与你看到的屏幕紧密相关,左永远是左,不管文本流动的方向如何。
物理属性在我们修改书写模式之后可能会存在一些问题,请看下面这个例子:
首先是中文:
html复制代码<body>
   <div style="direction: ltr;">
     <div style="margin-left: 20px; margin-top: 10px;">中文TITLE</div>
   </div>
</body>

然后是阿拉伯语:
html复制代码<body>
  <div style="direction: rtl;">
    <div style="margin-left: 20px; margin-top: 10px;">              </div>
  </div>
</body>

看出问题了吗?如果我们是一个国际化的项目,当我们切换成阿拉伯语之后,按照阿拉伯语的书写习惯,margin 应该在右侧才对,那么只是单纯的切换 direction 还不行,还得改 CSS 代码。不过使用逻辑属性编写就不需要修改 CSS 代码了。
逻辑属性

逻辑属性定义了在相对方向下与对应的实体属性相等价的属性。


现在我们就可以利用逻辑属性来解决上面的问题了:
html复制代码<body>
  <div style="direction: rtl;">
    <div style="margin-inline-start: 20px; margin-block-start: 10px;">              </div>
  </div>
</body>


选择器

选择器优先级从高到低依次为:

  • !important
  • 内联选择器(style)
  • ID选择器
  • 类选择器 == 属性选择器 == 伪类选择器(:hover、:active、:visited、:focus、:first-child、:last-child、:nth-child(n)等)
  • 元素选择器(div、p、span等) == 伪元素选择器(::before、::after、::first-letter、::first-line)
  • 通配符选择器(*) == 子选择器(#parent > .child) == 相邻选择器(#first + p) == 后代选择器(#menu li)。
如果有多个选择器的优先级相同,则后面的选择器会覆盖前面的选择器。同时,选择器的优先级计算是针对单个元素的,当一个元素同时被多个选择器选中时,会根据它们的优先级进行选择。
布局

了解一下布局的发展史。
文档布局

大概出现在1991年,最最最古早的布局,其实也算不上什么布局,仅仅依靠 HTML 的文档顺序来组织和展示 Web 页面信息。
表格布局

大概出现在1996年,table 其实本来不是用来布局的,但是表格布局在Web早期应用中是很流行的一种布局方式,不过可维护性不高,现在基本不会再用到了。
流式布局

1996年,CSS 出现将 Web 的结构(HTML)和样式分离。流式布局也逐渐出现,因为要适应不同屏幕的不同分辨率,于是出现了很多相对单位(例如百分比,所以流式布局也常被称为百分比布局)。流式布局之所以叫这个名字也是因为页面中的元素会像水一样,容器有多大,元素就会自适应变化。
浮动布局/定位布局

1998年,CSS 2.0发布,这也使得 Web 的布局进入到了一个新时代。
浮动布局就是靠 float 属性实现的,定位布局就是靠 position 属性实现的,它们都是 CSS 2.0 的特性。
响应式布局

响应式布局(RWD,Responsive Web Design),是一种可以在不同设备和屏幕大小上均能自适应的 Web 设计方法,我们开发的 Web 页面会“响应”用户的设备尺寸而自动调整布局,它的出现最早可以追溯到2010年。
响应式的出现和移动端 Web 场景的需求密切相关,很长一段时间里,很多网站的移动端和 PC 端是各自有一个版本,极大增加了适配工作量。
实现响应式主要用到了Flex布局、Grid布局以及媒体查询三种技术。
文本相关

文本空格及断行处理

word-break

指定单词内断行方式。

  • normal:默认断行规则,单词之间自动断行,CJK (CJK 指中文/日文/韩文) 文本按单个字断行。
  • break-all:对于 non-CJK 文本,可在任意字符间断行。
  • keep-all:CJK 文本不断行。Non-CJK 文本表现同 normal。
css复制代码div {
   width: 130px;
}

.break1 {
   word-break: normal;
}

.break2 {
   word-break: break-all;
}

.break3 {
   word-break: keep-all;
}
html复制代码<div class="break1">
   Hello World! Hello World! Hello World! Hello World! 你好世界! 你好世界! 你好世界! 你好世界!
</div>
<div class="break2">
   Hello World! Hello World! Hello World! Hello World! 你好世界! 你好世界! 你好世界! 你好世界!
</div>
<div class="break3">
   Hello World! Hello World! Hello World! Hello World! 你好世界! 你好世界! 你好世界! 你好世界!
</div>效果如下图所示:




word-wrap

指定长单词或URL地址的换行规则。

  • normal:不换行,可能会超出容器。
  • word-break:允许长单词或URL地址根据容器宽度进度换行。
css复制代码div {
   width: 100px;
   border: 1px solid red;
}

.wrap1 {
   word-wrap: normal;
}

.wrap2 {
   word-wrap: break-word;
}
html复制代码<div class="wrap1">
   https://juejin.com
</div>
<div class="wrap2">
   https://juejin.com
</div>效果如下图所示:




white-space

指定处理元素中空白(空格、制表符、换行符等)的策略。

  • normal:连续的空白符会被合并,换行符会被当作空白符来处理。
  • nowrap:和 normal 一样,连续的空白符会被合并。但文本内的换行无效。
  • pre:连续的空白符会被保留。在遇到换行符或者 <br> 元素时才会换行。
  • pre-wrap:连续的空白符会被保留。在遇到换行符或者 <br> 元素,或者需要为了填充容器时才会换行。
  • pre-line:连续的空白符会被合并。在遇到换行符或者 <br> 元素,或者需要为了填充容器时会换行。
  • break-spaces 与 pre-wrap的行为相同,除了:

  • 任何保留的空白序列总是占用空间,包括在行尾。
  • 每个保留的空格字符后都存在换行机会,包括空格字符之间。
  • 这样保留的空间占用空间而不会挂起,从而影响盒子的固有尺寸(最小内容大小和最大内容大小)。

javascript复制代码for (let i = 1; i <= 6; i++) {
   const div = document.getElementById(`ws${i}`);

   div.innerHTML =
`But ere she from the church-door stepped
      She smiled and told us why:
'It was a wicked woman's curse,'
      Quoth she, 'and what care I?'

She smiled, and smiled, and passed it off
      Ere from the door she stept—`;
}
css复制代码div {
   width: 232px;
   border: 1px solid red;
}
html复制代码<div id="ws1" style="white-space: normal;"></div>
<div id="ws2" style="white-space: nowrap;"></div>
<div id="ws3" style="white-space: pre;"></div>
<div id="ws4" style="white-space: pre-wrap;"></div>
<div id="ws5" style="white-space: pre-line;"></div>
<div id="ws6" style="white-space: break-spaces;"></div>
效果如下图所示:




word-spacing

控制单词之间的空格长度。
css复制代码.space1 {
  word-spacing: normal;
}

.space2 {
  word-spacing: 1rem;
}
html复制代码<div class="space1">
  Hello World,你 好 世 界
</div>
<div class="space2">
  Hello World,你 好 世 界
</div>效果如下图所示:




font-family

font-family 其实不能简单理解为字体,它实际上是一个优先级从高到低的一个字体列表或者字体族名列表,前面的字体(或族名)如果系统没有就继续找后面的字体(或族名),直到找到为止。另外,我们通常写的 serif、monospace、monospace、Helvetica 等也不是字体,而是字体族名,是用来描述一组相似设计风格的字体的名称,它们通常由一个主要的字体家族名称和一些特定字体样式组成。例如,Helvetica 是一个常见的字体族名,其中包含了 Helvetica Regular、Helvetica Bold、Helvetica Italic 等字体样式。
常见字体族名:

  • serif:带衬线字体,笔画结尾有特殊的装饰线或衬线。
  • sans-serif:无衬线字体,即笔画结尾是平滑的字体。
  • monospace:等宽字体,即字体中每个字宽度相同。
  • cursive:草书字体。这种字体有的有连笔,有的还有特殊的斜体效果。因为一般这种字体都有一点连笔效果,所以会给人一种手写的感觉。
  • system-ui:从浏览器所处平台处获取的默认用户界面字体。由于世界各地的排版习惯之间有很大的差异,因此为那些不适合其他通用字体的字体提供了这个通用字体。
html复制代码<div>Hello World! 你好世界!</div>
<div style="font-family: serif;">serif Hello World! 你好世界!</div>
<div style="font-family: sans-serif;">sans-serif Hello World! 你好世界!</div>
<div style="font-family: monospace;">monospace Hello World! 你好世界!</div>
<div style="font-family: cursive;">cursive Hello World! 你好世界!</div>
<div style="font-family: system-ui;">system-ui Hello World! 你好世界!</div>
<div style="font-family: Arial, Helvetica, sans-serif;">Arial, Helvetica, sans-serif Hello World! 你好世界!</div>
<div style="font-family: 'Courier New', Courier, monospace">'Courier New', Courier, monospace Hello World! 你好世界!</div>效果如下图所示:




外部字体引入

外部字体引入的第一步是定义字体,使用 @font-face 关键字定义字体,然后再用 font-famliy 使用即可。
比如,我这里在网上下了一个字体叫”美神勇兔生肖“
css复制代码@font-face {
   font-family: MeiShenYongTuShengXiao;
   src: url('./MeiShenYongTuShengXiao.ttf');
}
html
复制代码<h1 style="font-family: MeiShenYongTuShengXiao;">Hello World! 你好世界!</h1>效果如下图所示:



字体图标

字体图标是一种矢量图形,用于代表各种不同的符号、图标或图形,我们可以使用 CSS 去修改其样式就像字体一样,小巧方便,要比图片要轻很多。
比较流行的字体图表库有:

  • FontAwesome
  • iconfont-阿里巴巴矢量图标库
  • Material Icons
具体的使用方式直接看官方文档就好了。
CSS 变量

以 -- 开头,靠 var() 来使用。
两种用法展示:
局部变量
css复制代码div {
   --main-bg-color: brown;
}

.one {
   color: var(--main-bg-color);
}
html复制代码<div class="one">Hello World! 你好世界!</div>
<p class="one">Hello World! 你好世界!</p>效果如下图所示:




如果把 div 改为 :root,就可以全局使用这个变量了。
各种 deep 写法

deep 写法其实不是 CSS 的写法,这是在各个框架搞出了带作用域(scoped)的 style 之后才需要的东西。
举个例子,假设我现在正在使用 Ant Design Vue,我想用它的按钮组件,并且需要简单修改一下这个按钮组件的样式, 我在一个 div 下放了三个这样的按钮组件:
子组件:
vue复制代码<script setup lang="ts"></script>

<script lang="ts">
export default { name: 'MyBtns' };
</script>

<template>
   <div>
     <a-button type="primary">{{ 'btn1' }}</a-button>
     <a-button type="primary">{{ 'btn2' }}</a-button>
     <a-button type="primary">{{ 'btn3' }}</a-button>
   </div>
</template>

<style scoped lang="less"></style>父组件:
vue复制代码<script setup lang="ts">
import MyBtns from './btns.vue';
</script>

<template>
   <my-btns />
</template>

<style scoped lang="less">
.ant-btn {
   color: orange;
}
</style>如果这样写子组件的按钮的颜色肯定是不会变的,因为 .ant-btn 是放在父组件的样式作用域里面的,子组件有自己的作用域,他们就被隔离起来了,如果要想子组件中的 .ant-btn 样式也能生效就需要加 :deep 函数。
css复制代码:deep(.ant-btn) {
   color: orange;
}过去 :deep() 函数还有各种写法:::deep、>>>、/deep/、v-deep 等,但是这些写法都被淘汰了,就不要再用了。
React 中要实现类型功能需要用到 :global,当然 Vue 中也有 :global() 函数,相比之下 Vue 提供的方案更加全面。
Angular 中的 :host ::ng-deep 就和:deep() 函数的效果一样,如果不加 :host 会直接作用于全局。
原子化 CSS

原子化 CSS 是一种 CSS 编写的方法论,它的主要思想是将 CSS 样式规则拆分成小的、独立的、可复用的部分,这些部分被称为“原子类”
在原子化 CSS 中,每个原子类只包含一个很小的样式规则,比如设置颜色、字体大小、边距、背景等。通过将这些小的样式规则组合起来,可以构建出复杂的页面。
原子化 CSS 的优势包括:

  • 可复用性:由于样式规则被拆分成独立的原子类,可以在不同的元素上重复使用。这样可以减少代码冗余并提高代码的可维护性。
  • 灵活性:通过组合不同的原子类,可以快速构建出不同样式和布局的组件。
  • 性能优化:由于原子类是独立的小样式规则,可以利用浏览器的缓存机制,减少下载和解析的数据量,从而提高页面加载速度。
  • 易于维护:每个原子类只包含一个样式规则,修改和扩展样式更加清晰和方便。
尽管原子化 CSS 有一些好处,但也存在一些缺点和考虑因素:

  • 学习曲线:原子化 CSS 需要掌握一套新的类命名规则和组合方式,这可能需要一定的学习曲线。特别是对于团队中新加入的开发人员,需要一定的时间来适应和理解原子化 CSS 的工作方式。
  • 类名冲突:当原子类命名不规范或命名空间不清晰时,可能会导致类名冲突的问题。如果不小心定义了相同的类名,可能会造成样式的混乱和冲突。
  • 管理成本:由于原子化 CSS 以小的样式单元为基础,可能会导致样式表的冗余和重复。在大型项目中,管理和维护大量的原子类可能会变得复杂,并需要额外的工具和规范来确保一致性和可维护性。
  • 语义性问题:原子化 CSS 通常使用简短的类名来表示样式规则,这可能会导致语义性的丧失。开发人员和设计师可能需要花费额外的时间来理解每个类名的含义和作用。
  • 深度嵌套的问题:原子化 CSS 通常倾向于使用多个类名来定义样式,这可能导致在 HTML 标记中出现深度嵌套的问题。这可能会增加 HTML 的复杂性,并可能使选择器的性能受到影响。
因此,在决定是否使用原子化CSS时,需要考虑项目的规模、团队的技术能力以及维护成本等因素,权衡好其中的利与弊。
原子化 CSS 最流行的框架应该就是 Tailwind CSS 了。
来源 :https://juejin.cn/post/7252684645991694397
往期干货

【前端行业发展】:


  • Web 前端分为哪几个大方向,工资待遇如何,辛苦吗?
  • 找前端工作会不会很难?
  • 现在web前端的工资怎样?
  • 前端开发就业情况如何?
【前端工作内容】:


  • 前端工程师主要工作内容是什么?
  • 前端开发是做什么的?工作职责有哪些?
【前端学习路线】:


  • 0基础入门学前端,2023年前端学习路线总结,前端开发需要学什么?
【前端自学网站】:


  • 【建议收藏】91个前端开发必备学习国内外免费网站和个人博客
  • 13 个有趣且实用的 CSS 框架 / 组件
  • 有哪些好的前端社区?
【前端书籍推荐(32本)】:


  • 【建议收藏】2023前端书籍,前端开发入门与前端学习路线
【前端面试题】:


  • 【建议收藏】4500字前端面试题及答案汇总,前端八股文
  • 关于前端Vue框架的面试题,面试官可能会问到哪些?
  • 【一定要收藏】32000字的前端面试题总结!!!
  • (建议收藏)Vue面试热点问题
【前端简历模板】:


  • web前端简历个人技能该怎么写?
  • 前端简历中项目描述怎么写能够更加的优雅?
  • 一个优秀的前端工程师简历应该是怎样的?
  • 前端简历怎么写?前端尽力模板,4个动作帮你搞定心仪Offer
  • 面试前端工程师简历应该怎么写才容易通过?
  • 自学 web 前端人怎么找工作?
  • 自学前端简历怎么写啊?
【HTML教程】:


  • HTML5入门教程(含新特性),从入门到精通
  • HTML图文教程(表单域/文本框与密码框/单选按钮与复选框)
  • HTML图文教程(选按钮与复选框/input标签/提交按钮与重置按钮)
  • HTML图文教程(通按钮/文件域/label/下拉表单)
  • HTML零基础入门教程, 零基础学习HTML网页制作(HTML基本结构)
  • HTML零基础入门教程, 零基础学习HTML网页制作(HTML 标签)
【JavaScript教程】:


  • JavaScript 是什么?
  • JavaScript基础入门教程(引入方式/注释/变量/常量/数据类型/类型转换)
  • JavaScript基础入门教程(引入方式/注释/变量/数据类型/类型转换)
  • JavaScript基础入门教程(for循环/数组)
  • JavaScript基础入门教程(函数/返回值/作用域)
  • JavaScript基础入门教程(对象/内置对象)
  • JavaScript进阶教程(作用域/函数/解构赋值)
  • JavaScript进阶教程(构造函数/内置函数/继承/封装)
  • JavaScript进阶教程(深浅拷贝/异常/this/防抖节流)
  • JavaScript函数(函数创建和使用、参数传递)
  • JavaScript函数(函数返回值)
  • JavaScript数函(作用域和局部变量)
  • JavaScript函数(模态框插件的封装)
【VUE教程】:


  • Vue基础入门教程(vue-cli+vue指令)
  • Vue基础入门教程(vue指令大全)
  • Vue基础入门教程(vue组件化开发)
  • 最全的Vuex学习教程(Vuex基础、模块化、案例)
【Koa2教程】:


  • Koa2框架是什么?Koa框架教程快速入门Koa中间件
  • Koa2框架路由应用,Koa2前景、Koa2中间件
  • Koa2异常处理
【我的朋友们】


  • 少儿编程从业者,选择大于努力。
  • 无锡餐厅继承人如何转行前端,人生路皆由自己选,不畏艰辛!
  • 国际海运,为何选择了转行计算机,分享我的学习经历
  • 景观设计师如何转行前端开发?
  • 自学前端,工作2月被裁,系统学习终于高薪上岸!!
  • 计算机科班放弃考研,在学校系统补齐前端知识,50分钟拿下秋招
  • Offer收割机,电子商务专业,大学开始系统学前端,秋招斩获4个offer!
  • 计算机科班,2年Java转前端
  • 通信工程专业,我是如何自学前端的?
如果对你有帮助的话,点个赞收个藏,给作者一个鼓励。也方便你下次能够快速查找。最后,希望喜欢学习的你们,坚持下去,做一个有知识的前端人,加油~
今天先分享到这里,写了好久的回答,大家收藏的时候,也记得点个赞哈!
boyhxw 发表于 2023-12-8 14:59:03|来自:中国 | 显示全部楼层
最近,有靓仔吐槽在编译css代码时,每次写选择器都会变成CV大神,虽说有CV加持但是呢依然会觉得很麻烦,毕竟手速不像年轻时候那样为所欲为
在这里呢给推荐大家用一款神级插件,也是小编参与完成的轻量级插件--sass,该插件特点,简单易上手,不需过多配置,事少活好,简直编程一尤物。
那么我们动手前先简单了解一下什么是Sass预编译,Sass (英文全称:Syntactically Awesome Stylesheets) 一个最初由Hampton Catlin设计并由Natalie Weizenbaum开发的层叠样式表语言,是CSS的预处理器,
为什么要用它呢?Css本身语法不够强大,导致重复编写一些代码,无法实现复用,而且代码也不方便维护。有了Sass可以帮助我们减少CSS重复的代码,节省开发时间。
并且Sass 扩展了 CSS3,引入合理的样式复用机制,增加了规则、变量、混入、选择器、继承、内置函数等等特性。还能生成良好格式化的 CSS 代码,易于组织和维护,需要注意的是Sass文件后缀为 .scss。
*接下来手把手带大家用Sass轻松完成Css样式的工作,缓解咱们CV输出*
1. 首先在vscode输入sass
2. 下载安装sass插件
3. 配置:不需配置
4. 在站点创建demo.scss(此时编辑器自动激活插件)
5. 在文件里输入代码会自动生成demo.css(如需生成demo.min.css需在插件内对min.css选项打对钩)
6. Sass编译css具体操作如下(双击图片):



看完后是不是觉得css编译竟然如此简单!由于浏览器不识别scss文件,所以要引入自动生成的demo.css不然会引起样式在浏览器里加载无效!
哎哟 发表于 2023-12-8 14:59:49|来自:中国 | 显示全部楼层
一、书籍、社区文章

这应该是大家学习CSS最常见的方式了(我亦如此)。有以下几个场景:
场景一:开发中遇到「文本字数超出后以省略号(…)展示」的需求,打开百度搜索:css字数过多用省略号展示,诶~搜到了!ctrl+c、ctrl+v,学废了,完工!


场景二:某天早晨逛技术社区,看到一篇关于CSS的文章,看到标题中有个CSS属性叫resize,resize属性是啥,我咋没用过?点进去阅读得津津有味~ two minutes later ~ 奥,原来还有这个属性,是这么用的呀,涨姿势了!


场景三:我决定了,我要好好学CSS,打开购物网站搜索:CSS书籍,迅速下单!等书到了,开始每天翻阅学习。当然了此时又有好几种情况了,分别是:


就只有刚拿到书的第一天翻阅了一下,往后一直落灰
看了一部分,但又懒得动手敲代码,最终感到无趣放弃了阅读
认认真真看完了书,也跟着书上的代码敲了,做了很多笔记,最终学到了很多


无论是上面哪几种方式,我觉得都是挺不错的,顺便再给大家推荐几个不错的学习资源

  • 张鑫旭大佬的博客
  • 大漠老师的W3Cplus
  • coco大佬的iCSS
毕竟站在巨人的肩膀上,才是最高效的,你们可以花1个小时学习到大佬们花1天才总结出来的知识
二、记住CSS的数据类型

CSS比较难学的另一个点,可能多半是因为CSS的属性太多了,而且每个属性的值又支持很多种写法,所以想要轻易记住每个属性的所有写法几乎是不太可能的。最近在逛博客时发现原来CSS也有自己的数据类型,这里引用一下张鑫旭大佬的CSS值类型文档大全,方便大家后续查阅
简单介绍一下CSS的数据类型就是这样的:



图中用<>括起来的表示一种CSS数据类型,介绍一下图中几个类型:


  • <number>:表示值可以是数字
  • <length>:表示元素的尺寸长度,例如3px、33em、34rem
  • <percentage>:表示基于父元素的百分比,例如33%
  • <number-percentage>:表示值既可以是 <number>,也可以是 <percentage>
  • <position>:表示元素的位置。值可以是 <length>、<percentage>、left/right/top/bottom
来看两个CSS属性:

第一个是width,文档会告诉你该属性支持的数据类型有 <length> 和 <percentage>,那么我们就知道该属性有以下几种写法:width: 1px、width: 3rem、width: 33em、width: 33%
第二个属性是background-position,文档会告诉你该属性支持的数据类型有 <position>,那么我们就知道该属性有以下几种写法:background-position: left、background-position: right、background-position: top、background-position: bottom、background-position: 30%、background-position: 3rem
从这个例子中我们可以看出,想要尽可能得记住更多的CSS属性的使用,可以从记住CSS数据类型(现在差不多有40+种数据类型)开始,这样你每次学习新的CSS属性时,思路就会有所转变,如下图

没记住CSS数据类型的我:



记住CSS数据类型的我:


不知道你有没有发现,如果文档只告诉你background-position支持 <position> 数据类型,你确定你能知道该属性的全部用法吗?你确实知道该属性支持background-position: 3rem这样的写法,因为你知道 <position> 数据类型包含了 <length> 数据类型,但你知道它还支持background-position: bottom 50px right 100px;这样的写法吗?为什么可以写四个值并且用空格隔开?这是谁告诉你的?

这就需要我们了解CSS的语法了,请认真看下一节

三、读懂CSS的语法

我之前某个样式中需要用到裁剪的效果,所以准备了解一下CSS中的clip-path属性怎么使用,于是就查询了比较权威的clip-path MDN,看着看着,我就发现了这个


我这才意识到我竟然连CSS的语法都看不懂。说实话,以前无论是初学CSS还是临时找一下某个CSS属性的用法,都是直接百度,瞬间就能找到自己想要的答案(例如菜鸟教程),而这次,我是真的傻了! 因为本身clip-path这个属性就比较复杂,支持的语法也比较多,光看MDN给你的示例代码根本无法Get到这个属性所有的用法和含义(菜鸟教程就更没法全面地教你了)
于是我就顺着网线去了解了一下CSS的语法中的一些符号的含义,帮助我更好得理解语法
因为关于CSS语法符号相关的知识在CSS属性值定义语法 MDN上都有一篇超级详细的介绍了(建议大家一定要先看看MDN这篇文章!!非常通俗易懂),所以我就不多做解释了,这里只放几个汇总表格
属性组合符号

属性组合:表示多个属性值的书写组合情况。例如在border: 1px solid #000中,1px能否和solid互换位置、#000能否省略等等,这些都是属性的组合情况


组合符优先级

"与"组合符、"或"组合符、"互斥"组合符都是为了表示属性值出现的情况,但这三者之间还有个优先级。例如bold | thin || <length>,其中“或”组合符的优先级高于“互斥”组合符,所以该写法等价于bold | [thin || <length>]


属性重复符号

属性重复:表示某个或某些属性的出现次数。例如在rgba(0, 0, 0, 1)中,数字的个数能否是3个、最后一位能否写百分比。这有些类似于正则的重复符号


解读CSS语法

以本节clip-path的语法为例,我们来简单对其中某一个属性来进行解读(只会解读部分哦,因为解读全部的话篇幅会很长很长)
先看看整体的结构


一共分为四部分,顺序是从上到下的,每两个部分之间都以where来连接,表示的是where下面的部分是对上面那个部分的补充解释
①:表示的是clip-path这个属性支持的写法为:要不只写 <clip-source> 数据类型的值,要不就最起码从 <basic-shape> 和 <geometry-box> 这两者之间选一种类型的值来写,要不就为none。
②:我们得知①中的 <basic-shape> 数据类型支持的写法为:inset()、circle()、ellipse()、polygon()、path()这5个函数
③:因为我们想了解circle()这个函数的具体使用,所以就先只看这个了。我们得知circle()函数的参数支持 <shape-radius> 和 <position> 两种数据结构,且两者都是可写可不写,但如果要写 <position> ,那前面必须加一个at
④:首先看到 <shape-radius> 支持的属性是 <length-percentage> (这个顾名思义就是<length>和<percentage>)、closest-side、farthest-side。而 <position> 数据类型的语法看起来就比较复杂了,我们单独来分析,因为真的非常非常长,我将 <position> 格式化并美化好给你展现出来,便于你们阅读(我也建议你们如果在学习某个属性的语法时遇到这么长的语法介绍,也像我一下把它格式化一下,这样方便你们阅读和理解)


如图可得,整体分为三大部分,且这三部分是互斥关系,即这三部分只能出现一个,再根据我们前面学习的CSS语法的符号,就可以知道怎么使用了,因为这里支持的写法太多了,我直接列个表格吧(其实就是排列组合)!如果还有不懂的,你们可以仔细阅读一下MDN的语法介绍或者也可以评论区留言问我,我看到会第一时间回复!

<position>类型支持的写法


嚯!累死我了,这支持的写法也太多太多了吧!

四、多动手尝试

上一节,我们在学习clip-path属性的语法以后,知道了我们想要的圆圈裁剪(circle())的语法怎么写,那么你就真的会了吗?可能你看了MDN给你举的例子,知道了circle(40%)大致实现的效果是咋样的,如下图


如我前文说的一样,MDN只给你列举了circle()这个函数最简单的写法,但我们刚刚学习了其语法,得知还有别的写法(例如circle(40% at left)),而且MDN文档也只是告诉你支持哪些语法,它也并没有明确告诉你,哪个语法的作用是怎么样的,能实现什么样的效果。
此时就需要我们自己上手尝试了
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>尝试clip-path的circle()的使用</title>
    <style>
        #zero2one {
            width: 100px;
            height: 100px;
            background-color: ;
            clip-path: circle(40%);   <!-- 等会就在这一行改来改去,反复尝试! -->
        }
    </style>
</head>
<body>
    <div id="zero2one"></div>
</body>
</html>看一下效果,嗯,跟MDN展示的是一样的


再修改一下值clip-path: circle(60%),看看效果


我似乎摸出了规律,看样子是以元素的中心为基准点,60%的意思就是从中心到边缘长度的60%为半径画一个圆,裁剪掉该圆之外的内容。这些都是MDN文档里没有讲到的,靠我亲手实践验证出来的。
接下来我们来试试其它的语法~
试试将值改成clip-path: circle(40% at top)


诶?很神奇!为什么会变成这个样子,我似乎还没找到什么规律,再把值改一下试试clip-path: circle(80% at top)


看样子圆心挪到了元素最上方的中间,然后以圆心到最下面边缘长度的80%为半径画了个圆进行了裁剪。至此我们似乎明白了circle()语法中at 后面的<position>数据类型是干什么的了,大概就是用来控制裁剪时画的圆的圆心位置
剩下的时间就交给你自己来一个一个试验所有的语法了,再举个简单的例子,比如你再试一下clip-path: circle(40% at 30px),你一定好奇这是啥意思,来看看效果


直观上看,整个圆向左移动了一些距离,在我们没设置at 30px时,圆心是在元素的中心的,而现在似乎向右偏移了,大胆猜测at 30px的意思是圆心的横坐标距离元素的最左侧30px
接下来验证一下我们的猜测,继续修改其值clip-path: circle(40% at 0)


很明显此时的圆心是在最左侧的中间部分,应该可以说是证明了我们刚才的猜测了,那么不妨再来验证一下纵坐标的?继续修改值clip-path: circle(40% at 0 0)


不错,非常顺利,at 0 0中第二个0的意思就是圆心纵坐标离最上方的距离为0的意思。那么我们此时就可以放心得得出一个结论了,对于像30px、33em这样的 <length> 数据类型的值,其对应的坐标是如图所示的


好了,本文篇幅也已经很长了,我就不继续介绍其它语法的使用了,刚才纯粹是用来举个例子,因为本文我们本来就不是在介绍circle()的使用教程,感兴趣的读者可以下去自己动手实践哦~
所以实践真的很重要很重要!! MDN文档没有给你列举每种语法对应的效果,因为每种都列出来,文档看着就很杂乱了,所以这只能靠你自己。记得张鑫旭大佬在一次直播中讲到,他所掌握的CSS的特性,也都是用大量的时间去动手试出来的,也不是看看啥文档就能理解的,所以你在大佬们的一篇文章中了解到的某个CSS属性的使用,可能是他们花费几小时甚至十几个小时研究出来的。
CSS很多特性会有兼容性问题,因为市面上有很多家浏览器厂商,它们支持的程度各不相同,而我们平常了解CSS某个属性的兼容性,是这样的
查看MDN的某个属性的浏览器兼容性


通过Can I Use来查找某个属性的浏览器兼容性


这些都是正确的,但有时候可能某些CSS属性的浏览器兼容性都无法通过这两个渠道获取到,那么该怎么办呢?手动试试每个浏览器上该属性的效果是否支持呗(鑫旭大佬说他以前也会这么干),这点我就不举例子了,大家应该能体会到
☀️ 最后

其实每个CSS大佬都不是因为某些快捷的学习路径而成功的,他们都是靠着不断地动手尝试、记录、总结各种CSS的知识,也会经常用学到的CSS知识去做一个小demo用于巩固,前几个月加了大漠老师的好友,我就经常看到他朋友圈有一些CSS新特性的demo演示代码和文章(真心佩服),coco大佬也是,也经常会发一些单纯用CSS实现的炫酷特效(据说没有他实现不了的特效哦~)

另外,如果想要更加深入,你们还可以关注一下CSS的规范,这个比较权威的就是W3C的CSS Working Group了,里面有很多CSS的规范文档


好了,再推荐几本业界公认的还算不错的书籍吧~例如《CSS权威指南》、《CSS揭秘》、《CSS世界》、《CSS新世界》等等…
这是一篇超级好的CSS学习内容,尊重原创作者「零一」|热议:CSS为什么这么难学?(侵删)
<hr/>更多HTML+CSS学习笔记如下(已完结):


  • 认识网页 / HTML标签大全 / 表格 / 列表
  • CSS入门笔记 / Css样式表 / emmet语法 / CSS的复合选择器 / 显示模式 /
  • CSS背景 / CSS三大特性 / 盒子模型 / 圆角边框、盒子阴影、文字阴影
  • 浮动知识点汇总 / PS切图 / CSS属性书写顺序(重点) / CSS练手之学成在线页面制作
  • CSS定位的4种分类 / 学成在线模块添加 / HTML+CSS之定位(position)的应用
  • 案例:淘宝轮播图 / 元素的显示与隐藏 / 土豆网鼠标经过显示遮罩 /
  • 【重点】CSS之精灵图 / 字体图标 / 用户界面样式源码 / vertical-align 属性应用 /
  • 溢出的文字省略号显示 / 常见布局技巧+案例 / CSS新增选择器 / 盒子模型和其他新特性
HTML+CSS项目《品优购》完整笔记+源码(万字版可复制):

  • HTML+CSS大项目1:品优购项目笔记+源码(万字!收藏)
  • HTML+CSS大项目2:品优购项目笔记+源码(万字!收藏)
sevil 发表于 2023-12-8 15:00:42|来自:中国 | 显示全部楼层
自 @Ahmad Shadeed 去年在博客上发表了《The Just in Case Mindset in CSS》的时候,我自己也有一种冲动,也想整理一篇这方面的文章,但一直未动笔(说实话,整理一篇这样的文章真的很费时间)。去年年底在内网看到 @克军 老师发了一篇有关于“防御式CSS开发”的文章(外网 @Ahmad Shadeed 同样发了一篇有关于"防御式CSS开发")之后,这种冲动就更大了。我想我也应该写一篇这方面的文章。希望大家能从文中获益!
现状

@克军老师在他的文章(内网文章,不便贴链接)开头有这么一段话:
当前CSS开发的现状不容乐观,扫了一圈,发现各种问题。前端开发更多关注点还是 JavaScript,技术性相对更强。但从前端技术的根本价值出发,实现高可用性的产品用户界面,是用户体验的第一道关,这就跟 CSS 开发者的专业性紧密相关了。我认为体现 CSS 开发专业性看的就是防御式 CSS 开发。
稍微老一点的前端工程师都应该知道,在还没有前端这个职位的时候有一个名叫”重构“职位(有的公司称为重构工程师,有些人自嘲是”切图仔”)。嗯,我就是从这个岗位走过来的,一直延续到今天。大多数的时候,重构工程师的主要工作内容是 还原UI界面,即编写HTML结构(具有语义化的HTML结构)、编写CSS(具有扩展性,可维护性)和切图等。
这样的事情看上去似乎很简单,但是不是真的简单,我想只有自己知道!
就是这样的一群工程师,他们面对的是用户和设计师两个群体,也是他们之间的桥梁。也正是这么一群工程师,他首先将设计师的意图还原出来,以最好的一面呈现给用户。我想不少前端同学都碰到这样的怪象:
页面出了问题,不管三七二十一先找前端。大多数发现问题都可能是先在面上展现出来,比如说,布局乱了、UI不对、没有展示出来等等!
事实上呢?这都是题外话,我们还是回到正题中来。



CSS 开发者(现在并没有这样的职位)是第一个将设计呈现给用户的。CSSer 和设计师经常打交道,可以说是“相爱相杀”。为什么会这样呢?那我们就要从“像素级还原的故事”讲起。
不知道你听到“像素级还原这个词是什么时候?”
简单地说,“像素级还原”就是设计师希望你还原的UI设计稿能达到100%的还原。或许时至今日,你在工作中还会因为一个像素的偏差和设计师争得面红耳赤,甚至会因为这一个像素不欢而散。说实话,不同的角色站在不同的角度为这一个像素而争:

  • 设计师:希望自己设计出来的作品呈现给自己用户时完全不打折扣
  • 开发人员:不是我不想像素级还原,而是很多时候也是无耐
换句话说,都是“打工人”,都不容易。暂且抛开这些争执,先来看看什么是像素级还原。
“像素级”这个概念是由设计师和客户一起创造出来的,因为他们要求自己的设计稿必须反映出设计,并且要和设计一模一样。当设计师把完成的设计稿交给前端开发人员时,他们相信前端会完全不打折扣地实现他们的设计。他们的工作是通过Web前端的实现来完成的,他们希望Web在实现过程中不要损坏他的设计稿:




而设计稿最终要在浏览器(或客户端)中向用户呈现,因此最终像素级还原(设计师的意图)转嫁到前端中:
指在HTML和CSS能100%的实现设计稿的效果,一个像素也不差。
我也经常听到这样的担忧:
许多Web前端开发人员难以完美再现一个设计,即使是有多年前端经验的Web开发者。
对于Web开发者来说,很多时候他是无辜和无耐的,即使在Web开发时严格的按照设计师提供的设计稿进行还原,精确的字号,准确的间距等,但最终实现出来的总是有一定差异的,比如下图:




看上去似乎是一样,但我们将其叠加到一起,就能看到它们之间的差异:




而这种结果很容易被认为是不完美(不是像素级)的还原。那么在现实中,像素级的还原是否就是完美的呢?
先把这个问题说清楚。从严格意义上来说,个人认为完美的像素级还原是不可能的。因为Web开发人员在编写HTML和CSS需要在各种设备终端上运行,而时至今日,这些终端设备足以让我们感到眼花缭乱。换句话说,将有很多的变量会影响我们编码出来的Web页面渲染。比如将淘宝PC端在Chrome和Safari浏览器中效果截取出来,并且并排在一起看时,这两张截图似乎非常相似,没啥问题:




但将他们合在一起看时,差异就立马显现出来了:




上图只是其中一个变量的差异(同一系统下不同的两个浏览器)。想想,现实中还有多少因素会影响到Web页面的,比如:

  • 设备类型(台式机电脑、笔记本电脑、平板电脑、手机、手表等)
  • 屏幕(或视窗)大小
  • 屏幕像素密度(比如Retina屏幕和非Retina屏)
  • 屏幕技术(比如,OLED、LCD、CTR等)
  • 用户的操作(比如用户对屏幕进行缩放、调整默认字号等)
  • 性能(比如,设备硬件、服务器负载、网络速度等)
  • 设备色彩样正(比如夜间模式,iOS的暗黑模式等)
这仅是我能想到的,还有很多我想不到的呢?也就是说,我们永远无法确保屏幕上单个像素的RGB值百分百的一致。这是一个不可能的标准。但这也不是真正的重点。
另外,也没有人要求东西在放大镜下看起来是一样的。大多数情况下,设计师希望实现的东西看起来和肉眼一样(像素眼除外),并且收紧明显的错位和松散的间距。他们希望它是像素般接近的,而不是像素般完美的。
没有像素级还原就不完美?
前面两个示例都向大家展示了,Web开发人员在还原设计师提供的设计稿时,总是会存在差异,而且影响这方面的差异的因素非常地多。而这种效果在设计师眼里很容易被认为是不完美的。那么在现实中,它是否真的不完美?
在回答这个问题前,我们把时间拉回十年前。
在2010年,设计师或客户要求前端在还原设计稿时应该是一个像素级还原,这对于Web开发人员可能是可以做到的。因为在那个年代,Web在呈现的终端设备类型毕竟不多,大多在PC台式机上展示,笔记本也不多,更不用说现在这么多的移动终端设备了。Web开发者要考虑的屏幕尺寸数量也不多。简单地说就是PC页面尺寸,比如大家熟悉的 960px 宽度,或者后来的 1024px 宽度已经足够用了。然而,随着成千上百万的设备(智能手表,智能手机,平板电脑,笔记本电脑,台式机,电视等),像素级还原将不会是件易事,甚至是不可能的事情了。
这样的话从一名Web开发者口中说出,并不代表说Web开发者没有匠心精神,没有追求。
最近看一个新词“看(外观)和感觉”。放到我们Web设计中来看,“看(外观)”指的是从UI的角度看,一个Web网站的外观如何;而感觉主要是从Web功能和交互性的角度看。
我截了两张图:




上图是截取于Youtube首页,两者不同之处只是我在第二张图上对几个地方的颜色做了些许的调整。如果不专注看的话,这些小小的变化或许就把你忽悠了。感觉上两者并没有差异。
我们再来看另一张图:




左图是原始设计图;右图是Web前端开发出来的页面效果!
你可能已经发现两者之间的差异了吧,而且差异不小,这些差异显著影响了最终结果。上图展示的只是其中一个组件,这些差异只影响了一个卡片组件。或许有人会说这些差异很微小,并不重要,至少没有影响到功能和用户的交互。或许对于一个单一的组件,这样说法是对的。试想一下,如果所有组件都有一点或一些小差异,又将会是什么样的结果。
最终你将实现一个不完美的结果!
作为Web开发者而言,要修复这些差异很容易。然而,Web开发人员还原出来的结果和设计稿存有差异却有诸多原因,而这些原因会因不同的原因而不同。比如:

  • 懂不懂设计
  • 有没有足够的项目开发时间
  • CSS方面能力怎么样
  • 是否注重细节
简而言之,不管是什么原因,我始终不认为开发者不知道如何修复还原出来的差异,让最终效果更接近原始设计稿(达到设计师诉求)。对于我来说,我认为每个Web开发者都有一个思维模式。当开发者有一定的设计知识(或能力)的时候,你会发现还原出来的Web页面结果在差异性方面就会小很多;反之,如果Web开发人员对设计方面一点都不懂,那么还原出来的结果和设计稿相比就会差得比较大。
似乎要让 UI 完美的像素级还原并不仅仅是按照设计稿完成UI页面即可,这里面还会涉及到很多和设计相关的话题,但这方面的话题并不是我们今天要讨论的。
既然说到完美还原,那作为一名专业的CSSer来说,怎么的还原才是一个完美的还原呢?那就是我们今天要讨论的正题了。
具有防御式的 CSS 代码

维基百科是这样定义防御性编程的:
防御性编程(Defensive programming)是防御式设计的一种具体体现,它是为了保证,对程序的不可预见的使用,不会造成程序功能上的损坏。它可以被看作是为了减少或消除墨菲定律效力的想法。防御式编程主要用于可能被滥用,恶作剧或无意地造成灾难性影响的程序上。
在 UI 还原的过程中,设计师提供的设计稿是一种静态的产物,虽然优秀的设计师会在他的设计稿中展示出 UI 多态下的形态(表现形式),但他无法把动态数据完美的在设计稿中展示出来。如果 Web 开发者在还原 UI 的时候,仅仅是按照一比一的还原设计稿的话,就会产生很多问题(埋雷)。因为,Web页面在客户端中展示时,他面对的情况会很复杂,比如数据是动态的,设备是多样的等。这些东西的变化,都会增加 CSS 出问题的概率,甚至导致页面呈现的时候有一些奇怪的行为。简单地说,我们应该确保还原的 UI 能在不同的条件下都能工作。
要达到这样的基本要求,我们就应该具备“万一”(以防万一)的思维,即提前考虑一些特殊情况,比如一些边缘条件下可能会出现的情况。就我个人而言,我在还原 UI 的时候时常会问:

  • 标题超长应该怎么处理?直接裁切,还是末尾添加三个点
  • 数据没吐出呢?不处理还是将空白容器隐藏
  • 容器变小呢?它应该怎么展示?
  • 等等
除了这些展示的边缘情况,在使用CSS的时候也会有一些边缘情况(CSS中有些属性的组合就会触发)。
也就是说,我们在还原 UI 的时候,应该时常抱有“万一”的思维方式。遵循这种心态,至少可以减少你可能遇到的问题。一个坏的和一个好的区别,往往就是一行代码的问题。往往这简单的一行代码,会让你的 CSS 变得更健壮,更具防御性。
接下来是有关于具有防御性 CSS 的代码片段和场景的集合,在编写 CSS 时加上这些代码可以让你还原出来的 UI 更具保护性。
与 Flexbox 布局相关的防御式 CSS

我的第一本小册:现代 Web 布局CSS Flexbox 是目前最为主流的布局技术之一(在2022年,CSS Grid 也有可能成为主流布局技术之一)。很多 Web 开发者也首选 Flexbox 用作布局,但 Flexbox 用于布局时有一些细节需要掌握,掌握这些细节可以让你的 CSS 代码更健壮。
flex 还是 inline-flex

众所周知,要使用 Flexbox 首要条件就是在一个容器元素上显式声明 display 的值为 flex 或 inline-flex,并该容器变成 Flexbox 容器(即,FFC)。他们的表现形式有点类似于block 和 inline-block。
当 Flexbox 容器的父元素未被 display 的值重新定义上下文格式之下:

  • 设置为 flex 容器,它的宽度和父容器等宽,即 width 为 100%,也称块Flexbox容器
  • 设置为 inline-flex容器,它的宽度是由其子元素(后代元素)的内容来决定,相当于 width 为 auto,也称内联Flexbox容器




UI 设计也有与这个类似的场景:




如上面的设计稿中,按钮有的是和父容器宽度等宽的,有的是依赖内容(即内容节点的 max-content)决定的。为此,我们在使用 display 来显式声明 Flexbox 容器的时候,并不能不加思考的将所有 Flexbox 容器定义为 flex,更好的方式,根据UI的形态来定义 Flexbox 容器的上下文格式化方式:
.block-container {
    display: flex;
}

.inline-container {
    display: inline-flex;
}这也将和未来的 display 模块相匹配,CSS Display Module Level 3 定义了 display 可以接受两个值:
.block-container {
    display: flex;

    /*相当于*/
    display: block flex;
}

.inline-container {
    display: inline-flex;
    /* 相当于 */
    display: inline flex;
}这对于 CSS 来说是非常有意义的,也是很重要的。这样一来,代码就能正确地解释容器框(盒子)与其他框(盒子)的交互,即它们是块盒还是内联盒,以及子代的行为。对于理解display 是什么和做什么,就非常清晰了。这对于与UI 视觉元素的形态也有较好的匹配。
应该根据UI的视觉形态来正确的声明Flexbox容器,不能将所有Flexbox容器都声明为块盒!
需要特别注意的是,Flexbox 容器为块盒和内联盒也会对Flex项目的伸缩性,特别是flex-basis有影响。具体有何影响,这里不阐述,感兴趣的同学可以阅读:

  • Flexbox布局中不为人知的细节
  • 聊聊Flexbox布局中的flex的演算法
  • 你真的了解CSS的 flex-basis 吗?
Flexbox中的换行

默认情况之下,位于Flexbox容器的所有子元素都会排在同一行(或同一列),但Flexbox 容器的可用空间是未知的。当Flexbox没有足够多的空间来容纳其所有 Flex 项目(其子元素)时,Flex 项目会溢出 Flexbox 容器,将会打破布局或出现滚动条:




这种行为是预知的行为,并不能说是渲染问题,只不过他和我们预期或者说与设计师期望的效果不同。要解决这个问题,很简单,在Flexbox容器上显式设置 flex-wrap的值为 wrap(其默认值为nowrap):
.flex-container {
    display: flex;
    flex-wrap: wrap;
}我们应该尽量在Flexbox容器上使用 flex-wrap 来避免意外布局的行为。但另外有一点需要注意的是,flex-wrap: wrap 只有在Flex项目不能自动收缩扩展状态下有效,换句话说,如果在Flex项目中显式设置了 flex: 1时,即使你在Flexbox容器上显式设置flex-wrap为wrap也不能让Flex项目换行:
.flex-container {
    display: flex;
    flex-wrap: wrap;
}

.flex-item {
    flex: 1;
}

https://www.zhihu.com/video/1467255094529527808
虽然在Flexbox容器中显式设置flex-wrap为wrap时可以预防布局溢出,但并不代表着在所有Flexbox容器上都设置,在具体使用过程中要有一个心理预判。比如下面这个是使用了flex-wrap: wrap的效果:


https://www.zhihu.com/video/1467255223080751104
事实上,更好的效果应该是:


https://www.zhihu.com/video/1467255379561848832
flex-wrap: wrap 碰到了 flex:1 并不会让 Flex 项目换行(或列),另外使用flex-wrap: wrap 要有一个心理预判,不然也有可能会让UI视觉上不美,但不会撑破布局!  选择总是痛苦的(^_^)
flex:1 不一定能均分列

在现代 Web 布局中,特别是 Flexbox 布局,均分列(换句话说,均分容器,即等宽)都认为在 Flex 项目中显式设置 flex: 1 即可。
.item {
    flex: 1;
}



事实上呢?并不一定就是这样。为什么这么说呢?
这其中的道道非常的深,咱们就说flex:1吧,它相当于:
flex-grow: 1;
flex-shrink: 1;
flex-basis: 0%;如果未显式设置flex(它是flex-grow、flex-shrink 和 flex-basis的简写属性)时,其初始值是:
flex-grow: 0;
flex-shrink: 1;
flex-basis: auto;当flex-basis 为 auto 时,Flex项目的宽度是max-content。也就是说,flex:1时,flex-basis变成了0%,这将覆盖Flex项目的内在尺寸(max-content),Flex项目的基本尺寸现在是 0,但由于 flex-grow的存在,它们都可以增长以填补空的空间(Flexbox的剩余空间)。而实际上,在这种情况下,flex-shrink不再做任何事情,因为所有的Flex项目现在的宽度都是0,并且正在增长以填补可用空间。只不过,Flexbox容器有可有没有剩余空间,甚至是有不足空间。这个时候,flex:1也就不能均分Flexbox容器了。比如:


https://www.zhihu.com/video/1467255612057235456
正如上面录屏所示,最的一个Flex项目宽度要更大,它的max-content都比其他Flex项目大(它有四个汉字宽):




事实上就是触发了flex的边缘情况,如果你阅读W3C规范足够仔细的话,不难发现。因为W3C规范中已有明确的描述:
By default, flex items won’t shrink below their minimum content size (the length of the longest word or fixed-size element). To change this, set the min-width or min-height property.
大致意思是说:“默认情况下,弹性Flex项目(设置为flex:1的Flex项目)在收缩的时候,其宽度不会小于其最小内容尺寸(即 min-width,也就是max-content或固定尺寸元素的长度)。要改变这一点,需要显式设置min-width或min-height的值”。
这一环扣一环,它会涉及min-width或min-height的值。默认情况下,min-width(或min-height)的值为 auto,它会被计算为 0。当一个元素为Flex项目时,min-width或min-height的值又不会被计算为0,它的值为max-content。这就是会触发边缘的计算。
为此,要真正达到均分列,只显式设置flex:1还不行,还需要在Flex项目上显式设置min-width的值为0:
.item {
    flex: 1;
    min-width: 0;
}



这里涉及 CSS 多个方面的知识点,比如说,CSS中的内在尺寸和外在尺寸、CSS的尺寸、ref="https://www.w3cplus.com/css/auto-value-in-css.html">CSS的auto计算 等。
不过,在使用flex:1的时候,需要额外的注意,因为flex:1时,这个1会被视为flex-grow的值为1。更好的使用姿势是,显式给flex设置三个值,比如flex: 1 1 0% 或 flex: 1 1 100%。
.item {
    flex: 1 1 0%;
    min-width: 0;
}如果你有接触过 CSS Grid 布局的话,可能已经知道 CSS Grid 中独有的 fr 单位了。也能将网格容器均分,比如下面这个,将网格容器均分成五等份:
.grid {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
}但要真正达到均分,也存在像Flex项目的边缘情况。也就是说,要在Grid项目上显式设置min-width为0:
.item {
    min-width: 0;
}    See the Pen <a href="https://codepen.io/airen/pen/QWqJpYJ">   Untitled</a> by Airen (<a href="https://codepen.io/airen">@airen</a>)   on <a href="https://codepen.io">CodePen</a>.  
有关于这方面更详细的介绍,还可以移步阅读 @KevinJPowell 的《Equal Columns With Flexbox: It’s More Complicated Than You Might Think》。
如果你对 CSS Grid 布局感兴趣的话,可以阅读《2022年不能再错过 CSS 网格布局了》中所列的网格系列教程。
Flexbox 中的最小值 min-size

在使用 Flexbox 布局的时候,很有可能其中某个 Flex 项目的文本内容很长,最终导致内容溢出。




你可能想到了在文本节点容器(它也是一个Flex项目)上设置:
/* ① 长单词断行,常用西文 */
.long-word {
    overflow-wrap: break-word;
}

/* ② 文本截取,末尾添加 ... */
.text-overflow {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ③ 多行文本截取,末尾添加... */

.line-clamp {
    --line-clamp: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: var(--line-clamp);
    -webkit-box-orient: vertical;
}诸如此类的操作,我们只是希望防止长内容(或长单词破坏掉页面布局)。如下图所示:




设计师希望卡片标题在同一行,不能因为内容过长而让设计效果失去一致性。为此,我们可以使用上面代码 ② 来截取文本,并且在文本末尾出现三个点的省略号:
.text-overflow {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

https://www.zhihu.com/video/1467256144901545984
或者输入了恶意的内容,比如带下划线的URL或没有空格的数字,字母等:




在这样的布局中,即使我们的标题元素是一个Flex项目,并且已显式设置了flex:
.card__title {
    flex: 1 1 0%;
}你会发现,卡片右侧的Icon还是被长内容挤出容器(溢出):




你可能会想到,使用上面代码 ① 让长词断行显示:
.long-word {
    overflow-wrap: break-word;
}你会发现,并未起效果:




即使你加了 hyphens 为 auto 也未生效。
你可能会认为他是一个Flexbox中的Bug,事实上,W3C规范中也有相应的描述:
On a flex item whose overflow is visible in the main axis, when specified on the flex item’s main-axis min-size property, specifies an automatic minimum size.
大致意思上说:“ 主轴上Flex项目的overflow属性是visible时,主轴上Flex项目的最小尺寸(min-size)将会指定一个自动的(automatic)最小尺寸 ”。前面我们也提到过:
默认情况下,弹性Flex项目(设置为flex:1的Flex项目)在收缩的时候,其宽度不会小于其最小内容尺寸(即 min-width,也就是max-content或固定尺寸元素的长度)。要改变这一点,需要显式设置min-width或min-height的值。
因此,我们要解决这个问题,需要在使用overflow-wrap为break-word的地方重置min-width值,并且强制它变成0:
.long-word {
    overflow-wrap: break-word;
    min-width: 0;
}另外,要提出的是,Flex项目的 overflow 的值为 visible 以外的值时会导致 min-width 的值为0,这就是为什么在方法 ② 中做文本截取的时候,怎么没有 min-width: 0。
Web 中的长文本断行(Line Breaking)也是一个复杂的体系,涉及到的 CSS 属性也较多,比如 white-space、overflow-wrap、hyphens 、word-break 和 line-break 等。W3C 的 CSS Text Module Level 3 对这些属性有详细的定义和描述。另外推荐大家阅读 @frivoal 曾经分享过的一个这方面的主题《line breaking》。
继续回到这个话题上来,Flex项目的长文本(max-content)或显式设置 white-space: nowrap 在视觉上除了会打破布局之外,也会对相邻的 Flex项目进行挤压,即使这些Flex项目显式设置了尺寸。比如上面的示例:
.card__media {
    width: 4em;
    aspect-ratio: 1;
}

.card__action {
    width: 3em;
    aspect-ratio: 1;
}你会发现,后面三张卡片的左右两侧的Flex项目尺寸被挤压,甚至还会造成视觉上的变形:




造成这个现象是由于标题(它也是一个Flex项目)内容过长(max-content),Flexbox容器无剩余空间来放置它,这个时候将会对同一轴上的其他 Flex 项目进行挤压。大家知道,Flex项目的flex的默认值为:
flex-grow: 0;
flex-shrink: 1;
flex-basis: auto;flex-shrink的值为1,表示Flex项目可以被收缩。解决这种现象,我们有两种方法,最简单的方法是在标题这个 Flex项目元素上显式设置 min-width 的值为 0:
.card__title {
    min-width: 0;
}

https://www.zhihu.com/video/1467256791716864000
另一种解法是在显式设置了 width 或 height 的 Flex项目上重置 flex-shrink 的值为 0,告诉浏览器,即使Flexbox容器没有足够的剩余空间,你也不能来挤压我的空间:
.card__media,
.card__action {
    flex-shrink: 0;
}



相对而言,或者一劳永逸的方案是 在显式设置了 flex: 1 的 Flex 项目是显式设置 min-width 的值为 0
    See the Pen <a href="https://codepen.io/airen/pen/ZEXmNzd">   Untitled</a> by Airen (<a href="https://codepen.io/airen">@airen</a>)   on <a href="https://codepen.io">CodePen</a>.  
Flexbox 中的滚动失效

记得在 2019 年做双十一盖楼的互动项目时,就踩过这个坑。项目中需要完成下图这样的一个需求:




图中红色虚线框中的内容是带有滚动的。 因为容器的高度是固定的(100vh),内容很有可能会超过容器的高度。
还原这个组件UI和交互的时候,同样采首子 Flexbox 来布局,我在滚动容器上显式设置了 flex为1:




在滚动容器上显式设置了 overflow-y 为 scroll 也未生效(当时发现这个Bug的是在iOS系统中)。后来才发现,这只是触发了Flex项目的边缘情况。当然,实现这个需求,他对HTML结构是有一定的要求的:




对应上图结构图的 HTML 代码如下:
<div class="main-container"> <!-- flex-direction: column -->
    <div class="fixed-container">Fixed Container</div> <!-- height: 100px; -->
    <div class="content-wrapper"> <!--  min-height: 0; -->
        <div class="overflow-container">
            <div class="overflow-content">
            Overflow Content
            </div>
        </div>
    </div>
</div>关键的 CSS 代码:
.main-container {
    display: flex;
    flex-direction: column;
}

.fixed-container {
    height: 100px;
}

.content-wrapper {
    display: flex;
    flex: 1;
    min-height: 0; /* 这个很重要*/
}

.overflow-container {
    flex: 1;
    overflow-y: auto;
}



具体示例效果如下:
    See the Pen <a href="https://codepen.io/airen/pen/KKKGeRO">   Overflow And Flex</a> by Airen (<a href="https://codepen.io/airen">@airen</a>)   on <a href="https://codepen.io">CodePen</a>.  
关键部分是 设置了 flex: 1 的 Flex 项目需要显式设置 min-height 的值为 0 ,即滚动容器的父元素。这其实是 Flexbox 布局的一个边缘情况。
注意,在设置了 flex:1 的 Flex 项目中应该尽可能的重置它的 min-size 值(当主轴在水平方向时(flex-direction: row),设置min-width,当主轴在垂直方向时(flex-direction: column),设置min-height),一般情况下都将其重置为 0。
另一个至Flexbox布局中滚动失效是 flex-end。这个案例我自己没有碰到过,也未曾测试过这种场景。我也是前段时间看到 @张旭鑫 老师的 《flex-end为什么overflow无法滚动及解决方法》文章才知道。简单地来看一下这个案例。
<!-- HTML -->
<Container>
    <Card />
    <Card />
    <Card />
    <Card />
</Container>

/* CSS */
.container {
    display: flex;
}

.container--row {
    overflow-x: scroll;
}

.container--column {
    flex-direction: column;
    overflow-y: scroll;
}这是正常情况之下,没有使用任何对齐方式相关的属性,比如 justify-content。整个效果如下:


https://www.zhihu.com/video/1467257500017364992
为了让滚动体验更接近原生客户端,示例对滚动做了一些优化,比如在滚动容器中使用了 overscroll-behavior 和 滚动捕捉相关的特性。如果你对这方面感兴趣,可以阅读《改变用户体验的滚动新特性》和《CSS滚动捕捉:Part1和Part2》。
如果我们在滚动容器 .container 显式设置justify-content的值为flex-end:
.container {
    justify-content: flex-end;
}这个时候滚动失效:


https://www.zhihu.com/video/1467257776082350080
你可能会好奇,为什么会这样呢?
正如 @张旭鑫 老师的教程中所述。在 Web 中,我们的书写习惯和阅读模式是从左到右(LTR),从上到下。也就是说,一般情况之下(先不考虑其他的书写模式),水平方向内容向右溢出,垂直方向的内容向底部溢出,即 滚动条在设计的时候,就约定了,只有容器下方(或右侧)内容有多余,才需要滚动
在滚动容器(它刚好是Flexbox容器)显式设置了justify-content: flex-end(主轴方向从flex-start换成了flex-end)。这就导致,如果有内容是在上方或左侧超过容器的尺寸限制,滚动条是不会有任何变化的。也因此,滚动容器里面内容溢出容器的方向不是在容器的下方或者右侧,而是在容器的顶部和左侧,自然就无法触发滚动条的出现。简单地说,flex-end 会让内容反向溢出,也就没有滚动条,自然也就无法滚动:




在这种情况之下,想让滚动容器能正常滚动起来,其实很简单,借助 margin: auto即可。在Flex项目是显式设置margin的值为auto有着独特的效果,如下图所示:




解决方法很简单,对齐方式开始默认的对齐,即 justify-content不设置为flex-end,取默认值,然后使用 margin: auto 实现类似justify-content: flex-end对齐效果。在我们的示例中,只需要给第一个Flex项目设置margin-left(水平方向)或 margin-top(垂直方向)的值为auto:
.container--row .card:first-child {
    margin-left: auto;
}

.container--column .card:first-child {
    margin-top: auto;
}    See the Pen <a href="https://codepen.io/airen/pen/VwMqepY">   Untitled</a> by Airen (<a href="https://codepen.io/airen">@airen</a>)   on <a href="https://codepen.io">CodePen</a>.  
点击示例中的按钮动态增加卡片:


https://www.zhihu.com/video/1467258073786585088
与 Grid 布局相关的防御式 CSS

我的第一本小册:现代 Web 布局CSS Grid 是目前 Web 中唯一一种二维布局技术。它已经得到了现代主流浏览器的支持,在 2021 年我花将近半年时间,用了二十多篇篇幅来阐述 CSS Grid 布局的基础知识和相关应用,再次感受到她的强大之处。
说实话,她很强大,但也很复杂。当然,她也有很多不怎么为人所知的一面。早在 2019 年 @Manuel Matuzović 用了一个系列(目前完成了Part1 和 Part2,还有一些未完成 )阐述 CSS Grid 布局中一些不为人知的事情。




但这些并不是我们今天要探讨的。我们主要来聊聊使用 CSS Grid 时应该怎么编写 CSS 才更具防御性。
grid 还是 inline-grid

grid 和 inline-grid 也是 display 的属性,他们和有面所介绍的 flex 和 inline-flex 有点相似。grid 和 inline-grid 都是用来显式声明网格容器的,即创建网格格式化上下文(GFC),这两者不同之处是:

  • grid 创建的网格容器是块盒,网格容器的宽度和父容器宽度相等(前提是父容器未做别的格式化上下文处理)
  • inline-grid 创建的网格容器是内联盒子,网格容器的宽度将由其具有最大宽度(max-content)的子元素(网格项目)来决定
即:
.block-container {
    display: grid;

    /*相当于*/
    display: block grid;
}

.inline-container {
    display: inline-grid;
    /* 相当于 */
    display: inline grid;
}但它们和Flexbox 中的 flex以及 inline-flex 有着明显的区别:

  • Flexbox 布局中,不管是 flex 还是 inline-flex,默认情况下,都会让所有 Flex 项目排在主轴上(一行或一列)
  • Grid 布局中,不管是 grid 还是 inline-grid,默认情况下,都不会改变 Grid 项目的排列方式,将按照 HTML 结构中的源顺序排列,除非你在声明网格容器的时候,显式使用 grid-template-*(比如,grid-template-columns、grid-template-rows 或 grid-template-areas)改变其排列方式




在 CSS Grid 中,如果不使用 grid-template-* 显式定义网格轨道,那么不管 display 的值是 grid 还是 inline-grid ,他都是以 HTML 的源顺序渲染
注意网格轨道的固定值

CSS Grid 网格轨道的定我方式有很多种,除了显式使用 grid-template-* 属性定义网格轨道之外,还可以使用 grid-*(比如grid-row、grid-column或grid-area)根据网格线编号(或名称)、网格区域名称定义。除此之外,轨道尺寸设置方式也非常灵活,比如:

  • 带有不同单位的长度值,如 px、em、rem、%、vw等,还有网格布局中独有的单位 fr
  • 关键词,比如 none、auto、min-content和max-content
  • CSS 函数,比如 fit-content()、minmax()、repeat()、min()、max()和clamp()等
因此,为了让你的 CSS Grid 更为灵活(适应性更强),在定义网格轨道的时候,应该尽可能的使用内在尺寸,即 使用关键词和CSS的函数。用一个常见的布局案例(比如 Medium )来阐述。




CSS 中实现上图布局效果的方案有很多种,最简单地:
.container {
    padding-left: 20px;
    padding-right: 20px;
    width: 100%;
    max-width: 60ch;
    margin-left: auto;
    margin-right: auto;
}

.container--full {
    width: 100%;
}这种方案简单,但对结构有一定的要求。如果我们使用 CSS Grid ,会变得更为灵活,比如像下面这样:
:root {
    --gap: 20px;
    --minWidth: 60ch;
}

.container {
    display: grid;
    gap: 20px;
    grid-template-columns: 1fr min(var(--minWidth), calc(100% - var(--gap) * 2)) 1fr;
}

.container > * {
    grid-column: 2;
}

.container > .full {
    grid-column: 1 / -1;
}    See the Pen <a href="https://codepen.io/airen/pen/wvrOgMQ">   Untitled</a> by Airen (<a href="https://codepen.io/airen">@airen</a>)   on <a href="https://codepen.io">CodePen</a>.  
尝试着调整示例中的间距和居中内容的宽度,或者调整浏览器视窗的宽度,你将看到像下面这样的效果:


https://www.zhihu.com/video/1467258540537057280
如果你把示例中的 min() 函数换成 clamp() 函数的话,还可以在一个区间中选值,比如内容容器的宽度在 23ch ~ 65ch 之间:




.container {
    --limit-max-container-width: 65ch;
    --limit-min-container-width: 23ch;
    --gutter: 1rem;
    display: grid;
    grid-template-columns: 1fr clamp(
        var(--limit-min-container-width),
        100% - var(--gutter) * 2,
        var(--limit-max-container-width) ) 1fr;
    gap: var(--gutter);
}你也可以将 min() 和 minmax() 结合起来使用:
.container {
    --limit-max-container-width: 65ch;
    --limit-min-container-width: 23ch;
    --gutter: 1rem;
    display: grid;
    grid-template-columns: 1fr minmax(min(var(--limit-min-container-width), 100% - var(--gutter) * 2), var(--limit-max-container-width)) 1fr;
    gap: var(--gutter);
}    See the Pen <a href="https://codepen.io/airen/pen/yLzwbBj">   Untitled</a> by Airen (<a href="https://codepen.io/airen">@airen</a>)   on <a href="https://codepen.io">CodePen</a>.  
再来看一个 PC 端常见的两列布局,即 则边栏固定宽度,主内容区域自适应




如果使用 CSS Grid 来布局的话,通常会像下面这样:
.container {
    display: grid;
    gap: 1rem;
    grid-template-columns: 250px 1fr;
    grid-template-areas: "sidebar "main";
}但是,如果浏览器的视窗尺寸较小,有可能因为缺少足够的空间导致样式出现问题。为了避免这种情况发生,通常会在 CSS Grid 布局中使用媒体查询:
.container {
    display: grid;
    gap: 1rem;
    grid-template-areas:
        "sidebar"
        "main";
}

@media (min-width: 760px) {
    .container {
        grid-template-columns: 250px 1fr;
        grid-template-areas: "sidebar main";
    }
}    See the Pen <a href="https://codepen.io/airen/pen/XWeGRgQ">   Untitled</a> by Airen (<a href="https://codepen.io/airen">@airen</a>)   on <a href="https://codepen.io">CodePen</a>.  
改变浏览器视窗的大小,效果如下:


上面这个示例我们还可以进一步的让侧边栏宽度更为灵活。在定义侧边栏尺寸的时候,可以使用 fit-content() 函数,把 grid-template-columns: 250px 1fr; 换成:
.container {
    grid-template-columns: fit-content(250px) 1fr;
}代码中的 fit-content(250px) 相当于 min(max-content-size, max(min-content, 250px))。其中:

  • ①:max()函数的参数是 min-content(第一个参数) 和 250px(第二个参数),而 max() 函数是会返回两个参数中较大的一个值
  • ②:max()返回值会放到 min() 函数中,即 max() 返回的值变成了 min() 函数的第二个值,它会和最大的内容尺寸(max-content-size)进行比较,这是由于网格限制而产生的实际宽度,但最大为 max-content。min() 函数和 max() 类似,只不过它返回的是更小的值
因此,fit-content(250px) 用下面这个公式来描述更适合:
fit-content(200px) = min(min(max-content, available-size), max(min-content, 200px))公式中的 available-size 指的是网格中的可用宽度。
除此之外,规范中还提供了另一种公式来描述 fit-content():
fit-content(<length-percentage>) = max(minimum, min(limit, max-content))其中:

  • ①:minimum 代表自动最小值(通常但不总是等于min-content最小值)
  • ②:limit 是作为参数传给 fit-content() 参数,即 <length-percentage>,比如示例中的 250px
  • ③:min() 返回 limit 和 max-content 中更小的值,比如这个示例,min() 返回的是 250px 和 max-content 更小的一个
  • ④:max() 返回是 minimum 和 min(limit, max-content) 两个值中更大的一个
如果上面的描述不易于理解的话,我们可以这样来理解。比如示例中的 fit-content(250px),表示该列网格轨道宽度不会超过 250px,并且可以在内容很短的情况下缩小到 250px 以下。
    See the Pen <a href="https://codepen.io/airen/pen/KKXEmOE">   Untitled</a> by Airen (<a href="https://codepen.io/airen">@airen</a>)   on <a href="https://codepen.io">CodePen</a>.  
使用fit-content()前面的效果对比:




简单地说,在 CSS Grid 布局中,定义网格轨道尺寸时,尽可能的不要使用外在尺寸(比如设置固定的长度值<length>),应该尽可能的使用内在尺寸(比如 min-content)。即使不使用内在尺寸,也应尽可能的结合CSS的函数(比如min()、max()和clamp()等),来提高网格轨道尺寸的灵活性和自适应性
如果你想更深入的了解示例中用到的 CSS 特性,可以移步阅读:

  • CSS 的值和单位
  • 元素尺寸的设置
  • 使用内在尺寸定义网格轨道尺寸
  • 网格轨道尺寸的设置
  • CSS Grid: minmax()
  • CSS Grid: min-content和max-content
  • href="https://www.w3cplus.com/css/min-max-clamp-function.html">聊聊min(),max()和clamp()函数
  • 网格中的可用函数
  • 2022年不能再错过 CSS 网格布局了
网格中的断行

首先声明,这里所说的断行不是指文本的或词的断行。
前面提到过,在 Flexbox 布局中,如果要让布局断行,需要在 Flexbox 容器上显式设置 flex-wrap 为 wrap。
.cards {
    --gap: 18px;
    --columns: 3;
    --min-width: 220px;

    display: flex;
    flex-wrap: wrap;
    gap: var(--gap);
}

.card {
    flex: 1 1 calc((100% - var(--gap) * (var(--columns) - 1)) / var(--columns));
    min-width: var(--min-width);
}    See the Pen <a href="https://codepen.io/airen/pen/XWeGaNV">   Untitled</a> by Airen (<a href="https://codepen.io/airen">@airen</a>)   on <a href="https://codepen.io">CodePen</a>.  
通过改变间距、列数和卡片最小宽度或者容器尺寸,都能自动调整 UI 的布局效果:


不过该方案存在一定的缺陷,当卡片数量不是列数的倍数时,最后一排卡片宽度会像下面这个视频效果一样变化


虽然解决方案有很多种(可以阅读 @张旭鑫 老师的 《让CSS flex布局最后一行列表左对齐的N种方法》一文),只不过与 CSS Grid 相比,灵活度要欠缺很多。
在 CSS Grid 布局中,对于上面示例布局场景(称之为断行)是具有天然性的。只需要在定义网格轨道的时候,在 repeat() 函数中使用 auto-fill 或 auto-fit 关键词。

  • auto-fill :如果网格容器在相关轴上具有确定的大小或最大大小,则重复次数是最大可能的正整数,不会导致网格溢出其网格容器。如果定义了,将每个轨道视为其最大轨道尺寸大小函数 ( grid-template-rows 或 grid-template-columns 用于定义的每个独立值。 否则,作为最小轨道尺寸函数,将网格间隙加入计算. 如果重复次数过多,那么重复值是 1 。否则,如果网格容器在相关轴上具有确定的最小尺寸,重复次数是满足该最低要求的可能的最小正整数。 否则,指定的轨道列表仅重复一次。
  • auto-fit : 行为与 auto-fill 相同,除了放置网格项目后,所有空的重复轨道都将折叠。空轨道是指没有流入网格或跨越网格的网格项目。(如果所有轨道都为空,则可能导致所有轨道被折叠。)折叠的轨道被视为具有单个固定轨道大小函数为 0px,两侧的槽都折叠了。为了找到自动重复的轨道数,用户代理将轨道大小限制为用户代理指定的值(例如 1px),以避免被零除。
简单地说,auto-fit 将扩展网格项目以填补可用空间,而auto-fill不会扩展网格项目。相反,auto-fill将保留可用的空间,而不改变网格项目的宽度。比如下图,可以看出 auto-fit 和 auto-fill 的差异:




再来看一张 auto-fit 和 auto-fill 的对比图,即,在网格容器中有多个和仅有一个网格项目时,使用auto-fill 与 auto-fit 的差异:




我们可以使用这个特性来改造前面的 Flexbox 换行的示例:
.cards {
    --gap: 18px;
    --min-width: 220px;

    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(var(--min-width), 1fr));
    gap: var(--gap);
}    See the Pen <a href="https://codepen.io/airen/pen/mdBoBaM">   Untitled</a> by Airen (<a href="https://codepen.io/airen">@airen</a>)   on <a href="https://codepen.io">CodePen</a>.  
调整示例中的间距或卡片最小宽度或者改变网格容器的尺寸,你看到的效果如下:


或许你已经发现了,上面这个示例还存在一个小缺陷,当网格容器的宽度小于卡片的最小宽度时,网格容器会出现滚动条,或者说卡片被截取。为了让布局的灵活性,可防御性更强,我们可以在 minmax() 函数中使用min(),并且给 min()函数传入 var(--min-width) 和 100% 两个值。我们知道,100% 是相对于其父容器宽度来计算的,而且 min() 函数会取更小的那个值,也就是说,当网格容器小于一定值的值,min() 函数会到 100% 这个值,即与其父容器宽度相等:
.cards {
    --gap: 18px;
    --min-width: 220px;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(var(--min-width), 100%), 1fr));

    gap: var(--gap);
}    See the Pen <a href="https://codepen.io/airen/pen/qBPvVZR">   Untitled</a> by Airen (<a href="https://codepen.io/airen">@airen</a>)   on <a href="https://codepen.io">CodePen</a>.  
拖动示例中网格容器右下角滑块,当网格容器宽度小于 --min-width 也会自动调整 UI 的布局,卡片不会被容器截取或容器出现滚动条:


当然,容器小到无法容纳内容时,UI 看上去还是不美的。如果你对容器宽度有一个预判,比如说,网格容器小到一定的时候就不能再小了,可以在网格容器上使用 min-width 来设置一个值,比如:
.section {
    min-width: 180px;
}你可能已经感受到其强大和灵活性了,在这样的卡片布局场景,都可以不依赖任何 CSS 媒体查询,就可以实现响应式的布局效果。
有关于 CSS Grid 中 auto-fit 和 auto-fill 更详细的介绍,可以移步阅读《CSS Grid: 网格中的函数》一文。
网格中的最小宽度

与 Flexbox 布局类似,CSS Grid 中的子元素(网格项目)内容的默认最小值是 auto。也就是说,如果元素的尺寸超过网格项目,同样会发生内容溢出。




正如上图所示,内容区域(Main)包含了一个具有轮播功有的走马灯(Carousel)。HTML和CSS代码如下所示:
<!-- HTML -->
<div class="wrapper">
    <main>
        <section class="carousel"></section>
    </main>
    <aside></aside>
</div>

/* CSS */
@media (min-width: 1020px) {
    .wrapper {
        display: grid;
        grid-template-columns: 1fr 248px;
        grid-gap: 40px;
    }
}

.carousel {
    display: flex;
    overflow-x: auto;
}由于 Carousel 是一个不会发生断行的 Flexbox 容器,它的宽度超过了主内容区域(Main),而网格项目了会遵循这一点。因此,出现了水平滚动条。针对这个问题,有三种不同的解决方案:

  • 使用 minmax() 函数
  • 显式在网格项目上设置 min-width
  • 显式在网格项目上设置 overflow: hidden
做为一种防御性 CSS 机制,我选择了第一种,使用 minmax() 函数:
@media (min-width: 1020px) {
    .wrapper {
        display: grid;
        grid-template-columns: minmax(0, 1fr) 248px;
        grid-gap: 40px;
    }
}



CSS Grid 布局有一个独有的计算长度的单位,那就是 fr 单位,可以将其与%结合起来理解。简单地说:
1个fr(即1fr)就是100%网格容器可用空间;2个fr(即2fr)是各50%网格容器可用空间,即1fr是50%网格容器可用空间。以此类似,要是你有25个fr(即25fr),那么每个fr(1fr)就是1/25或4%
我们可以使用饼图来描述网格的 fr 单位:




注意,一个饼图(圆)就相当于网格容器的可用空间,分割的份数就相当于设置了弹性系数的网格轨道
在 CSS Grid 中,我们可以使用 fr 来做均分效果,类似于 Flexbox 中的 flex:1。只不过,在使用 fr 做均分列的时,需要在相应的网格项目中显式设置 min-width 的值为 0。
网格布局中的 position: sticky

CSS Positioned Layout Module Level 3 中的 position 新增了 sticky 属性,从表现形式来看,它是 relative 和 fixed 的结合物,即默认情况表现形式像relative,但达到一定条件,它的表现形式又类似于 fixed。不过在 CSS Grid 布局中给网格项目(Grid Item)设置position 为 sticky时,他的渲染行为和你理解的或者说非网格项目的渲染行为有所差异。
有接触过 CSS Grid 布局的同学都应该知道,默认情况下,网格项目是 Stretch(拉伸的)。正如前面介绍 fit-content() 示例所示,<aside> 和 <main> 两个网格项目的高度和网格容器的高度等同(即使没有足够多的内容来撑高):




而我们所理解的 position,在非static值的下,他都会脱离文档流,最直观的效果就是在没显式设置尺寸情况之下,它的大小由内容的大小来决定。换句话说,如果在网格项目上显式设置position为sticky,想让其表现形式如我们所期望的那样,就需要显式地在该网格项目上设置 align-self的值为 start:
aside {
    align-self: start;
    position: sticky;
    top: 0;
}    See the Pen <a href="https://codepen.io/airen/pen/poWYLgX">   Untitled</a> by Airen (<a href="https://codepen.io/airen">@airen</a>)   on <a href="https://codepen.io">CodePen</a>.  
具体效果如下所示:


间距

一直以来,给元素与元素之间设置间距,都习惯性的使用 margin 或 padding,但在很多时候,margin 和 padding 并不是很灵活,特别是在 Flexbox 和 Grid 布局中。在 Flexbox 和 Grid 布局中,使用 gap 来设置项目之间的间距会比 margin 要灵活:




正如上图所示,如果Flex项目或Grid项目与容器之间是零间距,便项目与项目之间有一定的间距,那么使用 gap 是最佳的。
.flex--container {
    display: flex;
    gap: 20px;
}

.grid--container {
    display: grid;
    gap: 20px;
}一般情况来说,gap 能满足大部分场景,因为现在主流的布局技术是 Flexbox 或 Grid。只不过很多开发碍于 gap 的兼容性,特别是在非 Flexbox、Grid和多列的布局中,更多采用 margin 来设置元素与元素之间的间距。拿下面这些示例来说。
我们在开发的过程中需要考虑不同的内容长度。这意味着,空白间距应该添加到元素上,即使它看起来并不需要:




在这个例子中,左侧有一个标题,右侧有一个可操作的行动点(比如一个操作按钮)。在上图中来说,它效果起起来不错,但是当标题的字符变多了呢?




注意到了吗?标题和按钮之间离得太近了,甚至因标题内容过长会覆盖按钮区域。当然,你可能会像前面所介绍的内容那样,让文本折行或文本截断。但即使你这样设置了样式,但也应该在标题和按钮之间设置一个间距,这样做最起码可以让文本和按钮之间不会紧挨着。
.title {
    margin-right: 1rem;
}



注意,如果标题和按钮是Flex项目或Grid项目,那么在他们的父容器中显式设置gap会更佳。如果不是的话,我们更应该把margin设置在按钮元素上,这样做的好处是,不管按钮是否存在,都不会让标题有一个额外的间距存在:
.button {
    margin-left: 1rem;
}除了像上面这样设置是间距之外,还可以使用 CSS 选择器相邻选择器(+)来设置margin:
.title + .button {
    margin-left: 1rem;
}上面这个方式,特别适用于多个并排元素之间。比如像下图所示,有两个相邻的按钮,只是希望在按钮之间有一个间距:




我们可以像下面这要来设置按钮之间的间距:
.button + .button {
    margin-left: 1rem;
}它是说:“如果一个按钮紧挨着另一个按钮,以防万一,给第二个按钮加一个margin-left”。 事实上,在 Web 开发中,这样的场景特别的多。




可以采用相同的方式来设置间距:
.cards + .cards {
    margin-top: 20px;
}

.card + card {
    margin-left: 20px;
}另外,在Web布局中,还有像下图这样的场景,会在容器中设置一个内距(padding),然后元素与元素之间设置一个margin-bottom,造成最后一个元素与容器之间的间距明显变大:


代码可能像下面这样写的:
.cards {
    padding: 20px;
}

.card {
    margin-bottom: 20px;
}你可能会说,使用结构性伪类选择器,可以避免:
.cards {
    padding: 20px;
}

.card:not(:last-child) {
    margin-bottom: 20px;
}

/* 或者 */

.card:not(:first-child) {
    margin-top: 20px;
}除此之外,采用相邻选择器会更好一点:
.cards {
    padding: 20px;
}

.card + .card {
    margin-top: 20px;
}
特别声明,最佳的还是gap。只不过,目前为止他只能用于 Flexbox 、Grid 和 多列布局中
继续拿上面卡片为例。在每张卡片(.card)上可能会设置一定的padding值,但有的时候,可能会因为某个原因,卡片上的数据(内容)并没有正常的输出,那么输出的效果可能会像下面这样:




为了避免上图这种现象出现,我们可以使用伪类选择器:empty 将卡片的内距重置为0:
.card {
    padding: 12px;
}

.card:empty {
    padding: 0;
}
在 CSS 伪类选择器中,除了:empty 之外,还有一个与其类似的伪类选择器 :blank,只不过到目前为止,支持:blank的选择器非常少。如果你对这两个选择器感兴趣,请移步阅读《CSS伪类选择器::empty vs. :blank》一文。
我们重新回到 CSS Flexbox 和 CSS Grid 布局中来,在 Flexbox 和 Grid 布局中,除了 gap、margin 可以设置Flex项目或Grid项目之间的间距之外,还可以使用 CSS Box Alignment 来控制它们之间的间距:




虽然说,使用 CSS Box Alignment 中的属性可以控制项目之间的间距,比如在 Flexbox 容器中,使用justify-content 将Flex项目彼此隔开,但这并不意味着,就是完美的。比如说,当Flex项目的数量固定时,布局看起来是没有问题的。但是,当Flex项目的个数增加或减少时,布局看起来就会变得非常奇怪。比如下图所示,Flexbox容器中有四个Flex项目,Flex项目之间的间距并不是由 gap 或 margin 来控制,而是由justify-content 的值来控制,比如 space-between:
.wrapper {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
}



让我们来看其怪异的场景,比如Flex项目的数量少于4个时,它呈现给用户的效果如下:




除了 space-between 之外,还有 space-around 和 space-evenly 有相似的现象。
正如上图所示,在一些场景(Flex项目过少或过多),这要的效果都是不友好的,但我们可以通过以下几种方式进行处理:

  • margin 设置外边距(但需要一些额外的技巧,详细的可以阅读《Flexbox gutters and negative margins, mostly solved》一文)
  • 在Flexbox容器(或Grid容器)上设置gap
  • 增加空元素来占位
最为简单的是使用 gap:
.wrapper {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
}



你是否留意过,前面介绍“Flexbox中的滚动失效”的示例中,虽然我们在滚动容器中显式设置了一个padding值,但在滚动容器中水平方向右侧和垂直方向底部的padding在视觉呈现过程中看似被丢失了:




要避免这种现象,需要把运用于滚动容器上的 padding 换成 border:
.scroll-container {
    border: 20px solid transparent;
}或者改变其 HTML 结构,在滚动容器外增加一层包裹元素:
<!-- HTML -->
<scroll-container-wrapper>
    <scroll-container>
    </scroll-container>
</scroll-container-wrapper>并且把运用滚动容器的padding移到其包裹容器中:
scroll-container-wrapper {
    padding: 20px
}



避免使用固定尺寸

很多 Web 开发者(特别对于经验不足的 Web 开发者)都喜欢在元素上显式设置width和height来控制尺寸。在一些 Low-Code 和 D2C(Design To Code)的应用上,这种现象更是很普遍。但稍微有一点经验的 Web 开发者都知道,显式在元素上设置 width 和 height 是最易于破坏布局的。
设计师给我们提供的设计稿,每个层(近似元素)都设置了 width 和 height值,而且效果是OK的。但在实际中,Web中的元素的内容并不像设计稿一样固定的,它是动态,而且具有不同长度。也就是说,在还原 UI 设计稿的时候,如果你根据设计稿的图层信息,给元素设置固定的尺寸(width 和 height),就会导致布局的破坏。例如下面这种情况:




为了避免内容超出容器,我们需要使用 min-height 来替代 height:
.hero {
    min-height: 350px;
}



这样一来,当内容变得更多时,布局依旧是完美的(不会因内容变多而撑破)。
与其类似的是,给元素设置宽度的时候,也应该尽可能的使用 min-width 来替代 width。比如,在构建一个 <Button> 组件时,你可能曾碰到过因按钮中的文字与左右边缘间隙过小(在没有显式设置padding情况下),会选择性地在按钮上使用 width 设置固定值:
.button {
    width: 100px;
}如果按钮里面的文字长度超过 100px,它将靠近按钮的左右边缘。如果再长一些,按钮文本就会超出其容器,也可能因此会打破布局,给用户的体验也较差。




把 width 换成min-width 就不会出现这种现象了:
.button {
    min-width: 100px;
}如果是在 Flexbox 或 Grid 布局中,更应该尽可能的设置固定的值,更好的方案是采用内在尺寸来定义元素的尺寸。
有关于这方面更多的介绍,还可以阅读:

  • CSS Box Sizing Module Level 3
  • 元素尺寸的设置
  • Making Things Better: Redefining the Technical Possibilities of CSS
  • Intrinsic Sizing In CSS
  • How Big Is That Box? Understanding Sizing In CSS Layout
在 Web 开发过程中,我想你可能经历过这种情况。你有一个元素,在折叠和展开时,希望能够使用 CSS 的 transition 让其具有一定的动画效果,即平滑的折叠和展开。只不过,元素展开的大小需要取决于其内容。你可能会这样编写 CSS:
.panel {
    transition: height .2s ease-out;
}

.panel.collapsed {
    height: 0;
}这样的代码,并没有过渡效果,只是两种尺寸之间的跳动。注意,引起这种现象,发生在元素开始和结束状态时height为auto。


height值为其他单位,比如 px、%或其他绝对单位时都能如期工作。但在实际生产中,我们编码时,并无法预知元素的内容有多少,也就不知道其高度。换句话说,如果显式设置一个固定值,比如 300px,那么就有可能会因内容过多而撑破布局。
在 CSS 中,我们有一些方案可以避免这种现象出现:

  • 使用一个合理的max-height 来替代 height
  • 使用 transform: scaleY()
比如:
.collapsible {
    transition: transform 0.5s ease-out;
    transform-origin: top left;
}

.collapsed {
    transform: scaleY(0);
}另外,在一些纯CSS写的手风琴的效果(Accordion)常使用 max-height替代height:
p {
    max-height: 800px;
    opacity: 1;
    transition: all 500ms ease;
}

input[type=checkbox]:checked ~ p {
    max-height: 0;
    opacity: 0;
}    See the Pen <a href="https://codepen.io/Pavan_Yuvan/pen/gPMdxR">   CSS only Accordion (No JavaScript)</a> by Pavan teja (<a href="https://codepen.io/Pavan_Yuvan">@Pavan_Yuvan</a>)   on <a href="https://codepen.io">CodePen</a>.  
有关于这方面更多的介绍,还可以阅读下面这些文章:

  • Using CSS Transitions on Auto Dimensions
  • How To Do CSS Transitions With Height: Auto
  • Animating height: auto
如果希望想改变 width 时添加平滑的过渡动效,上面的方式也适用于 width。最简单地方式使用 max-width 替代 width。
与图片相关的防御式 CSS

图片已然成为 Web 中重要媒体,俗话说,一图胜过千言万言。在 Web 中使用图片的方式很多种,除了HTML的 <img> 可以将图片引入 Web 之外,HTML5 还新增了 <picture> 元素,让大家在 Web 中使用图片有更多的选择和方式。在 CSS 中为图片服务的属性也不少。比如说:

  • 使用 background-image 和 mask-image 可以将图片引入 Web
  • 使用 background-size、mask-size、object-fit 等要以控制图片的尺寸
  • 使用 background-position、mask-position、object-position 等可以控制图片的位置
  • 使用 background-repeat 、mask-repeat 等可以控制图片是排列方式
  • 等等...
这些属性的使用可以帮助我们在处理 Web 图片时,具有一定的防御性。
防止图片拉伸或挤压

当我们无法控制Web上图片的宽高比时,最好能提前考虑,并在用户上传与宽高比不一致的图片时提供相应的解决方案。比如下面这个示例,在我们平时的业务开发中非常可见,一个带有产品图的卡片组件。




当用户上图不同尺寸的图片时,图片将被拉伸(甚至会失真)。




针对这个现象,可以使用 object-fit 来解决:
.card__thumb {
    object-fit: cover;
}



有一些Reset CSS(重置CSS)中,喜欢将 object-fit 应用于所有 img 元素上,以避免 Web 上的图片被意外的抻伸或挤压:
img {
    object-fit: cover;
}这是一个不错的选择,但并不是代表 object-fit:cover 适用于所有场景。有的时候,也需要根据场合做出正确(或者说合适)的选择。比如 @Ahmad Shadeed 的另一篇博文《Aligning Logo Images in CSS》。让产品Logo图片能在 Web 中有一个更好的呈现方式,如下图所示:




这个时候,运用在img上的 object-fit属性的contain要比cover更为合适:
.brands__item img {
    width: 130px;
    height: 75px;
    object-fit: contain;
}在这样的场景中,object-fit 取值为 contain 的好处是,无论宽度或高度如何,Logo图片都包含在其中,不会被扭曲。
特别声明,在 background-size 和 mask-size 两个属性中,也可以取值为 cover 和 contain,使用方式和 object-fit 相似,不同之处是 background-size 用于背景图片,mask-size 用于蒙层图片。另外,object-fit除了可以取 cover 和 contain之外,还可以取fill、scale-down和none等值。
当然,有些场景也不适合使用 object-fit、background-size 或 mask-size 的。比如说,如果元素或图片显式设置了一个固定高度的值。此时,使用 background-size: cover 或 object-fit: cover 就会出现图片过宽的情况,从而让图片失去重要的细节,可能会影响用户对图片的认知。
.card__thumb {
    height: 220px;
}



如上图所示,因为图片设置了一个固定高度,这个时候object-fit: cover 把图片变得更宽,同时也导致卡片组件宽度也变宽。造成这种现象是因为没有显式指定一个宽高比。我们可以使用 f="https://www.w3cplus.com/css/css-aspect-ratio.html">CSS 的 aspect-ratio 来避免这种现象:
.card__thumb img {
    aspect-ratio: 4 / 3;
}
注意,在CSS中实现宽高比除了使用 aspect-ratio 之外,还有一些其他 Hack 手段,如果你对这方面感兴趣的话,可以阅读《CSS实现长宽比的几种方案》一文。
不要忘了 *-repeat

这里的 *-repeat 指的是CSS中的 background-repeat 和 mask-repeat!
通常,当使用尺寸比较大的图片作为背景图片时,不要忘记检查一下页面在大屏幕上的展示效果。图片作为背景,在默认情况下会被重复显示,这是因为 background-repeat 的默认值为 repeat。
由于笔记本电脑的屏幕相对较小,出现图片重复显示的概率相对较小。但在更大屏幕上,元素的尺寸也可能会随之变大,此时背景图片就有可能会被重复展示:




为了避免这种情况,我们需要显式设置 background-repeat 的值为 no-repeat:
.hero {
    background-image: url('..');
    background-repeat: no-repeat;
}注意,如果你在开发过程中,使用 CSS 的 mask 属性,那么这样的现象也会出现在 mask-repeat上,需要将其设置为 no-repeat:
.hero {
    mask-image: url('..');
    mask-repeat: no-repeat;
}如果你想更进一步的了解 background-repeat 和 mask 相关的知识,可以移步阅读:

  • Clipping和Masking 何时使用
  • 探索CSS Masking模块:Masking
  • href="https://www.w3cplus.com/css3/css3-background-repeat-space-round.html">单聊 background-repeat
img 底部的额外 4px

使用 <img> 将图片引入 Web 上时,在默认情况之下,图片浏览器中展示时,底部分有大约 4px 的空白间距。




这是一个bug吗?当然不是,这是默认行为。
<img>元素默认情况之下是一个可替换元素(Replaced Element)。默认情况下,<img> 的底部与容器的基线(baseline)对齐。基线是像 a, b, c, d 这样的字母所在的位置,这意味着像 g , j, y 这样的字母,它们的一部分位于基线以下(顺便说一下,这些部分被称为"下降")。这就是你在默认情况下看到的大约 4px 的间隙,因为图像是在基线上渲染的,为下降线留出空间。
这个问题是由于图片相对于同一行其他元素的vertical-algin造成的,我们可以很容易地通过以下方式进行纠正:

  • 更改vertical-align属性,比如显式设置非baseline的值("https://www.w3.org/TR/css-inline-3/#transverse-alignment">baseline是其默认值),如top、bottom 或 middle等,但该属性仅适用于内联元素
  • 更改 display 属性值,使 <img> 成为一个块元素而不是内联元素
  • 还有一些其他的技巧,包括设置父容器的line-height为0,设置父容器的font-size为0
就我个要而言,我更喜欢在重置样式表中,给img 添加一个全局的样式,避免Web上图片底部有这个4px额外空白间距出现:
img {
    display: block;
}
注意,HTML 中的 <iframe> 和 <video> 元素和 <img> 同类型的标签元素,在这些元素中同样会存在这种现象,在使用类似 <img> 可替换元素时,都建议在重置CSS的时候,显式设置display 的值为block。
图片上的文字

在 Web 中,很多场景中,文字会出现在图片之上:




大多数的时候,开发者都会考虑在文本和图片之间增一个层,这个层可能是一个纯色层,也能是一渐变层,也可能是一个带有一定透明度的层,为增加文本的可读性:




正如《处理图片上文字效果的几种姿势》一文所介绍的,有多种方式可以来处理,比如下面这个示例:
.card__content {
    background-image: linear-gradient(
        to top,
        hsla(0, 0%, 0%, 0.62) 0%,
        hsla(0, 0%, 0%, 0.614) 7.5%,
        hsla(0, 0%, 0%, 0.596) 13.5%,
        hsla(0, 0%, 0%, 0.569) 18.2%,
        hsla(0, 0%, 0%, 0.533) 22%,
        hsla(0, 0%, 0%, 0.49) 25.3%,
        hsla(0, 0%, 0%, 0.441) 28.3%,
        hsla(0, 0%, 0%, 0.388) 31.4%,
        hsla(0, 0%, 0%, 0.333) 35%,
        hsla(0, 0%, 0%, 0.277) 39.3%,
        hsla(0, 0%, 0%, 0.221) 44.7%,
        hsla(0, 0%, 0%, 0.167) 51.6%,
        hsla(0, 0%, 0%, 0.117) 60.2%,
        hsla(0, 0%, 0%, 0.071) 70.9%,
        hsla(0, 0%, 0%, 0.032) 84.1%,
        hsla(0, 0%, 0%, 0) 100%
    );
    color: #fff;
}    See the Pen <a href="https://codepen.io/airen/pen/MWvyGWp">   Handling Text Over Images in CSS</a> by Airen (<a href="https://codepen.io/airen">@airen</a>)   on <a href="https://codepen.io">CodePen</a>.  
但很少有同学会在编写 CSS 的时候,考虑<img> 加载失败时的图片展示。比如说下面这图,左侧是图片加载正常的情况下,右侧是图片加载失败的情况。
图片在加载的正常的情况下,文字具有可读性(效果看起来还不错),但当图片加载失败的时候,图片上文字效果会受到影响(如上图中右侧的示意图,白色的文字和背景几乎融为一体),用户很难看清楚文本内容。




注意,上图中是图片和文本之间没有添加任何样式。如果在文本和图片之间增加了额外层,即图片加载失败,文本的可读性不会像上图那么差,这根据你的中间层样式来决定:




其实,更具防御式的 CSS 应该是在 <img> 中设置一个与文本颜色具有一定对比度的颜色。比如一个grey颜色,这样一来,图片加载失败了,也不会影响图片上文本的可读性:
.card__thumb img {
    background-color: grey
}



这样做,即使你在图片和文本之间有一层,也不会受影响,他只会更好的增加对比度,提高可读性:




当然,你可以像 @Ire Aderinokun 在 2016 年发表的博文《Styling Broken Images》那样,借助 CSS 伪元素::before 和 ::after 为加载失败的图片定制一个更美观,更符合设计要求的样式:
img {
    /* Same as first example */
    min-height: 50px;
}

img::before,
img::after {
    position: absolute;
    width: 100%;
    left: 0;

}

img:before {
    content: " ";
    top: -10px;
    height: calc(100% + 10px);
    background-color: rgb(230, 230, 230);
    border: 2px dotted rgb(200, 200, 200);
    border-radius: 5px;
}

img:after {
    content: "\f127" " Broken Image of " attr(alt);
    font-size: 16px;
    font-style: normal;
    font-family: FontAwesome;
    color: rgb(100, 100, 100);
    top: 5px;
    text-align: center;
}效果如下:
    See the Pen <a href="https://codepen.io/airen/pen/OJxGNyR">   Styling Broken Images with CSS</a> by Airen (<a href="https://codepen.io/airen">@airen</a>)   on <a href="https://codepen.io">CodePen</a>.  
图片的最大宽度

一般来说,不要忘记为所有图片设置max-width: 100%。这可以添加到你使用的 CSS 重置样式中:
img {
    max-width: 100%;
    height: auto;
    object-fit: cover;
}这样做是让图片具有一定的响应式能力。
暗黑模式下降低图片亮度

如果你的 Web 应用要具备暗黑模式,图片的处理也是一个不可忽略的细节。在一个小型的应用中,你可以使用 HTML5 的 <picture> 元素,为不同模式加载不同格式图片:
<picture>
    <source srcset="settings-dark.png" media="(prefers-color-scheme: dark)">
    <source srcset="settings-light.png" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)">
    <img src="settings-light.png" id="screenshot" loading="lazy">
  </picture>

但这种使用方式对于一中大型的Web应用来说,可能没有能力为 Web 应用提供两个版本的图片源。在这种情况之下,可以使用 CSS 的滤镜 filter 特性来降低图片的亮度,即 在 dark 模式下降低图片亮度:
@media (prefers-color-scheme: dark) {
    :root {
        --image-filter: grayscale(50%);
    }

    img:not([src*=".svg"]) {
        filter: var(--image-filter);
    }
}使用 filter 降低图片的灰度(grayscale(50%))虽然是dark模式下降底图片亮度的一个快速解决方案,但这不是最佳的方案。只有在不具备为暗黑模式提供专用图片的时候才推荐它。
图片上的内阴影

不知道是否曾在 <img> 元素上使用内阴影,像下面这样使用:
img {
    box-shadow:inset 5px 5px 5px 5px #09fa00;
}如果你像上面这样给一个 img 设置一个内阴影,肯定有发现过,运用于图片上的内阴影丢失了:


造成该现象的主要原因是 “img 元素被认为是一个空元素,而不是一个容器元素”。
注意,HTML中除了 <img> 元素是一个可替换元素(Replaced Element)之外,还有 <iframe>、<video>、<embed>等,除此之外,有些元素在特定情况下也会被视为可替换元素,比如 <option> 、<audio> 、<canvas>、<object> 和 <applet>等。另外,HTML规范也说了,<input>元素可替换,因为 image 类型的 <input> 元素就像 <img> 一样被替换。但是其他形式的控制元素,包括其他类型的 <input> 元素,被明确地列为非可替换元素(non-replaced elements)。
虽然可替换元素有很多个,但 box-shadow 的内阴影(inset)用于这些可替换元素时会被视为空元素的只有 <img>、<video>、<iframe> 和 <embed>。比如,你在 <video> 上使用 box-shadow 内阴影时,它的表现和 <img> 一样。
    See the Pen <a href="https://codepen.io/airen/pen/PoJdyKv">   box-shadow for Replaceable Element</a> by Airen (<a href="https://codepen.io/airen">@airen</a>)   on <a href="https://codepen.io">CodePen</a>.  
要避开这个现象,我们要在 <img> 和 <video> 元素的外层再套一个容器元素,比如 div,并且将运用于<img>(或<iframe>)元素上的内阴影用到其容器元素上:
<!-- HTML -->
<div class="media--wrapper">
    <img src="" alt="" >
</div>

/* CSS */
.media--wrapper {
    box-shadow: inset 0 0 4vmin 3vmin rgb(0 0 0 / .5);
}

.media--wrapper img,
.media--wrapper video {
    position: relative;
    z-index: -1;
}你将看到的效果如下:




但这还不是最佳的解决方案,对于图片而言,把图片运用于 background-image 才是更好的方案,但对于 <video> ,上面的解决方案已是较佳的解决方案了。
如果你对 Web 中阴影的使用感兴趣,可以移步阅读《图解CSS:CSS阴影》一文。
时至今日,图片已是Web重要媒体元素之一,大部分开发者都认为 Web 图片的展示非常的简单,方式也很多。我个人并不这么认为,特别是在 2021 年折腾渲染性能、响应式设计之类的,我发现要在 Web 上用好图片,难度还是较大的。如果你对这方面的话题感兴趣的话,可以阅读下面这些与图片相关的文章:

  • 探索Web上图片使用方式
  • href="https://www.w3cplus.com/html5/img-in-web.html">聊聊 img 元素
  • 响应式图片使用指南: (Part1)、Part2、Part3和Part4
  • 图片的优化
与滚动体验相关的防御式 CSS

这部分是与滚动体验相关的 CSS 属性的设置,这些属性的运用大部分是用来改善滚动相关的体验。
锁定滚动链

滚动链指的是z轴的多个容器都出现了滚动。
多个容器出现滚动条(z轴不同层的滚动容器)最常见的情景是你打开一个弹框(Modal)并向下滚动到底部(垂直方向)时,如果继续向下滚动则会引起弹框下方的内容(通常是 body 元素)会继续滚动。这也是滚动链默认的表现行为:


为了避免滚动扩散到其他滚动容器,我们可以在顶部的滚动容器中把 CSS 的 overscroll-behavior属性设置为 contain:
.modal__content {
    overscroll-behavior-y: contain;
    overflow-y: auto;
}就我们这个示例而言,在弹框(Modal)中显式设置 overscroll-behavior之后,弹框中的滚动容器滚动底部之后不会影响底部(body)的滚动:


注意,overscroll-behavior 属性只有在发生滚动的时候才会产生效果。有关于该属性更多的介绍,可以阅读《CSS overscroll-behavior》一文。
在必要时显示滚动条

在内容比较长的情况下,可以通过设置 overflow 控制滚动条是否展示。但是这里更推荐将overflow的值设置为 auto。如果你将 overflow显式设置为 scroll时,不管容器内容长短,滚动条都会像下图这样展示出来:




这种效果并不友好,在非必要的情况下,滚动条不应该向用户展示。只需要在滚动容器中显式设置overflow为auto即可改变这种现象:
.element {
    overflow-y: auto;
}容器设置overflow-y为auto时,只有内容过长溢出滚动容器时滚动条才会向用户展示,内容不溢出容器则不会展示滚动条:




滚动条的占用空间

关于滚动条方面另外要注意的地方是 滚动条占用元素的空间,导至渲染内容的区域变小。比如在前面提到的例子中,当内容变长出现了滚动条的时候,会引起布局发生变化,因为滚动条要占用布局元素的空间。




仔细对比上图中前后的变化,不难发现滚动条导致白色的内容区变窄了。我们可以设置scrollbar-gutter属性来避免这个问题。
.element {
    scrollbar-gutter: stable;
}



scrollbar-gutter是 CSS Overflow Module Level 4  新增的一个特性。有了这个属性,开发者就可以更好的控制滚动条,或者解决因滚动条类型不同引起布局的差异变化。下图中展示了 scrollbar-gutter的取值不时的效果:




美化滚动条 UI

不过,你要美化滚动条UI的话,还是要使用 2021 年 12 月 09日,W3C 新发布了的 CSS Scrollbars Styling Module Level 1 规范提供的 scrollbar-color 和 scrollbar-width 两个属性。以后,我们可以使用它们来轻易完成滚动条样式的定制:
.section {
    scrollbar-color: #6969dd #e0e0e0;
    scrollbar-width: thin;
}



我想大家都知道,使用 CSS 来美化滚动条样式主要原因之一是因为滚动条在不同的系统平台上显示有差异,外观不统一。
让滚动效果更丝滑

在某些情况之下,你可能发现滚动会有一定的卡顿。我们可以滚动容器中设置scroll-behavior的值为smooth,让滚动更顺滑。很多时候为了让页面滚动更平滑,建议在 html 元素上设置该样式:
html {
    scroll-behavior: smooth;
}



滚动捕捉

CSS 滚动捕捉(CSS Scroll Snap)规范 提供了一些优化滚动体验的特性,可以在用户滚动浏览文档时,将其滚动到特定的点(位置)。这对于在移动设备上甚至在PC端上为某些类型的应用程序(比如滑块组件)创造一个更类似于应用程序(原生客户端)的体验是很有帮助的。
简而言之,CSS 滚动捕捉可以:

  • 防止滚动时出现尴尬的滚动位置
  • 创建更好的滚动体验
比如下面这个示例:
.container {
    overflow-x: auto;
    overflow-y: hidden;
    scroll-behavior: smooth;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    scroll-snap-type: x mandatory;
}

img {
    scroll-snap-align: center;
    scroll-snap-stop: always;
}    See the Pen <a href="https://codepen.io/airen/pen/dyNJPoY">   水平全屏滚动</a> by Airen (<a href="https://codepen.io/airen">@airen</a>)   on <a href="https://codepen.io">CodePen</a>.  
在上面的示例滑动图片的时候,滚动体验就像是在一个客户端应用中的滑动体验。你每次滚动的时候,只能滚动一张图片:


有关于滚动捕捉更详细的介绍,可以阅读下面这两篇文章:

  • CSS滚动捕捉(Part1)
  • CSS滚动捕捉(Part2)
上面提到这个有关于滚动相关的属性的使用,他更多是用来优化滚动体验的,似乎离防御式CSS有点距离,但还是列到这个系列中的主要原因是,使用这些CSS特性,并不是很复杂,很多的CSS代码,但给用户带来的体验是完全不一样的。为此,在开发过程中,应该尽可能在滚动容器上加上这些特性。
如果你对上面提到的这些属性感兴趣,想进一步了解的话,可以阅读下面这些文章:

  • 你所不知道的CSS Overflow Module
  • ref="https://www.w3cplus.com/css/overscroll-behavior.html">CSS overscroll-behavior
  • 改变用户体验的滚动新特性
  • CSS滚动捕捉:Part1、Part2
与圆角相关的防御式 CSS

自从 ="https://www.w3cplus.com/css/css-border-radius.html">CSS 新增 border-radius 属性之后,在 Web 中构建圆角变得非常的简易,再也不需要依赖滑动门技术来实现圆角效果。但在使用 border-radius 给元素设置圆角时有几个地方需要注意。
嵌套圆角的计算

使用 border-radius 的时候,有的时候会产生圆角嵌套的视觉效果:




嵌套圆角的现象有可能发生在一个元素上,也有可能发生在内嵌元素之间,甚至都有可能同时发生在这两种现象中。一般情况之下,Web开发者在还原UI时,元素的border-radius的值是直接从视觉稿的图层属性中获取,很少在开发的过程中会注意圆角的嵌套现象。我们来看一个简单的示例:
<!-- HTML -->
<div class="card">
    <img src="" alt="" >
</div>

/* CSS */
:root {
    --border-radius: 10px;
    --border-width: 0px;
    --padding: 0px;
}

.card {
    border-width: var(--border-width);
    border-radius: var(--border-radius);
    padding: var(--padding);
}

.card img {
    --radius: calc(var(--border-radius) - var(--border-width) - var(--padding));
    border-radius: var(--radius);
}    See the Pen <a href="https://codepen.io/airen/pen/BawEbLr">   Untitled</a> by Airen (<a href="https://codepen.io/airen">@airen</a>)   on <a href="https://codepen.io">CodePen</a>.  
调整示例中border-radius、border-width 和 padding 的值,你可看到容器.card 和内容 img 元素的 border-radius 的变化:


从上面的效果中不难发现,内容区域的圆角半径的值计算是按下面的公式来计算的:
r = R - BW - PW其中:

  • R 是容器自身的 border-radius 的半径(外圆角半径)
  • BW 是容器自身的 border-width 的值(边框粗细)
  • PW 是容器自身的 padding 的值(内距)
  • r 内容区域的 border-radius 的半径(内圆角的半径)
如果是多个元素嵌套,且只在最外的容器显式设置 border-radius值,那么第一层嵌套的子元素的圆角半径将按上面的公式计算获得,且得出来的半径值将成为第二层的子元素的圆角半径(R)。依此类推,直到计算出来的 border-radius 的值为 0(小于0的值会被视为0)。
我想说的是,我们在还原 UI 的时候,需要考虑内外部元素之间的圆角半径之间的关系,这样在视觉的还原上会更协调。
半径重叠会发生什么

在Web中有一些UI的风格看上去就像“胶囊”的外形:




我们常把这种UI的风格称作“胶囊UI”,这种“胶囊UI”常用于一些button、checkbox和radio的元素上。




CSS实现这种胶囊UI的效果,为了能达到一劳永逸,时常给元素的border-radius值设置为一个较大的值,比如999rem、999vmax之类的。这样做不管元素高度是多少,都可以实现胶囊UI的效果:
.pill {
    border-radius: 999vmax;
}这行代码的意思,我想大家都懂,.pill元素四个角的“圆角”半径都是999vmax。这种方式很方便,因为这意味着我们不需要知道元素(矩形框)的尺寸,它也能正常的工作。


不过,在某些边缘情况上,会遇到一些奇怪的行为。比如在上面的示例基础上稍作调整,就是把border-radius的值设置为:
.pill {
    border-radius: 100px 999vmax 999vmax 100px;
}你会发现元素 .pill左上角和左下角虽然设置了border-radius的半径值为100px,但并没有圆角效果:


你可能会好奇,为什么左上角和左下角100px的border-radius 为什么没有生效?其实W3C规范中已经给出了答案:
Let f = min(Li/Si), where i ∈ {top, right, bottom, left}, Si is the sum of the two corresponding radii of the corners on side i, and Ltop = Lbottom = the width of the box, and Lleft = Lright = the height of the box. If f < 1, then all corner radii are reduced by multiplying them by f.
具体的解释请看下图:




公式看上去令人感到困惑,甚至是令人头痛。但我们只需要记住一点:这个公式的目的是防止border-radius(圆角半径)重叠。简单地说:
客户端(浏览器)本质上是在想:“按比例缩小所有半径(border-radius),直到它们之间没有重叠”!
我们来用简单的示例来阐述上述公式的一些基本原理,这样可以让大家更好的理解。
首先,它会计算矩形(元素)每条边的长度与与它接触的半径之和的比值:
元素每条边宽度 / (相邻圆角半径1 + 相邻圆角半径2)比如元素.pill设置的样式:
.pill {
    width: 600px;
    height: 200px;
    border-radius: 400px;
}就该示例而言,按照上面示提供的公式就可以“计算出.pill元素每条边的长度与与它接触的半径之和的比率”:




然后将所有圆角的半径去乘以这些比值(每条边计算出来的比率值)中的最小值。上例中计算出来的比率值只有.75和.25,取更小的值 .25,那么计算出来的圆角半径值则是:
400px x .25 = 100px我们元素.pill的height是200px(最短的边长),计算出来的border-radius刚好是height的一半,即 100px。这也让我们实现了一个“胶囊”UI效果。
为了能了解的更清楚一些,我们回到前面有问题的示例中,只不过我们用400px来替代999vmax,比如:
.pill {
    width: 600px;
    height: 200px;
    border-radius: 100px 400px 400px 100px;
}同样根据上面的公式来计算出每边的比例:
Ratio » 元素每条边宽度 / (相邻圆角半径1 + 相邻圆角半径2)

Top    » 600px / (100px + 400px)  = 1.2
Right  » 200px / (400px + 400px)  = 0.25
Bottom » 600px / (400px + 100px)  = 1.2
Left   » 200px / (100px + 100px)  = 1四个方向最小的比率是 0.25,那么所有指定圆角半径乘以这个比例:
Top-Left     » 100px x 0.25 = 25px
Top-Right    » 400px x 0.25 = 100px
Bottom-Right » 400px x 0.25 = 100px
Bottom-Left  » 100px x 0.25 = 25px这样一来,运用于.pill元素的border-radius值为25px 100px 100px 25px:




是不是觉得非常的神奇。我想这部分内容能解答你平时碰到的一些怪异的现象,即 使用border-radius怪异的现象
圆角碰到变换

前面提到过,在 CSS 中如果要给元素的 width 或 height 进行动画处理是需要采用别的技术方案的。比如说,使用 CSS 的 transform。但这也会有一个弊端。比如说,该元素设置了 border-radius。如下图所示,给一个设置了圆角(border-radius)的元素使用transform 来模拟宽度或高度变化的动效时,元素上的圆角并不会重新绘制,圆角只是被缩放了。




如果圆角要重新绘制就需要渲染引擎能重绘(Repaint),但GPU不会这样处理,它只处理像素,而不是元素的内容。因为 GPU 非常羞于处理像素(GPU只需要处理呈现该元素的像素),所以 transform 的操作的速度非常快。这也是使用transform来处理元素宽度或高度动效的主要原因之一。
如果要避免这种现象出现,就需要采用一些技术手段来规避。比如 @Rik Schennink 在他的文章《Animating CSS Width and Height Without the Squish Effect》中提到的九宫法(9-slice scaling)。下面这个示例,我是在其文章中的示例做了稍微的调整:
<!-- HTML -->
<div class="radius">
    <div class="content"></div>
</div>

/* CSS */
.radius {
    height: 100px;
    display: flex;
    justify-content: flex-start;
}

.radius::before,
.radius::after {
content: "";
    width: 20px;
    background: #098fae;
}

.radius::before {
    border-radius: 20px 0 0 20px;
}

.content {
    background: #098fae;
    width: 1px;
    transform: scale3d(1, 1, 1);
    transform-origin: left;
}

.radius::after {
    border-radius: 0 20px 20px 0;
    transform: translate3d(0, 0, 0);
}

@keyframes right-animate {
    0% {
        transform: translate3d(0, 0, 0);
    }
    100% {
        transform: translate3d(80px, 0, 0);
    }
}

@keyframes center-animate {
    0% {
        transform: scale3d(1, 1, 1);
    }
    100% {
        transform: scale3d(81, 1, 1);
    }
}

.content {
    animation: center-animate 1s linear infinite alternate;
}

.radius::after {
    animation: right-animate 1s linear infinite alternate;
}    See the Pen <a href="https://codepen.io/airen/pen/xxXevpY">   Untitled</a> by Airen (<a href="https://codepen.io/airen">@airen</a>)   on <a href="https://codepen.io">CodePen</a>.  
上面这个示例中:

  • 使用 Flexbox 布局替代原示例的绝对定位
  • 使用 3D Transform 替代原示例的 2D Transform
  • 使用伪元素 ::before 和 ::after 替代了原来空标签元素 .left 和 .right
还有一种更好的方案,就是使用 CSS Houdini 中的自定义属性 @property (也称 CSS Houdini 中的变量),@property 可以进一步的扩展 CSS 的动效。比如上面示例,采用 @property 可以像下面这样来实现:
@property --width {
    initial-value: 1px;
    inherits: false;
    syntax: "<length>";
}

@keyframes square {
    to {
        --width: 300px;
    }
}

.content {
    width: var(--width);
    animation: square 2s ease infinite alternate;
}上面展示的只是示例用到的关键代码(@property和@keyframes部分),详细代码请查阅 Codepen上的示例:
    See the Pen <a href="https://codepen.io/airen/pen/gOGyyRy">   Animating CSS width with  @property</a> by Airen (<a href="https://codepen.io/airen">@airen</a>)   on <a href="https://codepen.io">CodePen</a>.  
示例中左侧采用的是 Flexbox 布局,右侧采用的是 Grid布局。但动效采用的是相同的方案,效果几乎是一样的:


z-index 怎么失效了
如果你是一名前端面试官,在面试的时候提出,“有哪些方式可以触发z-index生效”?我想,大部分开发都会说:
当 position 属性的值为非 static,就会触发z-index 生效!
这样的回答并没有问题,但不够全面。时至今日,在 CSS 中触发 z-index 生效,绝不仅 position 值是非 static,还有有很多种方式。在 MDN 上有一个较为全面的清单,描述了触发 z-index 生效方式:

  • 文档根元素(<html>)
  • position 值为 absolute 或  relative 且 z-index 值不为 auto 的元素
  • position 值为 fixed 或 sticky 的元素
  • Flex项目,且 z-index 值不为 auto
  • Grid项目,且 z-index 值不为 auto
  • opacity 属性值小于 1 的元素
  • mix-blend-mode 属性值不为 normal 的元素
  • transform、filter、perspective、clip-path、mask、mask-image、mask-border 等属性值不为 none 的元素
  • isolation 属性值为 isolate 的元素
  • -webkit-overflow-scrolling 属性值为 touch 的元素
  • will-change 值设定了任一属性而该属性在 non-initial 值时会创建层叠上下文的元素
  • contain 属性值为 layout、paint 或包含它们其中之一的合成值(比如 contain: strict、contain: content)的元素
我们在元素上设置 z-index 的值主要是想用来控制元素在 z 轴上的顺序。它的值可以是 auto、0、正负整数值,其中 auto 是其默认值,但在表现上,z-index 取值 auto 和 0 是具有一定差异性的:

  • 不设置 z-index 值时,默认是 auto 。默认层也就是 0 层
  • z-index: 0 与没有定义 z-index ,也就是z-index: auto在同一层级内没有高低之分,文档流中后出现的会覆盖先出现的
  • z-index: 0 会创建层叠上下文 z-index: auto 不会创建层叠上下文
一般使用 z-index 来决定层叠顺序时,会分下面两种情况来讨论:

  • 如果层叠上下文元素不依赖z-index数值,则其层叠顺序是z-index:auto可看成z-index:0级别
  • 如果层叠上下文元素依赖z-index数值,则其层叠顺序由z-index值决定,越大的值越在顶层
有关于这方面的讨论,可以详细阅读下面这些文章:

  • What The Heck, z-index??
  • Understanding Z-Index in CSS
  • Z-index and stacking contexts
  • 深入理解CSS中的层叠上下文和层叠顺序
  • CSS定位和层叠控制
但我们在实际开发过程中,时不时的会碰到 z-index 不生效,即使是在 position 值是非 static 的元素上。就在前不久,我们团队一位同学在开发一个页面时,在定位元素(一个固定定位)显式设置了 z-index 为 9999,但其父元素是一个滚动容器,为了让该滚动容器在 iOS 设备上滑动效果更佳,会使用 -webkit-overflow-scrolling: touch;。只不过,这个时候,他会让其子元素的 position 的非 static 的值都被忽略,渲染行为类似于 static,z-index 也就失效了。为了避免这个现象,我们可以通过 transform: translateZ(0) 的方式重新触发 z-index 生效。
另一个会让 z-index 失效的场景是在 transform 属性触发的 3D 场景。简单地说,transform 有的时候会让 z-index “临时失效”(事实并非 z-index 失效了),只是 z-index 被用在不同的层叠上下文(Stacking Context)上,而非默认的层叠上下文上同等地比较z轴的层级了。所在 DOM 在 transform 的工程中,DOM 处于一个新的层叠上下文中,z-index也是相对于这个层叠上下文,所以表现出来的实际是层叠上下文的层级,动画一结束,DOM 又回到默认的层叠上下文中,这时 z-index 才是在同一个上下文中比较。这种现象的解决方案大致会有两种:

  • 方法1:父级,任意父级,非 body 级别,设置 overflow:hidden 可恢复和其他浏览器一样的渲染
  • 方法2:以毒攻毒。也可以使用3D transform变换,比如 translateZ(0)
就我个人经验而言,往往碰到 z-index 不生效时,采用 translateZ(0) 准保OK!
避免 overflow: hidden 不生效

在 2019 年开发一个互动项目过程中,踩到了 overflow:hidden 不生效的案例。
.root {
    overflow-x: hidden;
    width: 100vw;
}    See the Pen <a href="https://codepen.io/airen/pen/zQMgqv">   How to fixed overflow-x not working on mobile</a> by Airen (<a href="https://codepen.io/airen">@airen</a>)   on <a href="https://codepen.io">CodePen</a>.  
你会发现上面示例中的 overflow-x:hidden 未生效:


为什么会这样呢?简单地说,该示例触发了:

  • 拥有overflow:hidden元素并不具有position取非static的值
  • 内部元素通过position:absolute进行定位
一个绝对定位的后代块元素,部分位于容器之外。这样的元素是否剪裁并不总是取决于定义了overflow属性的祖先容器;尤其是不会被位于他们自身和他们的包含块之间的祖先容器的overflow属性剪裁。另外规范中也有说到:
当一个块元素容器的内容溢出元素的盒模型边界时是否对其进行剪裁。它影响被应用元素的所有内容的剪裁。但如果后代元素的包含块是整个视区(通常指浏览器内容可视区域,可以理解为body元素)或者是该容器(定义了overflow的元素)的父级元素时,则不受影响。
通常一个元素的包含块由离它最近的块级祖先元素的内容边界决定。但当元素被设置成绝对定位时,包含块由最近的position不是static的祖先元素决定。这样一来,知道问题是什么原因造成的就好办了。只需要在设置有overflow:hidden的元素上添加position属性,具值是非static即可。
事实上这种情形并非一无事处,很多时候我们往往又需要让绝对定位的元素不会被设置了overflow:hidden的元素隐藏。比如Tooltips:


对于上面这个示例的场景,我们需要一个这样的结构:
<div class="grand-parent">
    <div class="parent">
        <div class="child"></div>
    </div>
</div>样式简单的如下:
.grand-parent {
    position:relative;
}

.parent {
    overflow:hidden;
}

.child {
    position:absolute;
    top:-10px;
    left:-5px;
}    See the Pen <a href="https://codepen.io/airen/pen/RmELwY">   Overflow and Position</a> by Airen (<a href="https://codepen.io/airen">@airen</a>)   on <a href="https://codepen.io">CodePen</a>.  
另外overflow和position:fixed元素在一起使用的时候,fixed元素将会打破overflow容器的束缚。比如我们有这样的一个结构:
<!-- HTML -->
<div class="overflow">
    <img class="" src="" class="fixed" />
</div>

/* CSS */
.overflow {
    width: 50vw;
    height: 50vh;
    overflow: hidden;
}

.fixed {
    position: fixed;
    top: 2vw;
    left: 2vw;
}众所周知,position:fixed在无特殊属性的限制的时候,其定位是相对于视窗边缘来定位的。即使他的父容器设置了overflow:hidden同样也会失效。比如上面这个示例,图片打破了设置overflow:hidden的容器div:




如果要破这样的局,让fixed的元素也会被剪切,我们可以借助transform来搞定,即transform除了none之外的任何值都会创建层叠上下文和包含块。那该元素就会是固定元素的包含块。
.overflow {
    overflow: hidden;
    transform: translateZ(0);
}



在未来,可以使用CSS Containment达到等同的效果。使用contain: paint让 元素作为一个包含绝对定位和固定定位后代的块。
如果你想更深入了解 CSS 的 overflow 属性,还可以阅读《你所不知道的CSS Overflow Module》一文。
避开 100vh 的坑

在《2022 年的 CSS》一文中介绍新的视窗单位 lvh、svh时提到了 iOS 上Safari使用 100vh 长期存在的Bug。它不能与 vh 单位很好的配合。如果你将一个容器的高度设置为 100vh 时,会导致这个元素有点太高(会出现滚动条)。造成这种现象的主要原因是移动端上的 Safari 在计算 100vh 时忽略了它的部分用户界面。




如果你只是想快速解决这个问题,那可以将下面这段代码放到你的代码片段中:​
body {
    height: 100vh;
}

@supports (-webkit-touch-callout: none) {
    body {
        height: -webkit-fill-available;
    }
}并集选择器

对于同时作用到不同浏览器的样式,并不推荐使用并集选择器。比如,设置 input 中placeholder 的样式时,需要为每种浏览器使用对应的选择器。根据 w3c 的规定,我们如果在这种场景下使用了并集选择器,那么整个样式规则是不合法的。下面的代码是不推荐的。
/* 请不要像这样使用 */
input::-webkit-input-placeholder,
input:-moz-placeholder {
    color: #222;
}下面的代码是推荐的。
input::-webkit-input-placeholder {
    color: #222;
}

input:-moz-placeholder {
    color: #222;
}自定义属性备用值

CSS 自定义属性 (变量) 被越来越多的用于Web开发中。为了避免破坏用户体验,我们需要做一些额外的处理,以防 CSS 自定义属性的值因某种原因为空。
特别是使用 JavaScript 设置 CSS 自定义属性的值时,要更加注意自定义属性的值无效的情况。比如下面的例子:
.message__bubble {
    max-width: calc(100% - var(--actions-width));
}calc() 函数中使用了自定义属性 --actions-width,并且它的值由 JavaScript 代码提供。假如在某些情况下,Javascript 代码执行失败,那么 max-width 的值会被计算为 none。
为了避免发生这种问题,要用 var() 来设置一个备用值,当自定义属性的值无效时,这个备用值就会生效。
.message__bubble {
    max-width: calc(100% - var(--actions-width, 70px));
}这样,如果自定义属性 --actions-width 未被定义,就会使用备用值 70px。这个方法用于自定义属性值可能会失败的场景,比如这个值来自于 JavaScript。在其它场景中,它并不是必须的。
待续...

到目前为止,我能想到的都追加上来了,但我想肯定还有很多我没想到的。如果上面有什么是我遗漏的,欢迎分享。另外,在结束本文的时候,推荐下面几个站点,在这些站点上能获取一些新的 CSS 技术:

  • CSS Protips
  • SmolCSS
  • Modern CSS Solutions for Old CSS Problems
  • CSS Hell: Collection of common CSS mistakes, and how to fix them
  • CSS Snippets
  • Style Stage: A modern CSS showcase styled by community contributions
hshf007 发表于 2023-12-8 15:01:08|来自:中国 | 显示全部楼层
谢邀
CSS的奇淫技巧很多,各种设备限制和兼容性问题也很多。精通CSS的人应该是不仅能把各种特性融会贯通,对可能产生的问题也要有各种规避和解决的能力
前者需要多跟进最新的前沿发展,看官方文档和国外分享的最新内容。后者则需要自己做各种项目去排坑增加经验。
然后到了更高一个精通的层次,则是了解网页文档在浏览器的渲染特性,从而深入了解CSS在其中的原理。这部分其实已经有些脱离现在大部分使用者的生产场景了,但可能又是各种大厂面试喜欢考的,本人对此也知之甚少,只能抛砖引玉。
总结来说要精通CSS就是既要多用,也要多看,缺一不可。
本人也没达到精通水平,所以以上只是个人妄断,不一定客观

快速回帖

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则