背景

半页面弹窗是前端移动端中非常常用的组件,效果如下:

小程序如何优雅的实现半页面弹窗_小程序

一个用户体验好的半页面弹窗,应该有如下特点:

  1. 出现弹窗时,背景有个黑色半透明遮罩层,应该是逐渐出现的,透明度渐变。
  2. 出现弹窗时,弹窗是从下至上移动出现的,而不是闪现的。
  3. 关闭弹窗时,背景色也要逐渐消失,弹窗从上至下移动走。

一个好用的半页面弹窗,应该有如下特点:

  1. 动画样式实现不耦合弹窗的高度。无论里面放多少内容,弹窗有多高,不改样式的情况下,渐入渐出的动画都表现正常,且出现时,底部应该贴合屏幕底部。
  2. 控制出现/隐藏的方法简单,自动播放出现/隐藏的渐变动画,而非手动调用API播放渐变动画。

本文参考WeUI的实现,介绍如何优雅的实现半页面弹窗

遮罩层样式

.mask {
position: fixed;
z-index: 1000;
top: 0;
right: 0;
left: 0;
bottom: 0;
background: rgba(0,0,0,.6);
}

遮罩层动画

我们可以让遮罩层默认是隐藏状态(opacity为0),当要展示时,需要开发者设置一个新的class(比如叫show)给mask对应的标签。

然后,我们给遮罩层设置个transition即可。代码如下:

.mask{
opacity: 0;
visibility: hidden;
transition: opacity .3s;
}
.mask.show {
opacity: 1;
visibility: visible;
}

注意,我们必须设置​​visibility​​​属性。因为当你设置​​opacity​​​后,只是修改了透明度,它会在顶部挡住所有的交互事件。所以我们需要修改​​visibility​​,这样它就不会挡住那些交互事件了。

弹窗样式

为了让弹窗无论什么高度都可贴紧屏幕底部,我们直接设置​​bottom: 0​​即可。

此外,为了防止弹窗太低不美观,弹窗太高挡住页面,最好设置个​​min-height​​​和​​max-height​​​。欢迎阅读​​《你真的了解 width height 吗?》​​​了解​​min-height​​​和​​max-height​​的优先级。

.dialog {
position: fixed;
left: 0;
right: 0;
bottom: 0;
min-height: 255px;
max-height: 75%;
z-index: 5000;
border-top-left-radius: 12px;
border-top-right-radius: 12px;
overflow: hidden;
padding: 0 24px;
padding: 0 24px constant(safe-area-inset-bottom) 24px;
padding: 0 24px env(safe-area-inset-bottom) 24px;
}

也许你会好奇为什么设置3个​​padding​​​:下面的合法的padding会覆盖前面的,​​constant​​​和​​env​​​可能在一些浏览器并不支持,这时候下面的就不合法了,会使用兜底的​​0 24px​​这个padding值。

为什么要用​​env(safe-area-inset-bottom)​​呢?主要是iPhone最近几年的产品,底部有一个条带,如下图:

小程序如何优雅的实现半页面弹窗_小程序_02

如果你贴紧底部放置文本,用户是看不清的。所以就有了「安全区域」概念,给弹窗设置底部的安全区域到padding里,保证了弹窗内容不会跟这个条带重合,保障了iOS用户体验。

在近代iPhone系列产品中,env(safe-area-inset-bottom)通常是34px,其它产品中,这个值是0。

弹窗动画

如果没有​​.show​​,就隐藏,把他放在屏幕底下,通过transform的translateY实现。为了让动画跟元素高度像素值无关,我们使用100%,代表元素整体高度。

.dialog {
transform: translateY(100%);
transition: transform .3s;
}
.dialog.show {
transform: translateY(0);
}

看看wxml怎么写

这里以小程序为例:

<view aria-role="dialog" aria-modal="true" aria-hidden="{{!showDialog}}">
<view class="mask {{showDialog ? 'show' : ''}}" catchtouchmove="return"></view>
<view class="dialog {{showDialog ? 'show' : ''}}" catchtouchmove="return">
弹窗内容
</view>
</view>

注意这里的​​catchtouchmove="return"​​,是为了在打开弹窗后,拦截了用户滑动事件,避免弹窗下面的文档被滑动。

看看html怎么写

我写了个demo,可以参考下:

小程序如何优雅的实现半页面弹窗_CSS_03