优雅地实现滚动容器遮罩
2023-12-09
在设计前端页面时,可滚动容器的边界可能与父容器的边界不同。为了解决子元素溢出裁切问题,可以添加纯色遮罩。通过创建几个相同背景颜色的绝对定位元素实现效果,并在外部嵌套一个相对定位元素以固定两个遮罩位置。 改进方案是使用CSS属性"mask"来生成线性渐变蒙版,达到相同效果而无需额外元素或绝对定位。

在设计前端页面时,常常会遇到这种情况:可滚动容器的边界并非父容器的边界,导致子元素溢出造成裁切,让页面产生比较怪异的视觉效果(左图)

添加遮罩之后,效果自然了许多(右图)

纯色遮罩

以上图的这种情况举例,我们需要做的,是在可滚动容器的顶部和底部分别放置一个线性渐变的纯色遮罩,遮挡生硬的裁切线。创建两个元素 .top-mask.bottom-mask 来作为遮罩,遮罩的颜色与父容器背景一致,使用 absolute 定位。

.top-mask {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 24px;
    background: linear-gradient(to top, transparent 0%, white 100%);
}

.bottom-mask {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    height: 24px;
    background: linear-gradient(to bottom, transparent 0%, white 100%);
}
css

直接为可滚动容器设置 position: relative 是行不通的,这会导致遮罩跟随容器滚动。所以需要在可滚动容器外部再嵌套一层 relative 定位的元素,使两个遮罩根据其位置定位,最终的结构大概是下面这样的:

<!-- 无遮罩 -->
<div class="out-container">
    <div class="scrollable-container">
        <!-- 很多很多的子元素 -->
    </div>
</div>

<!-- 有遮罩 -->
<div class="out-container">
    <div class="relative-container">
        <div class="top-mask"></div>
        <div class="bottom-mask"></div>
        <div class="scrollable-container">
            <!-- 很多很多的子元素 -->
        </div>
    </div>
</div>
html

Codepen 查看演示

后续为了优化视觉效果,可以根据条件显示/隐藏对应的 mask 元素(滚动条在顶部时不显示 top-mask,反之亦然)

改进:Alpha 遮罩

上面的这种方法有许多缺陷:

  1. 引入了许多额外的元素,致使整体布局变得复杂。
  2. 蒙版覆盖在可滚动容器之上,需要使用 pointer-events: none; 避免影响滚动操作。
  3. 仅适用于父容器为纯色的场景,在父容器有透明度、有背景图案或渐变时,遮罩会露馅。

是否有一种方法,在不引入额外元素、不使用绝对定位的条件下,解决这些缺陷呢?这时候就可以用到 mask CSS属性。mask 属性允许提供一张图片作为蒙版,改变元素的可视区域。我们只需要生成一个线性渐变,将其作为可滚动容器的蒙版即可。

使用linear-gradient创建一个多段的线性渐变,得到图中的蒙版效果。

linear-gradient(to bottom, transparent 0%, white 25px, white calc(100% - 25px), transparent 100%)
css

接着,将得到的渐变图案作为 mask 应用到滚动容器上,为了便于自定义,将这里的遮罩高度 25px 提取出来,以 CSS 变量的形式提供。下面是完整的样式:

.scrollable-container {
  --show-top-mask: 0;
  --show-bottom-mask: 0;
  --mask-size: 25px;
  --gradient: linear-gradient(to bottom, transparent 0%, white calc(var(--show-top-mask) * var(--mask-size)),white calc(100% - calc(var(--mask-size)*var(--show-bottom-mask))), transparent 100%);
  -webkit-mask: var(--gradient);
  mask: var(--gradient);
}

.top-mask {
  --show-top-mask: 1;
}

.bottom-mask {
  --show-bottom-mask: 1;
}
css

因为我们将容器两侧的遮罩合并到了一个线性渐变中,想要控制其中一侧的遮罩就不太容易了,为了实现遮罩的独立控制,额外定义了 --show-top-mask--show-bottom-mask 变量。最终的效果如下图所示:

CodePen 查看

留言功能还在努力开发中,你可以前往旧版博客留下评论