《CSS揭秘》读书笔记9

交互式的图片对比控件

想要实现的效果是两张图片堆叠摆放,然后可以通过交互调节两张图片的显示范围,来达到对比的效果。书中第一种方案是利用元素的resize属性,在其中某一张图片包一层用来resize的元素控制显示大小,最终达到目的。代码示例如下:

1
2
3
4
5
6
7
<!-- html -->
<div class="image-slider">
<div>
<img src="adamcatlace-before.jpg" alt="Before" />
</div>
<img src="adamcatlace-after.jpg" alt="After" />
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/* css */
.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;
}

最后的效果并不太好,原生调节柄和自定义调节柄交叉在一起,包裹元素的width:50%max-width:100%导致chrome下调节显示范围变成了50% - 100%,并不能任意调节了。

第二种方法利用了html原生的滑块控件,将滑块的值的利用js反映到图片的包裹元素上,这个比较常见易行。

自适应内部元素

我们在开发中可以很轻易做到让内部元素适应外部元素的大小,但是让外部元素适应内部元素的内容大小,尤其是内部元素有多个的情况,除了很不优雅的floatinline-block方案,书中给我们提供了min-content这种方案。

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

1
2
3
4
5
6
7
8
9
<!-- html -->
<p>Some text [...]</p>
<figure>
<img src="adamcatlace.jpg" />
<figcaption>
The great Sir Adam Catlace was named after Countess Ada Lovelace, the first programmer.
</figcaption>
</figure>
<p>More text [...].</p>
1
2
3
4
5
6
7
/* css */
figure {
max-width: 300px;
max-width: min-content;
margin: auto;
}
figure > img { max-width: inherit; }

以上为不支持的浏览器添加了平稳退化,对于现代浏览器来说,后一条max-width声明会覆盖前一条。如果figure的尺寸是由内部因素决定时,第二条规则中的max-width: inherit就不会生效了。

精确控制表格列宽

table元素有一个table-layout属性,它的默认值是 auto,其行为模式被称作自动表格布局算法,也就是我们最为熟悉的表格布局行为。不过,它还接受另外一个值fixed,这样的话表格就不会自动计算各自的列宽了。

根据兄弟元素的数量来设置样式

这一节感觉很有用,采用摘抄的方式记录如下:

在某些场景下,我们需要根据兄弟元素的总数来为它们设置样式。例如仅当列表项的总数为 4 时才对这些列表项设置样式。我们可以用li:nth-child(4)来选中列表的第四个列表项,但这并不是我们想要的;我们需要在列表项的总数为 4 时选中每一个列表项。

对于只有一个列表项的特殊场景来说,解决方案显然就是:onlychild,实际上,:only-child等效于:first-child:last-child,道理很简单:如果第一项同时也是最后一项,那从逻辑上来说它就是唯一的那一项。:last-child其实也是一个快捷写法,相当于:nth-last-child(1)

这个 1 其实是一个参数,我们可以根据需要来修改这个值。你能猜到li:first-child:nth-last-child(4)会命中哪些元素吗?如果从:onlychild的例子举一反三,认定这个选择符会在列表项总数为 4 时命中所有列表项,那你可就太过乐观了。我们还没达到那个效果,但已经处在正确的方向上了。让我们把这两个伪类分开来想一想:我们在找一个同时匹配:first-childnth-last-child(4)的元素。因此,这个元素需要是父元素的第一个子元素,同时还需要是从后往前数的第四个子元素。有哪个元素可以满足这个条件呢?

答案就是,一个正好有四个列表项的列表中的第一个列表项。(参见图7-9)这并不完全是我们想要的结果,但已经非常接近了。我们现在已经找到一种命中特定数量列表项中第一项的方法,接下来就可以用兄弟选择符(~)来命中它之后的所有兄弟元素:相当于在这个列表正好包含四个列表项时,命中它的每一项,而这正是我们一直想要达成的目标:

1
2
3
4
li:first-child:nth-last-child(4),
li:first-child:nth-last-child(4) ~ li {
/* 当列表正好包含四项时,命中所有列表项 */
}

7-9.png

在实际的应用场景中,我们期望匹配元素的条件往往并不是列表项的具体数量,而是一个数量范围。:nth-child()选择符在这方面非常好用,我们可以用它来命中一个范围,比如“选中第 4 项之后的所有项”这样的需求就完全不在话下。它的参数不仅可以是简单的数字,也可以是像 an+b 这样的表达式(比如:nth-child(2n+1))。这里的 n 表示一个变量,理论上的范围是 0 到 +∞(在实际情况下,由于页面中元素的数量本来就是有限的,超过某个特定数量的值实际上也没有元素可选了)。如果使用 n+b 这种形式的表达式(此时相当于 a 的取值为1),那么不论 n 如何取值,这个表达式都无法产生一个小于 b 的值。因此,n+b 这种形式的表达式可以选中从第 b 个开始的所有子元素。举例来说,:nth-child(n+4)将会选中除了第一、二、三个子元素之外的所有子元素(参见图7-10)。

7-10.png

利用这个技巧,我们可以在列表项的总数是 4 或更多时选中所有列表项(参见图7-11)。在这种情况下,我们可以把表达式 n+4 传给:nth-lastchild()

1
2
3
4
li:first-child:nth-last-child(n+4),
li:first-child:nth-last-child(n+4) ~ li {
/* 当列表至少包含四项时,命中所有列表项 */
}

7-11.png

同理,-n+b 这种形式的表达式可以选中开头的 b 个元素。因此,仅当列表中有 4 个或更少的列表项时(参见图7-12),要选中所有列表项可以这样写:

1
2
3
4
li:first-child:nth-last-child(-n+4),
li:first-child:nth-last-child(-n+4) ~ li {
/* 当列表最多包含四项时,命中所有列表项 */
}

7-12.png

当然,我们还可以把这两种技巧组合起来使用,不过代码也会变得更加复杂。假设我们希望在列表包含 2 ~ 6 个列表项时命中所有的列表项,可以这样写:

1
2
3
4
li:first-child:nth-last-child(n+2):nth-last-child(-n+6),
li:first-child:nth-last-child(n+2):nth-last-child(-n+6) ~ li {
/* 当列表包含2~6项时,命中所有列表项 */
}

是不是厉害了呢!但我转念又有一个问题,其实这个效果利用js动态添加样式可以轻轻松松做出,综合考虑学习和开发成本、性能等因素,我们用css来做这个效果,除了把样式单独交给css,还有没有其他意义了呢?(●’◡’●)