学会了《CSS揭秘》中的这些,让99%的面试官对你刮目相看

241 阅读21分钟

摘要

你想知道饼图是如何实现的吗?想知道打字效果如何实现吗?控制原生表格一直是件让人头疼的事,以至于大学时期的老师都让我们尽量避免使用它,然而在这本书中,作者教我们如何精确控制表格列宽。在日常开发中,我们可能用不上那么多的奇淫巧技,然而,本书也有专门教学常用结构与布局的章节,其中不乏真知灼见。本篇将从日常用到的频率,将书中的精华挑出介绍。想知道这些的话,就继续看下去吧!

结构和布局

案例一:

image.png

上图中被红边框框起来的这块div,如何以图片的宽度作为其自适应宽度,如何让块的宽度由它内部的图片来决定,而不是由它的父元素来决定呢?可能很多人会通过浮动实现,但那样往往会改变它的布局模式,会导致我们不想要的结果。来看看本书是怎么做的。

 <div>
      <p>Let’s assume we have some text here. Bacon ipsum dolor sit amet turkey veniam shankle, culpa short ribs kevin t-bone occaecat.</p>
      <div className='align-img'>
        <img src={img} />
        <p>123  </p>
      </div>
      <p>We also have some more text here. Et laborum venison nostrud, ut veniam sint kielbasa ullamco pancetta.</p>
 </div>
.align-img{
  max-width: min-content;
  margin: auto;
}

.align-img>img{
  max-width: inherit;
}

min-content 这个关键字将解析为这个容器内部最大的不可断行元素的宽度(即最宽的单词、图片或具有固定宽度的盒元素)。

知道了 min-content的作用, 那你可能会问, max-content有什么作用呢?

max-content 这个属性,如果有一行很长的文本,那么它将不会折行展示,相当于 white-space:nowrap

image.png

实用指数:🌟🌟🌟🌟🌟

案例二:

“44年前我们就把人类送上月球了,但现在我们仍然无法在CSS中实现垂直居中。”
     James Anderson

对一个块垂直居中,老面试题了好吧!在场不会的可以叉出去了(bushi😈。本书中,又提供了一些新思路。

对于固定尺寸的块

.vertical-and-center-block{
    width: 18em;
    height: 6em;
    position:absolute;
    top:calc(50% - 9em);
    left:calc(50% - 3em);
  }

插播一条: em相对于父元素,rem相对于根元素。

但大多数块没有固定尺寸,我们怎么做?有多种办法

一号选手✊

.vertical-and-center-block{
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  }

面试时,常写的就是上面这个。但是这种方法有问题,当需要居中的元素在高度上超过了视口时,它的顶部会被裁掉。详见📖中7章40节。

二号选手✊

.vertical-and-center-block{
    margin:50vh auto 0;
    transform:translateY(-50%);
  }

是不是很迷惑,为什么距离上方50%,不是 marginTop:50%,而是 50vh。(50vh的意思是,视口高度的50%。视口即屏幕可见区。) 这是因为 margin 的百分比值是以父元素的宽度作为解析基准的 。没错,即使对于margin-top和margin-bottom来说也是这样!

三号选手✊

.father{
  display:flex;
  min-height:100vh;
}

.vertical-and-center-block{
  margin: auto;
}

作者认为这是最好的一种,因为flexbox就是专门为这类需求而生的(🆒 就是专业!)

实际上,它还可以将匿名容器(即没有被标签包裹的文本节点)垂直居中。如下所示的代码:

.vertical-and-center-block{
  display: flex;
  justify-content: center;
  align-items: center;
  width: 200px;
  height: 100px;
  background-color: antiquewhite;
}

image.png

实现的结果是这样,如上图。这行字作为匿名容器,被 vertical-and-center-block这一个div块包围。

综上,三号选手获胜✌️😎。

本例实用指数:🌟🌟🌟🌟🌟

案例三:

image.png

实现这种满幅的背景,内容定宽的效果,我们无需使用 margin:auto。 只需要两行代码就可以实现!

.block{
  padding: 1em calc(50% - 450px);
  background: #333;
}

当内边距是50%-450px时,只可能给内容留出900px(2×450px)的可用空间。所以我们就无需指定宽度。

本例实用指数:🌟🌟🌟🌟🌟

案例四:

实现header和footer高度固定,中间的内容(被main包裹)自适应。

有两种实现

  1. 使用calc实现
main {
  min-height: calc(100vh -2.5em -7em);
  /* 避免内边距或边框搞乱高度的计算:*/ 
  box-sizing:border-box; 
}

2.使用灵活的flex布局实现

body{
  display:flex;
  flex-flow:column;
  min-height:100vh;
}

main{flex:1; }

本例实用指数:🌟🌟🌟🌟🌟

案例五:

使用表格,对于不固定的内容来说,它们的布局是很难预测的。这是因为列宽根据其内容进行调整,即使我们显式地指定了width,也只能起到类似提示的作用。 为了解决这个问题,我们可以使用table-layout:fixed 在使用时,我们只需要对

元素或其他具有display: table样式的元素应用这个属性即可。请注意,为了确保这个技巧奏效,需要为这些表格元素指定一个宽度(哪怕是100%)。同样,为了让text-overflow:ellipsis发挥作用,我们还需要为那一列指定宽度。这就行了!

```
table{ table-layout:fixed; width:100%; }
```

表格应用了table-layout: fixed之后的效果。如果不指定任何宽度,则各列的宽度将是平均分配的;后续的表格行并不会影响列宽;给单元格指定很大的宽度也会直接生效,并不会自动缩小;overflow和text-overflow属性都是可以正常生效的;如果overflow的值是visible,则单元格的内容有可能会溢出。

本例实用指数:🌟🌟🌟

背景边框

如何实现如下图,背景透过白色内容块的边框展示出来?

image.png

.box{
  background-clip: padding-box;
  background-color: #fff;
  border: 20px solid rgba(255,255,255,.5);
 }

background-clip这个属性的初始值是border-box,意味着背景延伸至边框外沿。把它的值设为padding-box,背景延伸至内边距padding外沿。content-box背景被裁剪至内容区(content box)外沿。

如何给一个块加多层边框?

有两种方案,box-shadow和outline,使用box-shadow可以画多层边框,使用outline只能画一层边框。

 .box{
  background:yellowgreen;
  border: 10px solid yellow;
  box-shadow:0 0 0 10px #655 inset,
            0 0 0 15px deeppink inset,
            0 2px 5px 15px rgba(0,0,0,.6) inset;
    }

上述方法所创建出的假“边框”出现在元素的外圈。它们并不会响应鼠标事件,比如悬停或点击。如果这一点非常重要,你可以给box-shadow属性加上inset关键字,来使投影绘制在元素的内圈。

 .box{
      background:yellowgreen;
      border: 10px solid yellow;
      outline: 2px dashed blue;
      outline-offset: -20px;
    }

image.png

使用outline的好处是,你可以通过outline-offset属性来控制它跟元素边缘之间的间距,这个属性甚至可以接受负值,如上图所示。

本例实用指数:🌟🌟

如何让背景图片贴着内边距的右下角,使得背景图片距离边角的偏移量就跟内边距保持一致?

 .box{
  padding:10px;
  background:url("code-pirate.svg") no-repeat #58a
              bottom right; /* 或 100% 100% */
  background-origin:content-box;
}

很可能多次写过类似background-position:top left;这样的代码。你是否曾经有过疑惑:这个top left到底是哪个左上角?你可能知道,每个元素身上都存在三个矩形框.border box(边框的外沿框)、padding box(内边距的外沿框)和content box(内容区的外沿框)。那background-position这个属性指定的到底是哪个矩形框的左上角?默认情况下,background-position是以padding box为准的,这样边框才不会遮住背景图片。因此,top left默认指的是padding box的左上角。要实现目标,改成content-box(参见上面的代码),我们在background-position属性中使用的边角关键字将会以内容区的边缘作为基准。

本例实用指数:🌟🌟🌟

如何使用background画条纹?

   background:linear-gradient(#fb3 20%,#58a 80%); 

#fb3 20%是指这个颜色从div高度0-20%。#58a 80%,是指这个颜色从div高度80%-100%。所以,如果两个颜色都从20%开始,他们就会重合,没有渐变效果了,并且两个颜色占比为1:4。所以画条纹,将代码改为#fb3 50%,#58a 50%即可。按照规范,如果某个色标的位置值比整个列表中在它之前的色标的位置值都要小,则该色标的位置值会被设置为它前面所有色标位置值的最大值。这意味着,如果我们把第二个色标的位置值设置为0,那它的位置就总是会被浏览器调整为前一个色标的位置值。所以这样写也可,#fb3 50%,#58a 0

斜向条纹可以这样。使用repeating-linear-gradient

.box{
  padding:10px;
  height: 300px;
  background:#58a;
  background:repeating-linear-gradient(60deg,#fb3 0 15px,#58a 0 30px);
}

本例实用指数:🌟

技巧

  1. 假如我们给容器的四边指定相同的内边距,则实际效果看起来并不相等。原因在于,字母的形状在两端都比较整齐,而顶部和底部则往往参差不齐,从而导致你的眼睛把这些参差不齐的空缺部分感知为多出来的内边距。因此,如果我们希望四边的内边距看起来是基本一致的,就需要减少顶部和底部的内边距。

  2. 下面还有一些建议,可能会帮你避免不必要的媒体查询。
    ■ 使用百分比长度来取代固定长度。如果实在做不到这一点,也应该尝试使用与视口相关的单位(vw、vh、vmin和vmax),它们的值解析为视口宽度或高度的百分比。
    ■ 当你需要在较大分辨率下得到固定宽度时,使用max-width而不是width,因为它可以适应较小的分辨率,而无需使用媒体查询。
    ■ 不要忘记为替换元素(比如img、object、video、iframe等)设置一个max-width,值为100%。

  3. 你可能已经注意到下面背景属性简写的例子了:在background简写属性中指定background-size时,需要同时提供一个background-position值(哪怕它的值就是其初始值也需要写出来),而且还要使用一个斜杠(/)作为分隔。为什么有些简写的语法如此怪异?

  .box{
    background:url(tr.png) no-repeat top right /2em 2em,
    url(br.png) no-repeat bottom right /2em 2em,
    url(bl.png) no-repeat bottom left /2em 2em;
  }

这通常都是为了消除歧义。在这个例子中,top right显然是background-position,而2em 2em是background-size,不管它们的顺序如何。但是,请设想一下50% 50%这样的值,它到底是background-size还是background-position呢?当你在使用展开式属性时,CSS解析器明白你的意图;而当你使用简写属性时,解析器需要在没有属性名提示的情况下弄清楚50% 50%到底指什么。这就是需要引入斜杠的原因。

  1. 请不要忘记在calc()函数内部的-和+运算符的两侧各加一个空白符,否则会产生解析错误!这个规则如此怪异,是为了向前兼容

  2. background- size:cover;图片会拉伸。background-size:contain;图片不会变形, 可能会repeat或者 露出空白或设置的背景颜色。

视觉效果:投影等

box-shadow

/* x 偏移量 | y 偏移量 | 阴影模糊半径 | 阴影扩散半径 | 阴影颜色 */
box-shadow: 2px 3px 4px 1px rgba(0, 0, 0, 0.2);

绘制原理

(1) 以该元素相同的尺寸和位置,画一个rgba(0,0,0, .5)的矩形。

(2) 把它向右移2px,向下移3px。

(3) 使用高斯模糊算法(或类似算法)将它进行4px的模糊处理。这在本质上表示在阴影边缘发生阴影色和纯透明色之间的颜色过渡长度近似于模糊半径的两倍(比如在这里是8px)。

(4) 接下来,模糊后的矩形与原始元素的交集部分会被切除掉,因此它看起来像是在该元素的“后面”。这跟大多数开发者所理解的情况(元素叠在模糊后矩形的上层)可能稍有不同。 不过,在某些场景下,意识到没有任何投影绘制在元素的下层十分重要。举例来说,如果给元素设置一层半透明的背景,我们就看不到它下层有任何投影。这一点跟text-shadow不同,因为文字下层的投影不会被裁切。

这句话的意思是:text-shadow是两层文本重叠再偏移形成的,box-shadow并不是两层矩形重叠偏移,其投影是阉割过的!

单侧投影

使用box-shadow的第四个参数,扩张半径去扩大或缩小投影尺寸。

一个-5px的扩张半径会把投影的每边各减5px。所以投影的宽度和高度会各减少10px。

用扩张半径去抵消模糊半径就能出现单侧投影。需要自己去调试,找到一个最美观的单侧投影。

box-shadow: 0px 4px 5px -3px red;

image.png

双侧投影

双侧投影,我们只需要将单侧投影的技巧应用两遍。

    box-shadow:0px 5px 5px -5px,0px -5px 5px -5px;

image.png

染色效果

  1. 在图片上面覆盖半透明纯色。
  2. 使用css滤镜效果。 各种效果链接
  3. 混合模式。“混合模式”将上层元素的颜色与下层颜色进行混合。

有一件事情需要注意,滤镜是可动画的,而混合模式则不是。一张图片只需要在filter属性上设置好CSS过渡之后就可以从全彩样式慢慢淡化为单色样式,但你无法对混合模式做同样的事情。但是我们可以通过其它方式实现,当我们对一个背景图像以及一个透明背景色使用混合模式,将不会出现任何混合效果!

混合模式代码如下,你可以试一试!

.tinted-image{
    width:640px;height: 440px;
    background-size:cover;
    background-color:hsl(335,100%,50%);
    background-image: url(../image/img.png);
    background-blend-mode:luminosity;
    transition:.5s background-color;
}

.tinted-image:hover{
    background-color:transparent;
}

毛玻璃效果

书上的这个章节稍微有点晦涩,读了几遍才读懂。其实现的逻辑就是,只将文字下方的背景使用模糊函数。开始,你可能会想,给包含文本的块一张背景,然后使用filter:blur(20px),但是这样的话,文字会模糊的看不到。所以采用伪元素,用背景图片填充,对这个背景图片使用filter:blur(20px)即可。代码如下:

body{
	background: url(https://cdn.pixabay.com/photo/2017/04/12/19/59/nature-2225603_960_720.jpg) 0 /cover fixed;
}

.tinted-image{
	width: 500px;
	margin: 200px auto;
	filter: blur(.3);
	position: relative;
	padding: 20px;
	overflow: hidden;
	border-radius: .3em;
	box-shadow: 0 0 0 1px hsla(0,0%,100%,.3) inset,
	            0 .5em 1em rgba(0, 0, 0, 0.6);
	text-shadow: 0 1px 1px hsla(0,0%,100%,.3);
}

.tinted-image::before{
	position: absolute;
	 width: 500px;
	content: '';
	top: 0;
	bottom: 0;
	left: 0;
	right: 0;
	background: url(https://cdn.pixabay.com/photo/2017/04/12/19/59/nature-2225603_960_720.jpg) 0 /cover fixed;
	overflow: hidden;
	filter: blur(20px);
	z-index: -1;
}

用户体验

扩大可点击区域

  1. 加透明边框;但是透明边框有一个问题,如果有背景图片,背景图片会透过透明边框。在这时候再想加边框就只能通过加内嵌投影的方式了。
border:10px solid transparent;
box-shadow: 0 0 0 1px rgba(0,0,0,.3) inset;
background-clip:padding-box;
  1. 利用伪元素;我们可以在按钮上层覆盖一层透明伪元素,使伪元素在四个方向都比宿主大10px;
button{
	position:relative;
	/* [其余样式] */
}

button::before{
	content:'';
	position:absolute;
	top:-10px;right:-10px;
	bottom:-10px;left:-10px;
}

自定义复选框

本章其余不实用,但UI库中常见的技巧

  1. 模态框的蒙层
  2. 模态框后面的内容模糊效果
  3. 滚动提示,仿佛用js才能实现的效果
  4. 图片对比控件

字体排印

实现两端对齐时,往往会出现如下图这样的情况:

image.png

解决方案:

  1. 使用软连字符 &shy;
  2. 使用css属性
    hyphens:auto;

当hyphens属性和软连字符同时存在,优先处理软连字符。

文本折行的原理

与计算机科学中的很多事情类似,文本折行听起来简单易行,但实际上并非如此。这方面的算法有很多,最流行的方案主要是贪婪算法和Knuth-Plass算法。贪婪算法的工作原理是每次分析一行,把尽可能多的单词(当连字符可用时则以音节为单位)填充进该行;当遇到第一个装不下的单词或音节时,就移至下一行继续处理。Knuth-Plass算法(得名于开发它的工程师)的工作方式就要高级很多。它会把整段文本纳入考虑的范畴,从而产生出美学上更令人愉悦的结果,但其计算性能要明显差一些。绝大多数桌面文字处理程序采用Knuth-Plass算法。不过出于性能考虑,浏览器目前采用的是贪婪算法,因此它们的两端对齐效果往往不尽如人意。

插入换行

一个列表中要实现换行,固然可以使用<br/>,但也可以通过css。

    div{
    content:'\A',
    white-space:pre;
    }

还记得在HTML代码中输入换行符会发生什么吗?默认情况下,这些换行符会与相邻的其他空白符进行合并。空白符合并通常是一件非常好的事情,否则我们就得把整个HTML文档的源代码整理进一行里面!不过,有时候我们希望保留源代码中的这些空白符和换行,代码块就是最典型的例子。还记得我们在这种场景下通常会怎么做吗?我们会用到white-space: pre;

过渡和动画

缓动动画

要实现一个小球落下又弹起的动画,该如何实现? 初步要让这个小球动起来

@keyframes bounce{
    60%,80%,to{transform:translateY(350px); }
    70% {transform:translateY(250px); }
    90% {transform:translateY(300px); }
}

.ball{
    animation:bounce 3s;
    width: 100px;
    height: 100px;
    border-radius: 50%;
    background: red;
}

尝试这段代码后,你会发现小球的落下弹起效果都不真实,其运动规律应该是,落下时有一个加速度,对应下方调速函数ease-out。弹起时,有一个减速度,对应下方调速函数ease-in。

image.png

所以修改一下代码,就会发现这个动画真实多了。其实,除了这几种调速函数,CSS提供了一个cubic-bezier()函数,允许我们指定自定义的调速函数。我们可以借助https://cubic-bezier.com/自定义回弹动画

@keyframes bounce{
60%,80%,to{
	transform:translateY(400px);
	animation-timing-function:ease-out;
}
	70% {transform:translateY(300px); }
	90% {transform:translateY(360px); }
}

.ball{
	animation:bounce 3s ease-in;
}

闪烁效果

我们首先可能想到的就是把一个元素变透明。

@keyframes blink-smooth{
	to{ 
		color:transparent
	 }
} 
.highlight{
	animation:1s blink-smooth 3;
}

这样写,有个问题,元素会逐渐透明然后生硬的跳回原来的颜色。

如果我们希望文字颜色的变化不仅是平滑隐去的,同时还是平滑显现的。那我们就应该将透明状态切换放在循环的中间。

@keyframes blink-smooth{50% {color:transparent } }

.highlight{
animation:1s blink-smooth 3;
}

现在它似乎就是我们所期望的效果了。不过这里还有一个问题,虽然在这个特定的动画中表现得不是很明显(因为颜色或透明度的过渡很难体现出各种调速函数的特征),但我们心里一定要明白:这个动画一直是处在加速过程中的,不论是在文字显现还是隐去时——这对某些动画来说可能会显得不太自然(比如类似脉搏跳动的动画)。在那种情况下,我们可以从工具箱中请出另一件法宝:animation-direction。animation-direction的唯一作用就是反转每一个循环周期(reverse),或第偶数个循环周期(alternate),或第奇数个循环周期(alternate-reverse)。它的伟大之处在于,它会同时反转调整函数,从而产生更加逼真的动画效果。我们可以把它用在需要闪烁的元素上,比如:

@keyframes blink-smooth{to{color:transparent } }

.highlight{
    animation:.5s blink-smooth 6 alternate;
}

现在,我们有一个平滑的闪动动画。但是,如果,我们希望它生硬的闪烁应该怎么做呢? 直接告诉你答案吧

@keyframes blink{50% {color:transparent } }

.highlight{
animation:1s blink infinite steps(1); 
}  

steps(1)本质上等同于steps(1, end),它表示当前颜色与transparent之间的过渡会在一次步进中完成,所以我们应该调整动画的关键帧,让切换动作发生在50%处。

打字动画

@keyframes go {
    from {
        width:0
    }
}

@keyframes flashing {
    50% {
        border-color: transparent;
    }
}


h1{
    width: 15ch;
    overflow: hidden;
    white-space: nowrap;
    border-right: 2px solid red;
    animation: go 6s steps(15),flashing 1s steps(1) infinite;
}

其原理是:h1最开始的宽度为0,每一帧将h1的宽度增加一个字符的宽度,ch这个单位的含义是:一个字母的宽度。

然后对右边框应用闪动动画。

沿环形路径平移的动画

image.png

就像这样,几个球体在虚线上转动。

首先,我们来模拟一下小球沿着圆圈内边框转动。

image.png

代码如下:

    @keyframes spin{
	to{transform:rotate(1turn); }
}
.avatar{
	width: 50px;
	height: 50px;
	animation:spin 3s infinite linear;
	transform-origin:50% 150px; 
}

.path{
	width: 300px;
	height: 300px;
	background-color: antiquewhite;
	border-radius: 50%;
}

但问题在于,它不仅让头像沿着环形路径转动,同时还会让头像自身旋转。比如说,当头像转了半圈的时候,是头朝下的。如果有文字的话,那文字也会是颠倒的,这在可读性方面可是一个严重的问题。

如果希望,头像保持自己的朝向,那该怎么办呢?

给图片再套一个div,假设叫A,使A设置另一个旋转动画,让它以相反的方向自转一周。这两层旋转的作用会在头像上相互抵消,我们只会看到父元素旋转所产生的环绕动作!

代码如下:

@keyframes spin{
    to{transform:rotate(1turn); }
}

.path{
    width: 300px;
    height: 300px;
    background-color: antiquewhite;
    border-radius: 50%;
}


.avatar{
    animation:spin 3s infinite linear;
    transform-origin:50% 150px; 
}

.avatar>img{
    width: 50px;
    height: 50px;
    border-radius: 50%;
    animation:inherit;
    animation-direction:reverse;
}

形状

圆形/椭圆/半圆等

都是利用border-radius属性完成的。根据规范,若正方形边长为200,border-radius>=100时,都会生成一个圆形。 我们甚至可以为所有四个角提供完全不同的水平和垂直半径,方法是在斜杠前指定1~4个值,在斜杠后指定另外1~4个值。请注意这两组值是单独展开为四个值的。举例来说,当border-radius的值为10px / 5px 20px时,其效果相当于10px 10px 10px 10px / 5px 20px 5px 20px。 我们来实现一个例子你就明白了。

image.png

这个图形沿横轴对称,所以左上角和左下角的值应该是一样,右上角和右下角同理。 那么左上角的值应该是多少呢? 我们可以看到,顶部边缘并没有平直的部分(也就是说,整个顶边都是曲线),这意味着左上角和左下角的半径之和应该等于整个形状的高度。 所以,代码应该是这样

.box{
  background:#fb3;
  width:200px;
  height:100px;
  border-radius: 100px 0 0 100px/50px 0 0 50px; 
}

平行四边形

建议用伪元素生成平行四边形,因为如果不用的话,里面的文字也会变成斜体,实现的关键在于skew()函数,skew()  定义了一个元素在二维平面上的倾斜转换。具体代码如下:

.box{
  width:200px;
  height:100px;
  margin: 100px 200px;
  position:relative;
}

.box::before{
  content:''; /* 用伪元素来生成一个矩形 */
  position:absolute;
  top:0;right:0;bottom:0;left:0;
  z-index:-1;
  background:#58a;
  transform:skew(45deg);
}

菱形

实现这样的效果

image.png

clip-path 属性使用裁剪方式创建元素的可显示区域。区域内的部分显示,区域外的隐藏。用这个属性可以绘制各种各样的图形。

img {
	max-width: 250px;
	margin: 20px;
	-webkit-clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
	clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
	transition: 1s;
}

img:hover {
	-webkit-clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
	clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
}

书中还有绘制梯形和简单饼图的例子,因为平时几乎使用不到,可以通过书中章节去了解原理,在此不再赘述。