css揭秘 - 用户体验(二)

543 阅读5分钟

这是我参与更文挑战的第12天,活动详情查看: 更文挑战

通过阴影来弱化背景

难题

很多时候,我们需要通过一层半透明的遮罩层来把后面的一切整体调暗,以便凸显某个特定的 UI 元素,引导用户关注。比如,弹出层等。

通用的方法经常是增加一个额外的 HTML 元素用于遮挡背景,然后添加如下样式:

.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 元素:

body.dimmed::before {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 1;
    background: rgba(0,0,0,.8);
}

这个方法的可移植性还不够好,body 元素上可能有其他的需求占用了 ::before 伪元素,而且在使用这个方法时,还需要一点 JavaScript 来给 body 添加 dimmed 这个类。

如果把遮罩层交给这个元素自己的 ::before 伪元素来实现,就可以弥补这些不足了。给伪元素设置 z-index: -1; 就可以让它出现在元素的背后。尽管这解决了可移植性的问题,但无法对遮罩层的 Z 轴层次进行细粒度的控制。它可能会出现在这个元素之后(这是我们期望的),但也可能会出现在这个元素的父元素或祖先元素之后。

box-shadow 方案

box-shadow 的扩张参数可以把元素的投影向各个方向延伸放大。具体做法就是生成一个巨大的投影,不偏移也不模糊,简单而拙劣地模拟出遮罩层的效果:

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

这个方法在分辨率超过 2000px 就不能完全遮上,因此需要扩大投影的尺寸,因为无法分开指定水平和垂直方向上的扩张半径,所以此处最合适的视口单位是 vmax1vmax 相当于 1vw1vh 两者中的较大值。因此修改为如下:

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

这个技巧非常简洁易用,但它存在两个非常严重的问题:

  1. 遮罩层的尺寸是跟视口相关而不是跟页面相关,因此当滚动页面时,遮罩层的边缘就露出来了,除非加上 position: fixed。
  2. 当使用一个独立的元素(或伪元素)来实现遮罩层时,这个遮罩层不仅可以从视觉上把用户的注意力引导到关键元素上,还可以防止用户的鼠标与页面的其他部分发生交互,但是 box-shadow 并没有这种能力。

backdrop 方案

如果想引导用户关注的元素就是一个模态的 <dialog> 元素(<dialog> 元素可以由它的 showModal() 方法显示出来),那么根据浏览器的默认样式,它会自带一个遮罩层。借助 ::backdrop 伪元素,这个原生的遮罩层也是可以设置样式的,比如可以把它变得更暗一些:

dialog::backdrop {
    background: rgba(0, 0, 0, .8);
}

要注意兼容性问题。

通过模糊来弱化背景

难题

上一节是使用半透明的遮罩层弱化页面背景,但是如果页面元素较多的情况下,只有将其调到很暗的程度才能为背景之上的文本提供足够的对比度,另外还有一种更优雅的方法,就是把关键元素之外的其他元素都模糊掉,用来配合(或取代)阴影效果。

不过,这种方法的实现难度也更高。在滤镜效果出现之前,完全是不可能完成的任务,即使是在 blur() 滤镜出现之后,这个任务依旧是非常困难的。

解决方案

需要一个额外的 HTML 元素来实现这个效果,要将页面上除了关键元素之外的其他元素都包裹起来,这样就可以只对这个容器进行模糊处理了。<main> 元素比较合适。结构代码:

<main>我是一个页面主体</main>
<dialog>
  我是一个对话框,点击。
</dialog>
<!-- 其他对话框都写在这里 -->

每当弹出一个对话框,都需要给 <main> 元素增加一个类,以便对它应用模糊滤镜。

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

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

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

如果能把这两种弱化背景的手法(阴影和模糊)结合起来,那就更好了。有一种实现方法就是使用 brightness() 和 / 或 contrast() 滤镜:

main.de-emphasized {
    filter: blur(3px) contrast(.8) brightness(.8); 
} 

滚动提示

难题

当侧边栏的容器还有更多内容时,一层淡淡的阴影会出现在容器的顶部和 / 或底部。

解决方案

ul {
  display: inline-block;
  overflow: auto;
  width: 7.2em;
  height: 7em;
  border: 1px solid silver;
  padding: .3em .5em;
  list-style: none;
  font: 100 200%/1.6 'Frutiger LT Std', sans-serif;
  background: linear-gradient(white 15px, hsla(0, 0%, 100%, 0)) 0 0 / 100% 50px,
    radial-gradient(at top, rgba(0, 0, 0, .2), transparent 70%) 0 0 / 100% 15px,
    linear-gradient(to top, white 15px, hsla(0, 0%, 100%, 0)) bottom / 100% 50px,
    radial-gradient(at bottom, rgba(0, 0, 0, .2), transparent 70%) bottom / 100% 15px;
  background-repeat: no-repeat;
  background-attachment: local, scroll, local, scroll;
  margin-top: 30px;
}

交互式的图片对比控件

.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;
  right: 0;
  bottom: 0;
  width: 12px;
  height: 12px;
  padding: 5px;
  background: linear-gradient(-45deg, white 50%, transparent 0);
  background-clip: content-box;
  cursor: ew-resize;
  -webkit-filter: drop-shadow(0 0 2px black);
  filter: drop-shadow(0 0 2px black);
}

.image-slider img {
  display: block;
  user-select: none;
}

image.png

最后说一句

如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下,您的支持是我坚持写作最大的动力,多谢支持。