前言

最近在恶补css的知识,看到《css揭秘》一书,如获至宝。下面节选一部分笔记。

一、扩大按钮的点击范围

对于哪些较小的、难以瞄准的控件来说,如果不能把它的视觉尺寸直接放大,将其可点击区域向外扩大也能大幅度提升用户体验。因为一个点来点去都点不到的按钮可不招人喜欢。

只要加上cursor: pointer这个简单的css属性,它能以视觉的方式来提示如何与之进行交互,也能试探它的交互范围有多大。

解决方案一

最简单的方法就是设置一圈透明边框,因为鼠标对元素边框的交互也会触发鼠标事件的。

border: 10px solid transparent;
1

上述代码就是将元素的边框扩大了10px,但是效果并不好,因为它同时让按钮变大了。

使用border的之所以会使按钮变大,是因为背景在默认的情况下会蔓延到边框的下层。这时使用background-clip属性将背景限制在原来区域的下层就行。

border: 10px solid transparent;
background-clip: padding-box;
1
2

而且当按钮真正需要边框时,可以用box-shadow来模拟出边框

border: 10px solid transparent;
box-shadow: 0 0 0 2px red inset;
background-clip: padding-box;
1
2
3

perfect

解决方案二

不用边框,而是采用伪元素来代表宿主元素来影响鼠标交互。

可以在按钮的上层覆盖一层透明的比按钮大10px的伪元素。

btn: {
	position: relative;
}
.btn::before{
	content: '';
  	position: absolute;
  	top: -10px;
  	right: -10px;
  	botttom: -10px;
  	left: -10px;
}
1
2
3
4
5
6
7
8
9
10
11

这个方法的好处是只要有一个伪元素可利用,就能发挥效果,也不会干扰到其他的css属性。

codepen试一试 (opens new window)

二、遮罩层

很多时候,需要通过一层半透明的遮罩层来把后面的一切遮挡住,来凸显某个特定的UI元素,吸引用户的注意。比如弹窗等。

这个效果最常见的方案是增加一个额外的HTML元素用于遮挡背景

.overlay{
	position: fixed;
  	top: 0;
  	right: 0;
  	bottom: 0;
  	left: 0;
  	background: rgba(0,0,0, .8);
}
.dialog{
	position: absolute;
   	z-index: 1;
}
1
2
3
4
5
6
7
8
9
10
11
12

.overlay负责把这个关键元素背后的所以东西遮挡住,.dialog需要指定一个更高的z-index 虽然这个方法很好,但是需要一个额外的HTML元素。

解决方案一

可以使用伪元素来替代额外的HTML元素

body.overlay::before{
	position: fixed;
  	top: 0;
  	right: 0;
  	bottom: 0;
  	left: 0;
  	z-index: 1;
  	background: rgba(0,0,0, .8);
}
1
2
3
4
5
6
7
8
9

这个方案可以直接在CSS层达到遮罩层的效果,但还是有一些缺点:

  • 如果body元素上已经有其他效果占用的::before伪元素
  • 往往还需要JavaScriptbody添加overlay类名

解决方案二

伪元素可以满足绝大多数遮罩层的需要,但对于一些简单的应用场景来说,可以利用box-shadow来达到这个效果。

box-shadow: 0 0 0 999px rgab(0,0,0, .8);
1

box-shadow的扩张参数可以把元素的投影向各个方向延伸放大。就是生成一个巨大的投影,简单的实现遮罩层的效果。

但存在一个问题就是无法在较大的屏幕分辨率中正常工作(上述设置就无法在2000px以上的屏幕中正常工作)。要么就加大数字来缓解,要么换成视口单位。

box-shadow: 0 0 0 50vmax rgab(0,0,0, .8);
1

由于投影是同时往四个方向中扩展的,所以设置为50vmax就能满足需求。

但缺点还是很明显:

  • 由于使用视口单位,当页面滚动时,遮罩层的边缘会显示出来。除非加上fixed定位或者没有滚动
  • 当使用一个独立的元素来实现遮罩层时,这个遮罩层还可以防止用户与页面其他元素发生交互,但box-shadow并没有这个能力。

解决方案三

如果是想凸显一个弹窗元素<dialog\>,而去实现一个遮罩层的话,<dialog\>它会自带一个遮罩层。借助::backdrop伪元素,这个原声的遮罩层也可以设置样式

dialog::backdrop{
	background: rgba(0, 0 ,0, .8)
}
1
2
3

三、自定义下划线

尽管CSS有一个下划线属性text-decoration: underline,但这个属性非常简陋而且不能修改效果。

解决方案一

使用border属性

{
	border-bottom: 1px solid gray;
   	text-decoration: none;
}
1
2
3
4

使用border-bottom模拟出来的下划线可以自定义颜色、线条宽度和线的形状。但是线与字之间的距离或者说空隙很大

如果设置比较小的line-height,比如line-height: .9,距离的确能缩小,但又产生另一个问题:阻止正常的文本换行。

解决方案二

使用background-image属性

background: linear-gradient(gray, gray) no-repeat;
background-size: 100% 1px;
background-position: 0 1.115em;
text-shadow: .05em 0 white, -0.05em  0 white;
1
2
3
4

效果非常棒。也可以实现不同的线条类型:比如虚线下划线

background: linear-gradient(90deg, gray 66%, transparent 0) repeat-x;
background-size: .2em 2px;
backgrond-position: 0 1em;
1
2
3

通过色标的百分比位置值来微调虚线的虚实比例,还可以通过background-size来改变虚线的疏密。

codepen试一试 (opens new window)

四、自适应内部元素

如果不给元素一个具体的height,它会自动适应其内容的高度。那width能不能设置成这样的行为呢? 比如,如下html代码

<p>Some text</p>
<figure>
	<img src='adacatlace.jgp'>
    <figcaption>
    	The great Sir Adam Catlace was named after
        Countess Ada Lovelace, the first programmer
    </figcaption>
</figure>
1
2
3
4
5
6
7
8

在默认情况下,如上图所示,但系需求是这个figure元素能跟它包含的图片一样宽(尺寸往往不是固定的)而且是水平居中的。

比如说让figure元素浮动起来,这样就会得到正确的宽度,但这种方法的副作用非常明显,将布局模式都改变了。

解决方案

使用min-content属性

figure{
	width: min-content;
   	margin: auto;
}
1
2
3
4

两行css代码就能完成。

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

codepen试一试 (opens new window)

五、紧贴底部的页脚

具有块级样式的页脚,怎么让它紧贴内容的下方呢?

假设页面的HTML结构如下:

<header>
	<h1></h1>
</header>
<main>
...
</main>
<footer>
...
</footer>
1
2
3
4
5
6
7
8
9

解决方法

使用flexbox布局,首先对body元素设置display: flex,因为它是这三个元素的父元素,还需要设置flex-flow: column,否则子元素会被水平的排放在一行上。设置高度为100vh占满整个视口的高度。

页头和页脚的高度是由main来决定的,所有给mainflex属性指定一个大于0的值。

body{
	height: 100vh;
	display: flex;
	flex-flow: column;
}
main{
	flex: 1;
}
1
2
3
4
5
6
7
8

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

在某些场景下,需要根据兄弟元素的总数来为它们设置样式。最常见的场景就是,当一个列表不断延长的时候,通过隐藏控件或压缩控件等方式来节省屏幕空间,以此提升用户体验。

解决方案

1、只有一个列表项的特殊场景来说,使用:only-child伪类选择器。

当列表只有一个列表项时,把删除按钮隐藏起来,就需要:only-child选择器来完成

li:only-child{
	background: red;
}
1
2
3

:only-child等效于:first-child:last-child,因为第一项同时也是最后一项,那么它就是唯一的。

:last-child也是一个语法糖,它等价于:nth-last-child(1)

2、列表正好包含四个列表项时,命中它的每一项

li:first-child:nth-last-child(4),
li:first-child:nth-last-child(4) ~ li{
	background: red;
}
1
2
3
4

当然拉,这部分代码还是十分繁琐的,可以利用scss预处理器来避免这个问题

@mixin n-item($n) {
	&:first-child:nth-last-child(#{$n}),
	&:first-child:nth-last-child(#{$n}) ~ & {
    	@content
    }
}

li {
	@include n-item(4) {
    	// 具体样式写这里
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

3、列表项的总数是4或者更多时,选中所有的列表项

li:first-child:nth-last-child(n+4),
li:first-child:nth-last-child(n+4) ~ li{
	background: gray;
}
1
2
3
4

少于4个li元素时:

多于4个li元素时:

4、同理,仅当列表中有4个或更少时,选中所有列表项

li:first-child:nth-last-child(-n+4),
li:first-child:nth-last-child(-n+4) ~ li{
	background: yellow;
}
1
2
3
4

5、列表包含2~6个列表项时命中所有列表项

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{
	background: #456789;
}
1
2
3
4

大于6个:

大于等于2个,少于等于6个:

少于2个:

codepen试一试 (opens new window)

参考:

《css揭秘》