在移动端应用中,很多交互都需要弹窗完成;现在弹窗都是全屏的一定透明度的遮罩层,核心功能概括为以下几点:
-
样式方面: (1)全屏,fixed定位,层级尽可能高,建议998,避免某些特殊情况需要层级要高于遮罩层的样式, (2)蒙层透明度支持设置,默认rgba(0,0,0,0.8)
-
功能方面: (1)最基本的,防止滚动穿透,即弹窗弹起后,底部内容不可再滚动 (2)支持配置点击全屏可关闭,还是点击弹窗内容外蒙层可关闭
-
布局: 移动端弹窗内容一般为居中显示,非居中样式可定义内容全屏即可重置
fixed定位的问题
fixed 元素不一定是相对视口定位,fixed 元素本质是相对于它的包含块定位的;
fixed元素的块级格式上下文 Block Formatting Context(BFC) 由viewport创建,也就是fixed元素的BFC包含在根元素的BFC里;但是当父元素使用了transform时,那么设置了 position: fixed 的子元素 BFC 被包含在了 transform 元素的 BFC 里。因为,transform 属性使元素创建了新的 BFC,所有的子元素都被包含在这个新的 BFC 内;可参考
当元素祖先的 transform
, perspective
或 filter
属性非 none
时,容器由视口改为该祖先
有上述问题后,我们就要考虑到,现在有一个场景:
有一个赠送按钮,点击按钮,弹窗确认赠送弹窗,点击弹窗,可能出现额度不够时提示购买弹窗,点击购买,再弹出二次确认弹窗,所以我希望这个按钮就包含这一整套控件; 因为这个按钮,在主页中,也可能在其他弹窗中也会出现,也就符合多处使用的原则,所以我希望到时使用时,只需要引入我这个按钮控件即可;
<template lang="pug">
.req-bind-btn(@click="onReadySend")
//- 赠送确认弹窗
ready-send(
v-if="showSendConfirmDialog"
:targetUser="targetUser"
@onSureSendBrand="onSureSendBrand"
@onCancel="showSendConfirmDialog = false"
)
//- 购买
buy-band(
@readyBuyBand="onReadyBuyBand"
@close="showBuyBandDialog = false" v-if="showBuyBandDialog"
)
//- 苹果不足充值确认弹窗
t-dialog(:appendToBody="true" v-if="readyRecharge")
.yiyuan-bg.relative
.btn-charge.center-h(@click="buyProduct")
.close.absolute(@click="closeRechare")
</template>
但是这时会发现,fixed的弹窗被限制在了class为'req-bind-btn'的尺寸范围内,这时fixed的BFC变成了这个按钮,所以怎么解决呢 我们可以强制,dialog弹窗必须做成body的直接子元素,不让其再受到引用它的父元素的影响;
<template lang="pug">
.dialog.t-dialog.flex-c-c(
@click="fullScreenClose ? $emit('close') : ''"
:style="{backgroundColor: bgc}"
)
.dialog-mask(@click="aroundClose ? $emit('close') : ''")
slot
</template>
<script>
export default {
name: "t-dialog",
props: {
outAbleClose: Boolean,
fullScreenClose: Boolean,
bgc: { type: String, default: "rgba(0,0,0,0.8)"},
},
created() {
this.setBodyDisalbedScroll()
this.$once('hook:beforeDestroy', () => {
this.$el.style.display = "none";
const isDialogExitInBody = document.querySelector('body .t-dialog');
if (!isDialogExitInBody) {
this.handleScroll('auto')
}
})
},
mounted() {
this.isMounted = true;
let body=document.body || document.documentElement;
body.appendChild(this.$el)
},
methods: {
// 禁止body滚动
setBodyDisalbedScroll() {
document.body.style.overflowY = 'hidden' || document.documentElement.style.overflowY = 'hidden'
}
// 释放body
releaseBody() {
// 当前元素隐藏
this.$el.style.display = "none";
const isDialogExitInBody = document.querySelector('body .t-dialog');
// body下彻底不存在任何dialog时才会释放body; 解决关闭一个弹窗弹出另一个弹窗时,body被提前释放的问题;
if (!isDialogExitInBody) {
document.body.style.overflowY = 'auto' || document.documentElement.style.overflowY = 'auto'
}
}
},
}
</script>
<style lang="stylus" scoped>
.dialog
width 100%
height 101vh // 解决部分机型,顶部少一像素漏白线的问题
background-color rgba(0,0,0,0.8)
position fixed
top 0
left 0
z-index 998
font-size 0.24rem
.dialog-mask
width 100%
height 100vh
position absolute
top 0
left 0
z-index -1
</style>