《CSS揭秘》第6章-用户体验

选用合适的鼠标光标

cursor

扩大可点击区域

border、box-shadow、background-clip

难题

对于那些较小的、难以瞄准的控件来说,如果不能把它的视觉尺寸直接放大,将其可点击区域(热区)向外扩张往往也可以带来可用性的提升。
还有一些时候,我们想让某个元素在鼠标接近窗口某侧时自动滑入。

解决方案

扩张热区最简单的办法是为它设置一圈透明边框,因为鼠标对元素边框的交互也会触发鼠标事件,这一点是描边和投影所不及的。

1
border: 10px solid transparent;

但是效果并不好,因为它同时让按钮变大了!原因在于背景在默认情况下会蔓延到边框的下层。简单好用的background-clip属性可以把背景限制在原本的区域之内,同时加边框只能用box-shadow,因为border已经被占用

1
2
3
border: 10px solid transparent; 
box-shadow: 0 0 0 1px rgba(0,0,0,.3) inset;
background-clip: padding-box;

我们可能想给这个按钮再加一道真实的模糊化投影,来营造一种“浮出表面”的效果。此时用box-shadow把内嵌投影和(常规的)外部投影组合起来,将会得到一个怪异的效果,因为外部投影是绘
制在border box外部的,并且边框会影响布局。

我们放弃边框,然后改用另外一个特性来实现:伪元素同样可以代表其宿主元素来响应鼠标交互.
我们可以在按钮的上层覆盖一层透明的伪元素,并让伪元素在四个方向
上都比宿主元素大出 10px:

1
2
3
4
5
6
7
8
9
10
button { 
position: relative;
}

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

这个基于伪元素的解决方案极为灵活,我们基本上可以把热区设置为任何想要的尺寸、位置或形状,甚至可以脱离元素原有的位置!

自定义复选框(暂不考虑)

input[type=”checkbox”]
未完成,因为技巧中涉及到clip已被废弃

通过阴影来弱化背景

RGBA 颜色

难题

很多时候,我们需要通过一层半透明的遮罩层来把后面的一切整体调暗,以便凸显某个特定的 UI 元素,引导用户关注。比如,登录框。这个效果最常见的实现方法就是增加一个额外的 HTML 元素用于遮挡背景,然后为它添加如下样式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.overlay { /* 用于遮挡背景 */ 
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: rgba(0,0,0,.8);
}

.lightbox { /* 需要吸引用户注意的元素 */
position: absolute;
z-index: 1;
/* [其余样式] */
}

这个方法稳定可靠,但需要增加一个额外的 HTML 元素,这意味着该效果无法由 CSS 单独
实现。

伪元素方案

我们可以用伪元素来消除额外的HTML 元素,比如:

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

虽然可以直接在CSS层面操作,但又以下2个问题

  1. 可移植性还不够好,因为 元素上可能有其他需求已经占用了::before伪元素;
  2. 还需要一点JavaScript来给<body>添加 dimmed 这个类。

改进方法:可以把遮罩层交给这个元素自己的::before伪元素来实现,就可以弥补这些不足了,同时还要给伪元素设置z-index: -1,但是也有两个问题:

  1. 无法对遮罩层的Z 轴层次进行细粒度的控制,可能会出现在这个元素的父元素或祖先元素之后
  2. 伪元素无法绑定独立的 JavaScript 事件处理函数。

box-shadow方案

对于简单的应用场景和产品原型来说,我们可以利用box-shadow来达到调暗背景的效果:
box-shadow的扩张参数可以把元素的投影向各个方向延伸放大。

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

但是它无法在较大的屏幕分辨率(>2000px)下正常工作。

通过模糊来弱化背景

过渡动画,“毛玻璃效果”,“通过阴影来弱化背景”

难题

弱化背景有另外一种更加优雅的方法,就是把关键元素之外的一切都模糊掉,用来配合(或取代)阴影效果,如下图所示。这个效果的真实感更强,因为它营造出了 景深效果 :当我们的视线聚焦在距离较近的物体上时,远处的背景就是虚化的。

不过,这种方法的实现难度也更高。这跟“毛玻璃效果”一节中的问题非常类似,但我们无法在这里直接套用那里的解决方案,因为处在这个对话框下层的可能是任何元素,而不一定只有一张背景图片。那我们该怎么办?

解决方案

很遗憾,我们还是得动用一个额外的 HTML 元素来实现这个效果:
需要把页面上除了关键元素之外的一切都包裹起来,这样就可以只对这个容器元素进行模糊处理了。

1
2
3
4
5
6
7
8
9
<main>Bacon Ipsum dolor sit amet...</main> 
<dialog>
O HAI, I'm a dialog. Click on me to dismiss.
</dialog>


main.de-emphasized {
filter: blur(5px);
}


现在这个模糊效果是突然出现的,给人一种突兀的感觉。
由于 CSS 滤镜是可以设置动画的,我们可以让页面背景的模糊过程以过渡动画的形式来呈现。

1
2
3
4
5
6
7
main { 
transition: .6s filter;
}

main.de-emphasized {
filter: blur(5px);
}

滚动提示

CSS 渐变,background-size

难题

滚动条是一种常见的界面控件,用来提示一个元素除了可以看到的内容之外,还包含了更多内容。它往往太过笨重,在视觉上喧宾夺主。但滚动条对于元素内容可滚动的提示作用仍然是十分有用的,哪怕对于那些没有发生交互的元素也是如此;而且这种提示方式十分巧妙。

解决方案

先设置需要滚动的的结构样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<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>


ul{
overflow: auto;
width: 10em;
height: 8em;
padding: .3em .5em;
border: 1px solid silver;
}

接下来,有趣的事情即将发生。我们用一个径向渐变在顶部添加一条阴影:

1
2
3
background: radial-gradient(at top, rgba(0,0,0,.2), 
transparent 70%) no-repeat;
background-size: 100% 15px;


现在,当我们滚动列表时,这条阴影会一直停留在相同的位置。这正是背景图像的默认行为:它的位置是相对于元素固定的,不论元素的内容是否发生了滚动。有没有办法让背景图像跟着元素的内容一起滚动呢?
问题的答案是我们需要两层背景:
一层用来生成那条阴影,另一层基本上就是一个用来遮挡阴影的白色矩形,其作用类似于遮罩层。成阴影的那层背景将具有默认的background-attachment值(scroll),因为我们希望
它总是保持在原位。我们把遮罩背景的background-attachment属性设置为local,这样它就会在我们滚动到最顶部时盖住阴影,在向下滚动时跟着滚动,从而露出阴影。

我们会用一道线性渐变来生成这个矩形的遮罩,并让它的颜色与容器的
背景色保持一致(这里是白色):

1
2
3
4
5
6
background: linear-gradient(white, white), 
radial-gradient(at top, rgba(0,0,0,.2),
transparent 70%);
background-repeat: no-repeat;
background-size: 100% 15px;
background-attachment: local, scroll;


这好像已经达到我们想要的效果了,但还有一个很大的缺点:当我们只是滚动了一点距离时,阴影露出的方式非常生硬和突兀。有没有办法让它变得平滑一些?
别忘了我们的“遮罩层”是一层(逐渐淡化的)线性渐变,只要把它修改为一段从white到透明白色 (hsla(0,0%,100%,0)或rgba(255,255,255,0))的真正的渐变图案,就可以让阴影的显现过程变得平滑:

1
2
3
background: linear-gradient(white, hsla(0,0%,100%,0)), 
radial-gradient(at top, rgba(0,0,0,.2),
transparent 70%);

不过,它仍然有一个严重的缺陷:当我们滚动到最顶部的时候,这个“遮罩层”再也无法完整地遮住阴影了。这个问题也是可以解决的,我们只要把 white 色标向下移动一点,经过一番试验之后,50px 似乎是一个合理的数值。最终的代码如下所示:

1
2
3
4
5
6
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;

当然,为了完整地实现这个效果,我们还需要再用两层渐变来实现底部的阴影和它配套的遮罩,但逻辑是完全一致的。

交互式的图片对比控件

resize

难题

有时,我们需要展示两张图片的外观差异,有一种更友好的解决方案叫作“图片对比滑动控件” 。这个控件会把两张图片叠加起来,允许用户拖动分割条来控制这两张图片的显露区域。
有没有一种简单的方法可以实现这个控件呢?实际上,方法不止一种!

CSS resize 方案

仔细想一想就会发现,图片对比滑动控件基本上可以理解为两层结构:
下层是一张固定的图片;上层的图片则可以在水平方向上调整大小,从而或多或少地显露出下层图片。

resize,这个属性实际上适用于任何元素,只要它的overflow属性不是 visible。对几乎所有元素来说,resize默认都是设置为 none 的,即禁用调整大小的特性。除了 both 之外,这个属性接受的值还有 horizontal 和 vertical,它们可以限制元素调整大小的方向。

我们的第一个念头可能是列出两个<img>元素。但是,直接对一个<img>元素应用 resize 看起来会很怪异,因为直接调整图片大小会导致其变形失真。如果用一个<div>作为它的容器,再对这个容器应用 resize属性,那就合理多了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<div class="image-slider"> 
<div>
<img src="adamcatlace-before.jpg" alt="Before" />
</div>
<img src="adamcatlace-after.jpg" alt="After" />
</div>


.image-slider {
position:relative;
display: inline-block;
}

.image-slider > div {
position: absolute;
top: 0; bottom: 0; left: 0;
width: 50%; /* 初始宽度 */
overflow: hidden; /* 让它可以裁切图片 */
resize:horizontal;
}

.image-slider img { display: block; }

我们已经可以拖动这个手柄来随心所欲地调整上层图片的宽度了!不过,在稍作尝试之后,我们还是会发现一些缺点:

  • 可以把
    的宽度拉伸到超过图片宽度的程度
  • 调节手柄不容易辨认

第一个问题:是比较容易解决的。我们要做的就是把它的 max-width 指
定为 100%。
第二个问题:用一个伪元素覆盖在调节手柄之上。这一方面可以很方便地设置样式;另一方面,即使在不加pointer-events: none的情况下,这个伪元素也不会干扰调节手柄的功能。因此,一个跨浏
览器的调节手柄美化方案只需要把一个假的调节手柄覆盖在它上面。
到了这一步,我们就可以尽情地对它设置样式了。比如说,如果我们想把它设置
为一个白色三角形,并且让它跟图片的边缘保持5px 的间隙

1
2
3
4
5
6
7
8
9
10
11
.image-slider > div::before { 
content: '';
position: absolute;
bottom: 0; right: 0;
width: 12px; height: 12px;
background: white;
cursor: ew-resize;
padding: 5px;
background: linear-gradient(-45deg, white 50%, transparent 0);
background-clip: content-box;
}

我们可以对这两张图片应用user-select:none,这样即使用户在没有点中调节手柄的情况下拖动鼠标,也不会误选图片。把所有想法综合到一起,最终的代码如下所示:

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
29
30
.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;
}

范围输入控件方案

以后精进CSS水平时再深入研究

liborn wechat
欢迎您扫一扫上面的微信二维码,订阅我的公众号!