选用合适的鼠标光标
cursor
扩大可点击区域
border、box-shadow、background-clip
难题
对于那些较小的、难以瞄准的控件来说,如果不能把它的视觉尺寸直接放大,将其可点击区域(热区)向外扩张往往也可以带来可用性的提升。
还有一些时候,我们想让某个元素在鼠标接近窗口某侧时自动滑入。
解决方案
扩张热区最简单的办法是为它设置一圈透明边框,因为鼠标对元素边框的交互也会触发鼠标事件,这一点是描边和投影所不及的。
1 | border: 10px solid transparent; |
但是效果并不好,因为它同时让按钮变大了!原因在于背景在默认情况下会蔓延到边框的下层。简单好用的background-clip
属性可以把背景限制在原本的区域之内,同时加边框只能用box-shadow
,因为border
已经被占用
1 | border: 10px solid transparent; |
我们可能想给这个按钮再加一道真实的模糊化投影,来营造一种“浮出表面”的效果。此时用box-shadow
把内嵌投影和(常规的)外部投影组合起来,将会得到一个怪异的效果,因为外部投影是绘
制在border box外部的,并且边框会影响布局。
我们放弃边框,然后改用另外一个特性来实现:伪元素同样可以代表其宿主元素来响应鼠标交互.
我们可以在按钮的上层覆盖一层透明的伪元素,并让伪元素在四个方向
上都比宿主元素大出 10px:
1 | button { |
这个基于伪元素的解决方案极为灵活,我们基本上可以把热区设置为任何想要的尺寸、位置或形状,甚至可以脱离元素原有的位置!
自定义复选框(暂不考虑)
input[type=”checkbox”]
未完成,因为技巧中涉及到clip已被废弃
通过阴影来弱化背景
RGBA 颜色
难题
很多时候,我们需要通过一层半透明的遮罩层来把后面的一切整体调暗,以便凸显某个特定的 UI 元素,引导用户关注。比如,登录框。这个效果最常见的实现方法就是增加一个额外的 HTML 元素用于遮挡背景,然后为它添加如下样式:
1 | .overlay { /* 用于遮挡背景 */ |
这个方法稳定可靠,但需要增加一个额外的 HTML 元素,这意味着该效果无法由 CSS 单独
实现。
伪元素方案
我们可以用伪元素来消除额外的HTML 元素,比如:
1 | body.dimmed::before { |
虽然可以直接在CSS层面操作,但又以下2个问题
- 可移植性还不够好,因为 元素上可能有其他需求已经占用了
::before
伪元素; - 还需要一点JavaScript来给
<body>
添加 dimmed 这个类。
改进方法:可以把遮罩层交给这个元素自己的::before
伪元素来实现,就可以弥补这些不足了,同时还要给伪元素设置z-index: -1
,但是也有两个问题:
- 无法对遮罩层的Z 轴层次进行细粒度的控制,可能会出现在这个元素的父元素或祖先元素之后
- 伪元素无法绑定独立的 JavaScript 事件处理函数。
box-shadow方案
对于简单的应用场景和产品原型来说,我们可以利用box-shadow
来达到调暗背景的效果:box-shadow
的扩张参数可以把元素的投影向各个方向延伸放大。
1 | box-shadow: 0 0 0 999px rgba(0,0,0,.8); |
但是它无法在较大的屏幕分辨率(>2000px)下正常工作。
通过模糊来弱化背景
过渡动画,“毛玻璃效果”,“通过阴影来弱化背景”
难题
弱化背景有另外一种更加优雅的方法,就是把关键元素之外的一切都模糊掉,用来配合(或取代)阴影效果,如下图所示。这个效果的真实感更强,因为它营造出了 景深效果 :当我们的视线聚焦在距离较近的物体上时,远处的背景就是虚化的。
不过,这种方法的实现难度也更高。这跟“毛玻璃效果”一节中的问题非常类似,但我们无法在这里直接套用那里的解决方案,因为处在这个对话框下层的可能是任何元素,而不一定只有一张背景图片。那我们该怎么办?
解决方案
很遗憾,我们还是得动用一个额外的 HTML 元素来实现这个效果:
需要把页面上除了关键元素之外的一切都包裹起来,这样就可以只对这个容器元素进行模糊处理了。
1 | <main>Bacon Ipsum dolor sit amet...</main> |
现在这个模糊效果是突然出现的,给人一种突兀的感觉。
由于 CSS 滤镜是可以设置动画的,我们可以让页面背景的模糊过程以过渡动画的形式来呈现。
1 | main { |
滚动提示
CSS 渐变,background-size
难题
滚动条是一种常见的界面控件,用来提示一个元素除了可以看到的内容之外,还包含了更多内容。它往往太过笨重,在视觉上喧宾夺主。但滚动条对于元素内容可滚动的提示作用仍然是十分有用的,哪怕对于那些没有发生交互的元素也是如此;而且这种提示方式十分巧妙。
解决方案
先设置需要滚动的的结构样式
1 | <ul> |
接下来,有趣的事情即将发生。我们用一个径向渐变在顶部添加一条阴影:
1 | background: radial-gradient(at top, rgba(0,0,0,.2), |
现在,当我们滚动列表时,这条阴影会一直停留在相同的位置。这正是背景图像的默认行为:它的位置是相对于元素固定的,不论元素的内容是否发生了滚动。有没有办法让背景图像跟着元素的内容一起滚动呢?
问题的答案是我们需要两层背景:
一层用来生成那条阴影,另一层基本上就是一个用来遮挡阴影的白色矩形,其作用类似于遮罩层。成阴影的那层背景将具有默认的background-attachment
值(scroll),因为我们希望
它总是保持在原位。我们把遮罩背景的background-attachment
属性设置为local,这样它就会在我们滚动到最顶部时盖住阴影,在向下滚动时跟着滚动,从而露出阴影。
我们会用一道线性渐变来生成这个矩形的遮罩,并让它的颜色与容器的
背景色保持一致(这里是白色):
1 | background: linear-gradient(white, white), |
这好像已经达到我们想要的效果了,但还有一个很大的缺点:当我们只是滚动了一点距离时,阴影露出的方式非常生硬和突兀。有没有办法让它变得平滑一些?
别忘了我们的“遮罩层”是一层(逐渐淡化的)线性渐变,只要把它修改为一段从white到透明白色 (hsla(0,0%,100%,0)或rgba(255,255,255,0))的真正的渐变图案,就可以让阴影的显现过程变得平滑:
1 | background: linear-gradient(white, hsla(0,0%,100%,0)), |
不过,它仍然有一个严重的缺陷:当我们滚动到最顶部的时候,这个“遮罩层”再也无法完整地遮住阴影了。这个问题也是可以解决的,我们只要把 white 色标向下移动一点,经过一番试验之后,50px 似乎是一个合理的数值。最终的代码如下所示:
1 | background: linear-gradient(white 30%, transparent), |
当然,为了完整地实现这个效果,我们还需要再用两层渐变来实现底部的阴影和它配套的遮罩,但逻辑是完全一致的。
交互式的图片对比控件
resize
难题
有时,我们需要展示两张图片的外观差异,有一种更友好的解决方案叫作“图片对比滑动控件” 。这个控件会把两张图片叠加起来,允许用户拖动分割条来控制这两张图片的显露区域。
有没有一种简单的方法可以实现这个控件呢?实际上,方法不止一种!
CSS resize 方案
仔细想一想就会发现,图片对比滑动控件基本上可以理解为两层结构:
下层是一张固定的图片;上层的图片则可以在水平方向上调整大小,从而或多或少地显露出下层图片。
resize
,这个属性实际上适用于任何元素,只要它的overflow
属性不是 visible。对几乎所有元素来说,resize
默认都是设置为 none 的,即禁用调整大小的特性。除了 both 之外,这个属性接受的值还有 horizontal 和 vertical,它们可以限制元素调整大小的方向。
我们的第一个念头可能是列出两个<img>
元素。但是,直接对一个<img>
元素应用 resize 看起来会很怪异,因为直接调整图片大小会导致其变形失真。如果用一个<div>
作为它的容器,再对这个容器应用 resize属性,那就合理多了。
1 | <div class="image-slider"> |
我们已经可以拖动这个手柄来随心所欲地调整上层图片的宽度了!不过,在稍作尝试之后,我们还是会发现一些缺点:
- 可以把 的宽度拉伸到超过图片宽度的程度
- 调节手柄不容易辨认
第一个问题:是比较容易解决的。我们要做的就是把它的 max-width 指
定为 100%。
第二个问题:用一个伪元素覆盖在调节手柄之上。这一方面可以很方便地设置样式;另一方面,即使在不加pointer-events: none
的情况下,这个伪元素也不会干扰调节手柄的功能。因此,一个跨浏
览器的调节手柄美化方案只需要把一个假的调节手柄覆盖在它上面。
到了这一步,我们就可以尽情地对它设置样式了。比如说,如果我们想把它设置
为一个白色三角形,并且让它跟图片的边缘保持5px 的间隙
1 | .image-slider > div::before { |
我们可以对这两张图片应用user-select:none
,这样即使用户在没有点中调节手柄的情况下拖动鼠标,也不会误选图片。把所有想法综合到一起,最终的代码如下所示:
1 | .image-slider { |
范围输入控件方案
以后精进CSS水平时再深入研究