微信小程序原生的对话框功能一般够用,但是样式通常不能满足实际的项目,这篇文章介绍模拟实现自定义的对话框,下面是效果示例图:


同上次的自定义导航栏一样,这次也把对话框做成自定义组件的形式,组件的 wxml 部位代码:
<view wx:if="{{modal}}" class="VIEW_mask" style="opacity:{{opacity}};"></view>
<view class="VIEW_dialog_box" style="left:{{dialog_box_left}};top:{{dialog_box_top}};opacity:{{opacity}};">
<view class="VIEW_title_bar" wx:if="{{close&&title!=''}}">
<view>{{title}}</view>
<image bind:tap="tapClose" class="IMAGE_close" wx:if="{{close}}" src="/images/close_gray.png" mode="aspectFit"></image>
</view>
<slot></slot>
<view class="VIEW_button_bar" wx:if="{{confirm||cancel}}">
<button wx:if="{{confirm}}" bind:tap="tapConfirm" class="BUTTON_action" type="primary">确定</button>
<button wx:if="{{cancel}}" bind:tap="tapCancel" class="BUTTON_action" type="warn">取消</button>
</view>
</view>
其中用到了个 slot
元素,这个元素可以让开发者在微信小程序的自定义组件元素内部插入自定义元素,这个元素支持多个,但此例只用到了一个,用在自定义对话框中,使开发者使用时能在自定义组件标记中加入任何自定义的内容。
组件的 wxss 部位代码:
.VIEW_mask {
width: 100%;
height: 100%;
position: fixed;
left: 0;
top: 0;
background: rgba(0, 0, 0, .3);
z-index: 9998;
transition: opacity .5s;
}
.VIEW_dialog_box {
padding: 10px;
border-radius: 8px;
position: fixed;
z-index: 9999;
background: #fff;
max-width: 80%;
max-height: 80%;
transition: opacity .5s;
box-shadow: 0 0 3px 0 rgba(0, 0, 0, .5);
}
.VIEW_title_bar {
display: flex;
justify-content: space-between;
margin: 0 0 10px 0;
}
.IMAGE_close {
width: 20px;
height: 20px;
}
.VIEW_button_bar {
display: flex;
justify-content: center;
margin: 10px 0 0 0;
}
.BUTTON_action {
margin: 0 5px !important;
}
微信小程序的原生按钮组件改样式有些冲突问题,这里用到了 !important
值表示强制覆盖,暴力解决问题。
组件的 js 部位代码:
Component({
/**
* 组件的属性列表
*/
properties: {
title: {
type: String,
value: ''
},
modal: {
type: Boolean,
value: true
},
close: {
type: Boolean,
value: true
},
confirm: {
type: Boolean,
value: true
},
cancel: {
type: Boolean,
value: true
}
},
/**
* 组件的初始数据
*/
data: {
dialog_box_left: 0,
dialog_box_top: 0,
opacity: 0
},
lifetimes: {
attached: function () {
var that = this
var query = this.createSelectorQuery()
var node_VIEW_dialog_box = query.select(".VIEW_dialog_box")
node_VIEW_dialog_box.boundingClientRect(function (res) {
var dialog_box_left = "calc(50% - " + res.width / 2 + "px)"
var dialog_box_top = "calc(50% - " + res.height / 2 + "px)"
that.setData({
dialog_box_left,
dialog_box_top,
opacity: 1
})
}).exec()
}
},
/**
* 组件的方法列表
*/
methods: {
tapClose() {
var eventDetail = {} // detail对象,提供给事件监听函数,用户可在此传递自定义数据
var eventOption = {} // 触发事件的选项,参见官方文档
this.triggerEvent('tapClose', eventDetail, eventOption)
},
tapConfirm() {
var eventDetail = {}
var eventOption = {}
this.triggerEvent('tapConfirm', eventDetail, eventOption)
},
tapCancel() {
var eventDetail = {}
var eventOption = {}
this.triggerEvent('tapCancel', eventDetail, eventOption)
}
}
})
在组件加载时根据组件的宽高值计算出了对话框的 top、left 位置,这里是让对话框出现在布局容器的中间,共监听了三个事件,确定按钮、取消按钮、关闭按钮。
页面的 wxml 部位代码:
<modal-dialog wx:if="{{showModalDialog}}" title="警告" modal="{{is_modal}}" close="{{true}}" confirm="{{true}}" cancel="{{true}}" bind:tapClose="tapClose" bind:tapCancel="tapCancel" bind:tapConfirm="tapConfirm">
<view>
<image class="IMAGE_warning" src="/images/icon_warning.png" mode="aspectFit"></image>
<text>你正在执行敏感的操作,一旦确认将不能退回,确定要继续吗?</text>
</view>
</modal-dialog>
<button class="BUTTON_action" type="primary" bind:tap="showModalDialog">打开模态对话框</button>
<button class="BUTTON_action" type="primary" bind:tap="showDialog">打开非模态对话框</button>
<view class="VIEW_actionMessage">{{actionMessage}}</view>
页面引用组件的配置就不贴代码了,如上 modal-dialog
元素就是我们的自定义对话框,元素内部的 view 容器包裹了一张图片和一段文本,这就是自定义组件中 slot 元素的作用。
页面的 js 部位代码:
Page({
/**
* 页面的初始数据
*/
data: {
showModalDialog: false,
is_modal: true,
actionMessage: ""
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
},
showDialog() {
this.setData({
showModalDialog: true,
is_modal: false
})
},
showModalDialog() {
this.setData({
showModalDialog: true,
is_modal: true
})
},
tapClose() {
this.setData({
showModalDialog: false,
actionMessage: "点击了关闭按钮"
})
},
tapConfirm() {
this.setData({
showModalDialog: false,
actionMessage: "点击了确定按钮"
})
},
tapCancel() {
this.setData({
showModalDialog: false,
actionMessage: "点击了取消按钮"
})
}
})
注意控制对话框显隐是在页面部分用条件渲染的方式实现的,这样子并不利于对话框显隐时的首尾动画,此例只会在显示对话框时才会出现半透明的渐变动画,而关闭对话框受微信小程序条件渲染的方式影响,会直接被移除节点树,因此使 showModalDialog 值为 false 来关闭对话框会直接让其消失。
页面的 wxss 部位代码:
.IMAGE_warning {
width: 24px;
height: 24px;
vertical-align: top;
margin: 0 5px 0 0;
}
.BUTTON_action {
margin: 10px 0;
}
.VIEW_actionMessage {
margin: 10px;
}
结语:此例的对话框位置计算时没有包括非客户区域,比如手机的状态栏、微信小程序的导航栏,因此是内容区域的居中显示;大多数时候对话框都是用模态的,就是后面有个遮罩屏蔽了用户点击到底下内容的可能,不模态的对话框我觉得完全可以被其它的取代;此例的自定义组件代码有个逻辑不恰当的地方,就是当关闭按钮和对话框标题其中缺失一个的话就会导致没有对话框的标题栏,把逻辑与(&&)改成逻辑或(||)吧;遮罩层没有添加点击事件,可以加个点击遮罩关闭对话框的功能;如果想要实现有首尾动画的对话框显隐,我建议不要用微信小程序的条件渲染(wx:if),如果非要用,那可以监听动画的结束再改变条件,但我觉得最好单独实现动画代码并做成个可动态配置的项。