CSS 揭秘 阅读笔记:(Ch7-Ch8)
CSS 揭秘 阅读笔记:(Ch7-Ch8)
DRY Don’t Repact Youself
WET We Enjoy Typing or Write Everything Twice
Ch7 结构与布局
7.36 自适应内部元素
width 和height 属性定义了一个新的关键字:min-content 。这个关键字将解析为这个容器内部最大的不可断行元素的宽度(即最宽的单词、图片或具有固定宽度的盒元素)。
7.37 精确控制表格列宽
table-layout。它的默认值是auto,其行为模式被称作自动表格布局算法。它还接受另外一个值fixed,这个值的行为要明显可控一些。这种固定表格布局算法不仅更容易预测、便于使用,同时也明显更快。因为表格的内容并不会影响单元格的宽度,所以在页面的下载过程中,表格不需要频繁重绘。
表格应用了table-layout: fixed;
之后的效果。按从上到下的顺序总结为:如果不指定任何宽度,则各列的宽度将是平均分配的;后续的表格行并不会影响列宽;给单元格指定很大的宽度也会直接生效,并不会自动缩小;overflow 和text-overflow 属性都是可以正常生效的;如果overflow 的值是visible,则单元格的内容有可能会溢出。
7.38 根据兄弟元素的数量来设置样式
:nth-child() 选择符命中一个范围。它的参数不仅可以是简单的数字,也可以是像 a n + b an+b an+b 这样的表达式(比如:nth-child(2n+1))。这里的n 表示一个变量,理论上的范围是0 到 + ∞ + ∞ +∞ 。 n + b n+b n+b 这种形式的表达式可以选中从第b 个开始的所有子元素。同理, − n + b -n+b −n+b 这种形式的表达式可以选中开头的b 个元素。
7.39 满幅的背景,定宽的内容
为每个区块准备两层元素:外层用来实现满幅的背景,内层用来实现定宽的内容。后者是通过margin: auto 实现水平居中的。
<footer>
<div class="wrapper">
<!-- 页脚的内容写在这里 -->
</div>
</footer>
footer {
background: #333;
}
.wrapper {
max-width: 900px;
margin: 1em auto;
}
用calc() 取代原先的auto:
.wrapper {
max-width: 900px;
margin: 1em calc(50% - 450px);
}
可以把这个长度值应用到父元素的padding 上,把width 这一行声明注释掉。
footer {
padding: 1em;
padding: 1em calc(50% - 450px);
background: #333;
}
.wrapper {}
7.40 垂直居中
元素具有固定的宽度和高度
先把这个元素的左上角放置在视口(或最近的、具有定位属性的祖先元素)的正中心,然后再利用负外边距把它向左、向上移动(移动距离相当于它自身宽高的一半),从而把元素的正中心放置在视口的正中心。这个方法最大的局限在于它要求元素的宽高是固定的。
position: absolute;
top: calc(50% - 3em);
left: calc(50% - 9em);
width: 18em;
height: 6em;
CSS 变形属性
translate() 变形函数中使用百分比值,是以这个元素自身的宽度和高度为基准进行换算和移动的。只要换用基于百分比的CSS 变形来对元素进行偏移,就不需要在偏移量中把元素的尺寸写死了。
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
在某些浏览器中,这个方法可能会导致元素的显示有一些模糊,因为元素可能被放置在半个像素上。
基于视口单位的解决方案
margin 的百分比值是以父元素的宽度作为解析基准的。
把元素相对于视口进行居中,可以用视口相关的长度单位。
vw 是与视口宽度相关的。与常人的直觉不符的是,1vw 实际上表示视口宽度的1%,而不是100%。
与 vw类似,1vh 表示视口高度的 1%。
当视口宽度小于高度时,1vmin 等于 1vw,否则等于 1vh。
当视口宽度大于高度时,1vmax 等于 1vw,否则等于 1vh。
margin: 50vh auto 0;
transform: translateY(-50%);
基于Flexbox 的解决方案
先给这个待居中元素的父元素设置display: flex,再给这个元素自身设置我们再熟悉不过的margin: auto。
body {
display: flex;
min-height: 100vh;
margin: 0;
}
main {
margin: auto;
}
当我们使用Flexbox 时,margin: auto 不仅在水平方向上将元素居中,垂直方向上也是如此。
它还可以将匿名容器(即没有被标签包裹的文本节点)垂直居中。
main {
display: flex;
align-items: center;
justify-content: center;
width: 18em;
height: 10em;
}
7.41 紧贴底部的页脚
页头和页脚的高度由其内部因素来决定,而内容区块的高度应该可以自动伸展并占满所有的可用空间。我们只要给main 这个容器的flex 属性指定一个大于0 的值(比如1 即可),就可以实现这个效果了。
这个flex 属性实际上是flex-grow、flex-shrink 和flex-basis 的简写语法。任何元素只要设置了一个大于0 的flex 值,就将获得可伸缩的特性;flex 的值负责控制多个可伸缩元素之间的尺寸分配比例。
body {
display: flex;
flex-flow: column;
min-height: 100vh;
}
main { flex: 1; }
Ch8 过渡与动画
8.42 缓动效果
弹跳动画
所有过渡和动画都是跟一条曲线有关的,这条曲线(缓动曲线)指定了动画过程在整段时间中是如何推进的。如果不指定调速函数,它就会得到一个默认值。这个默认的缓动效果并不是我们想像中的匀速效果,而是当时间进行到一半时,这个过渡已经推进到80% 了!
ease-out 是ease-in 是反向版本。这一对组合正好是实现回弹效果所需要的:每当小球的运动方向相反时,调速函数也是相反的。在animation 属性中指定一个通用的调速函数,然后在关键帧中按需覆盖它。下落方向上的调速函数是加速的(easeout),而弹起方向上是减速的(ease-in)。
@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;
}
CSS 的调速函数都是只有一个片断的贝塞尔曲线,因此每个调速函数只有两个控制锚点。CSS 提供了一个cubic-bezier() 函数,允许我们指定自定义的调速函数。它接受四个参数,分别代表两个控制锚点的坐标值,我们通过这两个控制锚点来指定想要的贝塞尔曲线。cubic-bezier(x1, y1, x2, y2),其中(x1, y1) 表示第一个控制锚点的坐标,而(x2, y2) 是第二个。曲线只能有两个节点,两个控制锚点的x 值都被限制在[0, 1] 区间内。只要我们把控制锚点的水平坐标和垂直坐标互换,就可以得到任何调速函数的反向版本。
@keyframes bounce {
60%, 80%, to {
transform: translateY(400px);
animation-timing-function: ease;
}
70% { transform: translateY(300px); }
90% { transform: translateY(360px); }
}
.ball {
/* 外观样式 */
animation: bounce 3s cubic-bezier(.1,.25,1,.25);
}
弹性过渡
假设有一个文本输入框,每当它被聚焦时,都需要展示一个提示框。
<label>
Your username: <input id="username" />
<span class="callout">Only letters, numbers,
underscores (_) and hyphens (-) allowed!</span>
</label>
input {
display: block;
padding: 0 .4em;
font: inherit;
}
.callout {
position: absolute;
max-width: 240px;
padding: .6em .8em;
border-radius: .3em;
margin: .3em 0 0 -.2em;
background: #fed;
border: 1px solid rgba(0, 0, 0, .3);
box-shadow: .05em .2em .6em rgba(0, 0, 0, .2);
font-size: 75%;
}
.callout:before {
content: "";
position: absolute;
top: -.4em; left: 1em;
padding: .35em;
background: inherit;
border: inherit;
border-right: 0; border-bottom: 0;
transform: rotate(45deg);
}
触发这个提示框(.callout)所需要的CSS 代码如下所示:
input:not(:focus) + .callout {
transform: scale(0);
}
.callout {
transition: .5s transform;
transform-origin: 1.4em -.4em;
}
如果它在结尾时能再夸张一点的话,会显得更加自然和生动(比如说,先扩大到110% 的尺寸,然后再缩回100%),因此希望调速函数可以先达到110% 的程度(相当于scale(1.1)),然后再过渡回100%。只想给提示框的关闭过程指定普通的ease 调速函数,那么可以在定义关闭状态的CSS 规则中把当前的调速函数覆盖掉。提示框的关闭动作明显要迟钝一些。我们既可以单独覆盖transition-duration 这一个属性,也可以用transition 这个简写属性来覆盖所有的值。选择后者的话,就不需要显式指定ease 了。为避免不小心对颜色设置了弹性过渡,可以尝试把过渡的作用范围限制在某几种特定的属性上,transition-property 就会得到它的初始值:all。这意味着只要是可以过渡的属性,都会参与过渡。
input:not(:focus) + .callout {
transform: scale(0);
transition: .25s transform;
}
.callout {
transform-origin: 1.4em -.4em;
transition: .5s cubic-bezier(.25,.1,.3,1.5) transform;
}
8.43 逐帧动画
steps() 会根据你指定的步进数量,把整个动画切分为多帧,而且整个动画会在帧与帧之间硬切,不会做任何插值处理。
@keyframes loader { to { background-position: -800px 0; } }
.loader {
width: 100px; height: 100px;
background: url(img/loader.png) 0 0;
animation: loader 1s infinite steps(8);
/* 把文本隐藏起来 */
text-indent: 200%;
white-space: nowrap;
overflow: hidden;
}
steps() 还接受可选的第二个参数,其值可以是start 或end(默认值)。这个参数用于指定动画在每个循环周期的什么位置发生帧的切换动作。
8.44 闪烁效果
平滑闪烁
animation-direction 的唯一作用就是反转每一个循环周期(reverse),或第偶数个循环周期(alternate),或第奇数个循环周期(alternate-reverse)。它会同时反转调整函数,从而产生更加逼真的动画效果。请注意,必须把动画循环的次数翻倍,因为现在一次淡入淡出的过程是由两个循环周期组成的。基于同样的原因,也要把animation-duration 减半。
@keyframes blink-smooth { to { color: transparent } }
.highlight {
animation: .5s blink-smooth 6 alternate;
}
硬切闪烁
由于无法通过配置steps() 来让切换动作发生在动画周期的中间点(只能发生在起点或终点),唯一的解决方案是调整动画的关键帧,让切换动作发生在50% 处。
@keyframes blink { 50% { color: transparent } }
.highlight {
animation: 1s blink 3 steps(1); /* 或用step-end */
}
8.45 打字动画
核心思路就是让容器的宽度成为动画的主体:把所有文本包裹在这个容器中,然后让它的宽度从0 开始以步进动画的方式、一个字一个字地扩张到它应有的宽度。这个方法是有局限的:它并不适用于多行文本。另外一件需要注意的事情是,动画的持续时间越长,动画效果越差。
<h1>CSS is awesome!</h1>
用steps() 来修复整个动画是平滑连贯的,而不是逐字显现的。
通过ch 单位来缓解像素单位。ch 单位,表示“0”字形的宽度。在等宽字体中,“0”字形的宽度和其他所有字形的宽度是一样的。用ch 单位来表达标题的宽度,那取值实际上就是字符的数量。
@keyframes typing { from { width: 0; } }
h1 {
width: 15ch; /* 文本的宽度 */
overflow: hidden;
white-space: nowrap;
animation: typing 6s steps(15);
}
8.46 状态平滑的动画
在默认情况下只显示这张照片的左边缘,当用户跟它交互(比如鼠标悬停)的时候,让它滚动并显露出剩余的部分。
当我们把鼠标悬停到图片上时,它会从图片的最左侧区域开始,向右慢慢滚动,并逐渐显示出图片的右侧区域。不过,当我们把鼠标移出图片时,它就会生硬地跳回最左侧。有一个属性是为暂停动画的需求专门设计的:animation-play-state。
因此,我们需要把动画加在条样式中,但是让它一开始就处于暂停状态,直到:hover 时再启动动画。这再也不是添加和取消动画的问题了,而只是暂停和继续一个一直存在的动画,因此再也不会有生硬的跳回现象了。
@keyframes panoramic { to { background-position: 100% 0; } }
.panoramic {
width: 150px; height: 150px;
background: url("img/naxos-greece.jpg");
background-size: auto 100%;
animation: panoramic 10s linear infinite alternate;
animation-play-state: paused;
}
.panoramic:hover, .panoramic:focus {
animation-play-state: running;
}
8.47 沿环形路径平移的动画
希望它只是沿着环形进行移动,同时保持自己本来的朝向。
两个元素的解决方案
用内层的变形来抵消外层的变形效果,这次的抵消作用是贯穿于整个动画的每一帧的。
<div class="path">
<div class="avatar">
<img src="lea.jpg" />
</div>
</div>
对头像元素设置另一个旋转动画,让它以相反的方向自转一周,这两层旋转的作用会在头像上相互抵消,我们只会看到父元素旋转所产生的环绕动作!让内层动画从父元素那里继承所有的动画属性,然后用animation-direction: reverse;
,它可以得到原始动画的反向版本。
@keyframes spin { to { transform: rotate(1turn); } }
.avatar {
animation: spin 3s infinite linear;
transform-origin: 50% 150px; /* 150px = 路径的半径 */
}
.avatar > img {
animation: inherit;
animation-direction: reverse;
}
单个元素的解决方案:
为同一个元素指定多个变形原点。每个transform-origin 都是可以被两个translate() 模拟出来的。比如,下面两段代码实际上是等效的:
transform: rotate(30deg);
transform-origin: 200px 300px;
transform: translate(200px, 300px) rotate(30deg) translate(-200px, -300px);
transform-origin: 0 0;
变形函数并不是彼此独立的。每个变形函数并不是只对这个元素进行变形,而且会把整个元素的坐标系统进行变形,从而影响所有后续的变形操作。这也说明了为什么变形函数的顺序是很重要的,变形属性中不同函数的顺序如果被打乱,可能会产生完全不同的结果。
<div class="path">
<img src="lea.jpg" class="avatar" />
</div>
借助这个思路,我们就可以基于同一个transform-origin 来实现前面用到的两个旋转动画(我们会再次把动画分成两套,因为现在它们的关键帧已经完全不一样了):
@keyframes spin {
from {
transform: translate(50%, 150px) rotate(0turn) translate(-50%, -150px);
}
to {
transform: translate(50%, 150px) rotate(1turn) translate(-50%, -150px);
}
}
@keyframes spin-reverse {
from {
transform: translate(50%,50%) rotate(1turn) translate(-50%,-50%);
}
to {
transform: translate(50%,50%) rotate(0turn) translate(-50%, -50%);
}
}
.avatar {
animation: spin 3s infinite linear;
}
.avatar > img {
animation: inherit;
animation-name: spin-reverse;
}
现在已经不需要不同的变形原点了,而这正是先前需要动用两个元素(和两套动画)的唯一理由。由于现在所有变形函数所使用的都是相同的原点,可以把这两套动画合并为一套。
@keyframes spin {
from {
transform:
translate(50%, 150px) rotate(0turn) translate(-50%, -150px)
translate(50%,50%) rotate(1turn) translate(-50%,-50%)
}
to {
transform:
translate(50%, 150px) rotate(1turn) translate(-50%, -150px)
translate(50%,50%) rotate(0turn) translate(-50%, -50%);
}
}
.avatar { animation: spin 3s infinite linear; }
把连续的translate() 变形操作合并起来,单纯水平方向上的位移还是可以相互抵消的。由于同一关键帧内的两次旋转也会相互抵消,还可以把旋转之前和之后的水平位移动作去掉,再把垂直位移合并起来。
@keyframes spin {
from {
transform: translateY(150px) translateY(-50%)
rotate(0turn)
translateY(-150px) translateY(50%)
rotate(1turn);
}
to {
transform: translateY(150px) translateY(-50%)
rotate(1turn)
translateY(-150px) translateY(50%)
rotate(0turn);
}
}
.avatar { animation: spin 3s infinite linear; }
把头像放在圆心并以此作为起点,我们就可以消除最开始的那两个位移操作了,而实际上这两个位移在本质上所做的就是把它放在圆心。
@keyframes spin {
from {
transform: rotate(0turn) translateY(calc(50% - 150px)) rotate(1turn);
}
to {
transform: rotate(1turn) translateY(calc(50% - 150px)) rotate(0turn);
}
}
.avatar { animation: spin 3s infinite linear; }
这似乎已经是我们在当下所能做到的最优结果了。代码还没有彻底满足DRY 的要求,但已经相当简短了。我们已经尽可能减小了代码的重复度,而且除去了冗余的HTML 元素。