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

13,062 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第30天,点击查看活动详情

背景

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

83_1661873821.gif

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

  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-heightmax-height。欢迎阅读《你真的了解 width height 吗?》了解min-heightmax-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会覆盖前面的,constantenv可能在一些浏览器并不支持,这时候下面的就不合法了,会使用兜底的0 24px这个padding值。

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

image.png

如果你贴紧底部放置文本,用户是看不清的。所以就有了「安全区域」概念,给弹窗设置底部的安全区域到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,可以参考下:

demo

写在最后

推荐阅读:

我是HullQin,公众号线下聚会游戏的作者(欢迎关注公众号,联系我,交个朋友),转发本文前需获得作者HullQin授权。我独立开发了《联机桌游合集》,是个网页,可以很方便的跟朋友联机玩斗地主、五子棋等游戏,不收费无广告。还独立开发了《合成大西瓜重制版》。还开发了《Dice Crush》参加Game Jam 2022。喜欢可以关注我噢~我有空了会分享做游戏的相关技术,会在这2个专栏里分享:《教你做小游戏》《极致用户体验》