CSS 揭秘 阅读笔记:(Ch5-Ch6)
CSS 揭秘 阅读笔记:(Ch5-Ch6)
DRY Don’t Repact Youself
WET We Enjoy Typing or Write Everything Twice
Ch5 字体排印
5.20 连字符断行
hyphens: none | manual | auto
manual 是它的初始值,我们可以在任何时候手工插入软连字符,来实现断词折行的效果。
hyphens: auto;
仅此一行足矣。当然,为了确保它奏效,你需要在HTML 标签的lang 属性中指定合适的语言。
5.21 插入换行
有一个Unicode 字符是专门代表换行符的:0x000A。在CSS 中,这个字符可以写作"\000A",或简化"\A"。在HTML 代码中输入换行符,这些换行符会与相邻的其他空白符进行合并。有时候我们希望保留源代码中的这些空白符和换行,用到white-space: pre;。
<dl>
<dt>Name:</dt>
<dd>Lea Verou</dd>
<dt>Email:</dt>
<dd>lea@verou.me</dd>
<dd>leaverou@mit.edu</dd>
<dt>Location:</dt>
<dd>Earth</dd>
</dl>
在理想情况下,我们只想针对dt 之前的最后一个dd 来插入换行,一个想法就是换行符不用加在dd 的后面,而是加在dt 的前面。在理想情况下,我们希望能够告诉浏览器“只在后面还跟着一个dd 的dd 尾部插入逗号”,但我们又一次遇上了那个限制,眼下的CSS 选择符还表达不出这种需求。因此,我们再次调整思路,在每个前面有dd 的dd 头部插入逗号。
dt, dd { display: inline; }
dd {
margin: 0;
font-weight: bold;
}
dd + dt::before {
content: '\A';
white-space: pre;
}
dd + dd::before {
content: ', ';
margin-left: -.25em;
font-weight: normal;
}
5.22 文本行的斑马条纹
对整个元素设置统一的背景图像,一次性加上所有的斑马条纹。它的background-size 需要设置为line-height 的两倍,因为每个背景贴片需要覆盖两行代码。让背景自动跟着内边距的宽度走,background-origin 可以告诉浏览器在解析background-position 时以content box 的外沿作为基准,而不是默认的padding box 外沿。
padding: .5em;
line-height: 1.5;
background: beige;
background-size: auto 3em;
background-origin: content-box;
background-image: linear-gradient(rgba(0,0,0,.2) 50%, transparent 0);
5.23 调整tab 的宽度
CSS 属性tab-size 可以控制缩进,这个属性接受一个数字(表示字符数)或者一个长度值。
tab-size: 2;
5.24 连字
就像人与人一样,字形(glyph)与字形也不都是和睦相处的。为了缓解这个问题,字体设计师通常会在字体中包含一些额外的字形,称作连字(ligature)。这些字形被设计为双字形或三字形的单一组合体,专门提供给排版软件使用,代为显示特定的字符组合。
在CSS 字体(第三版)中,原有的font-variant 被升级成了一个简写属性,由很多新的展开式属性组合而成。其中之一叫作font-variant-ligatures,专门用来控制连字效果的开启和关闭。如果要启用所有可能的连字,需要同时指定这三个标识符:
font-variant-ligatures: common-ligatures
discretionary-ligatures
historical-ligatures;
如果要把font-variant-ligatures 属性复位为初始值,应该使用normal 而不是none。
5.25 华丽的& 符号
&:& amp;
unicode-range 描述符只在@font-face 规则内部生效,它可以把字体作用的字符范围限制在一个子集内。它对本地字体和远程字体都是有效的。unicode-range 在实践中非常实用,它的语法是基于“Unicode 码位”的,而不是基于字符的字面形态。
"&".charCodeAt(0).toString(16); //查出指定字符的十六进制码位
5.26 自定义下划线
text-decoration 属性规定添加到文本的修饰,下划线、上划线、删除线等。
text-decoration: none; /*没有文本装饰*/
text-decoration: underline red; /*红色下划线*/
text-decoration: underline wavy red; /*红色波浪形下划线*/
text-decoration-style: solid | double | dotted | dashed | wavy | initial | inherit;
背景可以完美地跟随换行的文本,而且借助CSS 背景与边框(第三版)中与背景相关的新属性,我们已经拥有了细粒度控制背景的能力。我们甚至不需要用到额外的HTTP 请求来加载背景图片,因为可以通过CSS 渐变来凭空生成所需的图像。
请注意下划线会穿过某些字母(比如p 和y)的降部。下划线在遇到字母时会自动断开避让,假如背景是一片实色,就可以设置两层与背景色相同的text-shadow 来模拟这种效果。
background: linear-gradient(gray, gray) no-repeat;
background-size: 100% 1px;
background-position: 0 1.15em;
text-shadow: .05em 0 white, -.05em 0 white;
使用渐变来实现下划线的高明之处在于,这些线条极为灵活。可以通过色标的百分比位置值来微调虚线的虚实比例,还可以通过background-size 来改变虚线的疏密。
background: linear-gradient(90deg, gray 66%, transparent 0) repeat-x;
background-size: .2em 2px;
background-position: 0 1em;
5.27 现实中的文字效果
text-shadow 属性应用于阴影文本。
text-shadow: h-shadow v-shadow blur color;
值 | 描述 |
---|---|
h-shadow | 必需。水平阴影的位置。允许负值。 |
v-shadow | 必需。垂直阴影的位置。允许负值。 |
blur | 可选。模糊的距离。 |
color | 可选。阴影的颜色。参阅 CSS 颜色值。 |
凸版印刷效果
出现在底部的浅色投影(或者出现在顶部的暗色投影)会让人产生物体是凹进平面内的错觉。出现在底部的暗色投影(或者出现在顶部的浅色投影)会让人产生物体从平面上凸起的错觉。
在浅色背景上使用深色文字时,在底部加上浅色投影通常效果最佳;
background: hsl(210, 13%, 60%);
color: hsl(210, 13%, 30%);
text-shadow: 0 1px 1px hsla(0,0%,100%,.8);
在深色底、浅色文字的情况下,给文字顶部加深色投影是最佳方案。
background: hsl(210, 13%, 40%);
color: hsl(210, 13%, 75%);
text-shadow: 0 -1px 1px black;
空心字效果
使用多个text-shadow,分别为这些投影加上不同方向的少量偏移;
background: deeppink;
color: white;
text-shadow: 1px 1px black, -1px -1px black,
1px -1px black, -1px 1px black;
还可以重叠多层轻微模糊的投影来模拟描边。
text-shadow: 0 0 1px black, 0 0 1px black,
0 0 1px black, 0 0 1px black,
0 0 1px black, 0 0 1px black;
文字外发光效果
准备几层重叠的text-shadow 即可,不需要考虑偏移量,颜色也只需跟文字保持一致。
background: #203;
color: white;
text-shadow: 0 0 .1em, 0 0 .3em;
也可以换种思路,使用CSS 滤镜来实现文字的模糊效果。
background: #203;
color: white;
filter: blur(.1em);
文字凸起效果
使用一长串累加的投影,不设模糊并以1px 的跨度逐渐错开,使颜色逐渐变暗,然后在底部加一层强烈模糊的暗投影,从而模拟完整的立体效果。
background: #58a;
color: white;
text-shadow: 0 1px hsl(0,0%,85%), 2px hsl(0,0%,80%),
0 3px hsl(0,0%,75%), 4px hsl(0,0%,70%),
0 5px hsl(0,0%,65%),
0 5px 10px black;
这种效果还有很多变种。比如把所有的投影都设成黑色,并且去掉最底层的投影,就可以模拟出一种在复古标志牌中常见的文字效果。
color: white;
background: hsl(0,50%,45%);
text-shadow: 1px 1px black, 2px 2px black,
3px 3px black, 4px 4px black,
5px 5px black, 6px 6px black,
7px 7px black, 8px 8px black;
5.28 环形文字
把每个字母包裹在独立的span 元素之中,然后把各个字母分别旋转,从而构成一个环形。这种方式不仅有很浓的hack 味道,而且还在没有正当理由的情况下给页面增加了臃肿的脚本和冗余的DOM 元素。
目前还没有更好的纯CSS 实现方法,但我们其实可以借助一点内联SVG 来轻松解决这个难题。
<div class="circular">
<svg viewBox="0 0 100 100">
<path d="M 0,50 a 50,50 0 1,1 0,1 z" id="circle" style="fill: none;" />
<text>
<textPath xlink:href="#circle">circular reasoning works because</textPath>
</text>
</svg>
</div>
.circular {
width: 30em;
height: 30em;
margin: 3em auto 0;
}
.circular svg {
display: block;
overflow: visible;
}
Ch6 用户体验
6.29 选用合适的鼠标光标
cursor属性定义了鼠标指针放在一个元素边界范围内时所用的光标形状
值 | 描述 |
---|---|
url | 需使用的自定义光标的 URL。 |
default | 默认光标(通常是一个箭头) |
auto | 默认。浏览器设置的光标。 |
crosshair | 光标呈现为十字线。 |
pointer | 光标呈现为指示链接的指针(一只手) |
move | 此光标指示某对象可被移动。 |
e-resize | 此光标指示矩形框的边缘可被向右(东)移动。 |
ne-resize | 此光标指示矩形框的边缘可被向上及向右移动(北/东)。 |
nw-resize | 此光标指示矩形框的边缘可被向上及向左移动(北/西)。 |
n-resize | 此光标指示矩形框的边缘可被向上(北)移动。 |
se-resize | 此光标指示矩形框的边缘可被向下及向右移动(南/东)。 |
sw-resize | 此光标指示矩形框的边缘可被向下及向左移动(南/西)。 |
s-resize | 此光标指示矩形框的边缘可被向下移动(北/西)。 |
w-resize | 此光标指示矩形框的边缘可被向左移动(西)。 |
text | 此光标指示文本。 |
wait | 此光标指示程序正忙(通常是一只表或沙漏)。 |
help | 此光标指示可用的帮助(通常是一个问号或一个气球)。 |
6.30 扩大可点击区域
人类移动到某个目标区域所需的最短时间是由目标距离与目标宽度之比所构成的对数函数。
T
=
a
+
b
log
2
(
1
+
D
W
)
T=a+b \log_{2} (1 + \frac{D}{W} )
T=a+blog2(1+WD)
T 表示所需时间,D 是从起点到目标中心的距离,W 是目标区域的宽度,而a 和b 都是常数。
目标越大,越容易到达。因此,对于那些较小的、难以瞄准的控件来说,如果不能把它的视觉尺寸直接放大,将其可点击区域(热区)向外扩张往往也可以带来可用性的提升。没有人愿意对一个狭小的按钮尝试点按很多次。
扩张热区最简单的办法是为它设置一圈透明边框,因为鼠标对元素边框的交互也会触发鼠标事件,这一点是描边和投影所不及的。背景在默认情况下会蔓延到边框的下层,用background-clip 属性可以把背景限制在原本的区域之内。给按钮加上真正的边框效果时,可以用内嵌投影来模拟出一道(实色)边框。
border: 10px solid transparent;
box-shadow: 0 0 0 1px rgba(0,0,0,.3) inset;
background-clip: padding-box;
6.31 自定义复选框
我们倒是可以基于复选框的勾选状态借助组合选择符来给其他元素设置样式。当label 元素与复选框关联之后,也可以起到触发开关的作用。
由于label 不是复选框那样的替换元素,我们可以为它添加生成性内容(伪元素),并基于复选框的状态来为其设置样式。然后,就可以把真正的复选框隐藏起来(但不能把它从tab 键切换焦点的队列中完全删除),再把生成性内容美化一番,用来顶替原来的复选框!
<input type="checkbox" id="awesome" />
<label for="awesome">Awesome!</label>
input[type="checkbox"] + label::before {
content: '\a0'; /* 不换行空格 */
display: inline-block;
vertical-align: .2em;
width: .8em;
height: .8em;
margin-right: .2em;
border-radius: .2em;
background: silver;
text-indent: .15em;
line-height: .65;
}
input[type="checkbox"]:checked + label::before {
content: '\2713';
background: yellowgreen;
}
把原来的复选框以一种不损失可访问性的方式隐藏起来。这意味着不能使用display: none,因为那样会把它从键盘tab 键切换焦点的队列中完全删除。
input[type="checkbox"] {
position: absolute;
clip: rect(0,0,0,0);
}
这就完成了,我们得到了一个简单定制化的复选框!我们还可以进一步优化,比如在它聚焦或禁用时改变它的样式:
input[type="checkbox"]:focus + label::before {
box-shadow: 0 0 .1em .1em #58a;
}
input[type="checkbox"]:disabled + label::before {
background: gray;
box-shadow: none;
color: #555;
}
你甚至可以用过渡或动画来让各个状态之间的切换更加平滑,或者脑子一热创建一个拟物化的开关。
开关式按钮
只需要把label 设置为按钮的样式即可,并不需要用到伪元素。
input[type="checkbox"] {
position: absolute;
clip: rect(0,0,0,0);
}
input[type="checkbox"] + label {
display: inline-block;
padding: .3em .5em;
background: #ccc;
background-image: linear-gradient(#ddd, #bbb);
border: 1px solid rgba(0,0,0,.2);
border-radius: .3em;
box-shadow: 0 1px white inset;
text-align: center;
text-shadow: 0 1px 1px white;
}
input[type="checkbox"]:checked + label,
input[type="checkbox"]:active + label {
box-shadow: .05em .1em .2em rgba(0,0,0,.6) inset;
border-color: rgba(0,0,0,.3);
background: #bbb;
}
6.32 通过阴影来弱化背景
弹出层,最常见的实现方法就是增加一个额外的HTML 元素用于遮挡背景。遮罩层负责把这个关键元素背后的所有东西调暗,弹出层需要指定一个更高的z-index,以便绘制在遮罩层的上层。这个方法稳定可
靠,但需要增加一个额外的HTML 元素,这意味着该效果无法由CSS 单独实现。
伪元素方案
把遮罩层交给这个元素自己的::before 伪元素来实现。
body.dimmed::before {
position: fixed;
top: 0; bottom: 0; left: 0; right: 0;
z-index: 1;
background: rgba(0,0,0,.8);
}
给伪元素设置z-index: -1; 就可以让它出现在元素的背后。它可能会出现在这个元素之后(这是我们期望的),但也可能会出现在这个元素的父元素或祖先元素之后。伪元素无法绑定独立的JavaScript 事件处理函数。
box-shadow 方案
利用box-shadow 来达到调暗背景的效果:box-shadow 的扩张参数可以把元素的投影向各个方向延伸放大。具体做法就是生成一个巨大的投影,不偏移也不模糊,简单而拙劣地模拟出遮罩层的效果:
box-shadow: 0 0 0 999px rgba(0,0,0,.8);
换用视口单位,只有这样才能确保“遮罩层”总是可以覆盖(甚至超出)视口。1vmax 相当于1vw 和1vh 两者中的较大值。100vw 等于整个视口的宽度,100vh 就是视口的高度。因此,满足我们需求的最小值就是50vmax。
box-shadow: 0 0 0 50vmax rgba(0,0,0,.8);
但它存在两个非常严重的问题。
第一,由于遮罩层的尺寸是与视口相关,而不是与页面相关的,当我们滚动页面时,遮罩层的边缘就露出来了,除非给它加上position: fixed; 这个样式,或者页面并没有长到需要滚动的程度。
第二,当使用一个独立的元素(或伪元素)来实现遮罩层时,这个遮罩层不仅可以从视觉上把用户的注意力引导到关键元素上,还可以防止用户的鼠标与页面的其他部分发生交互,因为遮罩层会捕获所有指针事件。boxshadow 并没有这种能力,因此它只能在视觉上起到引导注意力的作用,却无法阻止鼠标交互。
6.33 通过模糊来弱化背景
动用一个额外的HTML 元素来实现这个效果:需要把页面上除了关键元素之外的一切都包裹起来,这样就可以只对这个容器元素进行模糊处理了。
filter: blur(5px);
6.34 滚动提示
当侧边栏的容器还有更多内容时,一层淡淡的阴影会出现在容器的顶部和/ 或底部。
<ul>
<li>Ada Catlace</li>
<li>Alan Purring</li>
<li>Schrödingcat</li>
<li>Tim Purrners-Lee</li>
<li>WebKitty</li>
<li>Json</li>
<li>Void</li>
<li>Neko</li>
<li>NaN</li>
<li>Cat5</li>
<li>Vector</li>
</ul>
overflow: auto;
width: 10em;
height: 8em;
padding: .3em .5em;
border: 1px solid silver;
用一个径向渐变在顶部添加一条阴影。当我们滚动列表时,这条阴影会一直停留在相同的位置。
background: radial-gradient(at top, rgba(0,0,0,.2), transparent 70%) no-repeat;
background-size: 100% 15px;
我们需要两层背景:一层用来生成那条阴影,另一层基本上就是一个用来遮挡阴影的白色矩形,其作用类似于遮罩层。生成阴影的那层背景将具有默认的background-attachment 值(scroll),因为我们希望它总是保持在原位。我们把遮罩背景的background-attachment 属性设置为local,这样它就会在我们滚动到最顶部时盖住阴影,在向下滚动时跟着滚动,从而露出阴影。
“遮罩层”是一层(逐渐淡化的)线性渐变,只要把它修改为一段从white 到透明白(hsla(0,0%,100%,0) 或rgba(255,255,255,0) 的真正的渐变图案,就可以让阴影的显现过程变得平滑。
当我们滚动到最顶部的时候,这个“遮罩层”再也无法完整地遮住阴影了。只要把white 色标向下移动一点(与阴影的高度相等),就可以在淡化区域之前插入一段白色的实色区域。此外,我们还需要把遮罩层的尺寸增大,超过阴影的尺寸,否则这个遮罩层就没有由深到浅的渐变区域了。
background:
linear-gradient(white 30%, transparent),
radial-gradient(at 50% 0, rgba(0,0,0,.2), transparent 70%);
background-repeat: no-repeat;
background-size: 100% 50px, 100% 15px;
background-attachment: local, scroll;
为了完整地实现这个效果,我们还需要再用两层渐变来实现底部的阴影和它配套的遮罩,但逻辑是完全一致的。
background:
linear-gradient(white 30%, transparent),
radial-gradient(at 50% 0, rgba(0, 0, 0, .2), transparent 70%),
linear-gradient(transparent, white 70%) 0 100%,
radial-gradient(at 50% 100%, rgba(0, 0, 0, .2), transparent 70%) 0 100%;
background-repeat: no-repeat;
background-size: 100% 50px, 100% 15px, 100% 50px, 100% 15px;
background-attachment: local, scroll, local, scroll;
6.35 交互式的图片对比控件
“图片对比滑动控件”,这个控件会把两张图片叠加起来,允许用户拖动分割条来控制这两张图片的显露区域。
CSS resize 方案
resize属性指定一个元素是否是由用户调整大小的。
resize: none | both | horizontal | vertical;
值 | 描述 |
---|---|
none | 用户无法调整元素的尺寸。 |
both | 用户可调整元素的高度和宽度。 |
horizontal | 用户可调整元素的宽度。 |
vertical | 用户可调整元素的高度。 |
图片对比滑动控件基本上可以理解为两层结构:下层是一张固定的图片;上层的图片则可以在水平方向上调整大小,从而或多或少地显露出下层图片。直接调整图片大小会导致其变形失真,用一个 div 作为它的容器,再对这个容器应用resize 属性。
<div class="image-slider">
<div>
<img src="adamcatlace-before.jpg" alt="Before" />
</div>
<img src="adamcatlace-after.jpg" alt="After" />
</div>
可以把 div 的宽度拉伸到超过图片宽度的程度。把它的max-width 指定为100%。
调节手柄不容易辨认。用一个伪元素覆盖在调节手柄之上。这一方面可以很方便地设置样式;另一方面,即使在不加pointer-events:none 的情况下,这个伪元素也不会干扰调节手柄的功能。
.image-slider {
position:relative;
display: inline-block;
}
.image-slider > div {
position: absolute;
top: 0; bottom: 0; left: 0;
width: 50%;
max-width: 100%;
overflow: hidden;
resize: horizontal;
}
.image-slider > div::before {
content: '';
position: absolute;
bottom: 0; right: 0;
width: 12px; height: 12px;
padding: 5px;
background: linear-gradient(-45deg, white 50%, transparent 0);
background-clip: content-box;
cursor: ew-resize;
}
.image-slider img {
display: block;
user-select: none;
}
范围输入控件方案
将一个原生的滑块控件(HTML 范围输入控件)覆盖在图片上,用它来控制上层图片的伸缩。想让这个范围输入控件在视觉上与整个控件更加统一,可以用混合模式和/ 或滤镜来实现。
<div class="image-slider">
<div>
<img src="adamcatlace-before.jpg" alt="Before" />
</div>
<img src="adamcatlace-after.jpg" alt="After" />
<input type="range" />
</div>
let div = $('div')[0];
let range = $('input')[0];
range.oninput = function () {
div.style.width = this.value + '%';
};
.image-slider {
position:relative;
display: inline-block;
}
.image-slider > div {
position: absolute;
top: 0; bottom: 0; left: 0;
width: 50%;
overflow: hidden;
}
.image-slider img {
display: block;
user-select: none;
}
.image-slider input {
position: absolute;
left: 0;
bottom: 10px;
width: 100%;
margin: 0;
filter: contrast(.5);
mix-blend-mode: luminosity;
}