首先,布局上分为显示和隐藏两个部分。 隐藏块是没有初始高度的,通过触发显示计算出高度
接下来,我们看计算高度需要考虑的因素有哪些。(异形屏,页面组件外边上下部分)
实现思路
1.异形屏头部和底部
// 获取异形屏头部和底部
getNotchHeight().then(data => {
this.ASA = data
})
2.设置遮罩距顶部距离和高度
getBoxStyle () {
// 设置遮罩高度
this.defatulHeight = document.body.clientHeight - this.pxToRemPx(49) - this.getElemPos(this.$refs.selecterBox).y - this.$refs.selecterBox.offsetHeight
// 设置遮罩距顶部距离
this.boxTop = this.$refs.selecterBox.offsetHeight
},
注:1.由于是移动端手机适配的原因,需要获取动态的盒子高度
/**
* 转换固定px值到自适应px值
* @param {String} val 数值
* @return {String}
*/
pxToRemPx (val) {
return val * parseFloat(document.documentElement.style.fontSize) / 10
},
2.同时需要考虑组件在页面的相对位置。
/**
* 获取元素相对位置
* @param {Object} obj 元素
* @return {Object}
*/
getElemPos (obj) {
// 基础位置数据
let pos = {
// 顶部距离
'top': 0,
// 左边距离
'left': 0
}
if (obj.offsetParent) {
while (obj.offsetParent) {
pos.top += obj.offsetTop
pos.left += obj.offsetLeft
obj = obj.offsetParent
}
} else if (obj.x) {
pos.left += obj.x
} else if (obj.x) {
pos.top += obj.y
}
return { x: pos.left, y: pos.top }
},
到了这里,我们通过执行 getBoxStyle() 获得了组件的 高度 距顶部距离的值。组件位置部分已经完成。接下来,就需要考虑盒子展开的动效实现方法。
1.盒子包含2个部分,盒子需要嵌套的内容和对内容进行控制的按钮
//- 模态框
.selecter-box(ref="selecterBox" :style="boxStyle")
.selecter-content
//- TODU
.footer
.clear 清空
.confirm 完成
2.最外层的三个元素,决定了模块展开的交互结果。需要注意的地方我在代码中注明
.drap-pop
display flex
position relative
// 1.如盒子无高度,高度自动充满竖屏
align-items stretch
// 2.子元素从上到下,竖屏排队
flex-direction column
// 3.如有多余宽度,元素所占面积自动膨胀到最大
flex-shrink 0
box-shadow 0rem 0.4rem 0.4rem 0rem #eee
z-index 99
.selecter-box
display flex
align-items stretch
flex-direction column
position absolute
width 100%
left 0
// 4.元素最贴近用户
z-index 999
// 5.如有高度,0.5秒速率逐渐展示。
transition height 0.5s
// 6.超出内容滚动
最后,需要一个函数触发 整个模态框显示
/**
* 显示selecter
* @param {String} val 点击的bar
*/
showSelecter(val)()
课后题:实现为模态框加一个遮罩。
实现代码
<template lang="pug">
.drap-pop
//- 选项栏
.bar
.bar-item(@click="showSelecter('选项')" :class="{'current': selectedBar === '选项'}")
.bar-label 选项
.iconfont.icon-jiantou
//- 模态框
.selecter-box(ref="selecterBox" :style="boxStyle")
.selecter-content
//- TODU
.footer
.clear 清空
.confirm 完成
</template>
<script>
import { getNotchHeight } from '~/plugins/utils'
export default {
data () {
return {
// 下拉部分高度
defatulHeight: 0,
// 下拉部分top偏移量
boxTop: 0,
// 控制组件是否显示
showBox: false,
// 位置信息
ASA: {
top: 0,
bottom: 0
}
}
},
computed: {
/**
* 下拉模态框定位位置
* @return {Object} 覆盖模态框样式
*/
boxStyle () {
return {
height: this.showBox ? this.defatulHeight - this.ASA.top + 'px' : '0rem',
top: this.boxTop + this.pxToRemPx(44) + 'px'
}
}
},
mounted () {
// 获取异形屏头部和底部
getNotchHeight().then(data => {
this.ASA = data
})
this.getBoxStyle()
},
methods: {
/**
* 获取盒子高度
*/
getBoxStyle () {
// 设置遮罩高度(页面高度-底部高度(49)-组件相对高度-盒子内兄弟高度)
this.defatulHeight = document.body.clientHeight - this.pxToRemPx(49) - this.getElemPos(this.$refs.selecterBox).y - this.$refs.selecterBox.offsetHeight
// 设置遮罩距顶部距离
this.boxTop = this.$refs.selecterBox.offsetHeight
},
/**
* 转换固定px值到自适应px值
* @param {String} val 数值
* @return {String}
*/
pxToRemPx (val) {
return val * parseFloat(document.documentElement.style.fontSize) / 10
},
/**
* 获取元素相对位置
* @param {Object} obj 元素
* @return {Object}
*/
getElemPos (obj) {
// 基础位置数据
let pos = {
// 顶部距离
'top': 0,
// 左边距离
'left': 0
}
if (obj.offsetParent) {
while (obj.offsetParent) {
pos.top += obj.offsetTop
pos.left += obj.offsetLeft
obj = obj.offsetParent
}
} else if (obj.x) {
pos.left += obj.x
} else if (obj.x) {
pos.top += obj.y
}
return { x: pos.left, y: pos.top }
},
/**
* 显示selecter
* @param {String} val 点击的bar
*/
showSelecter (val) {
if (this.selectedBar !== val) {
this.selectedBar = val
this.showBox = true
} else {
this.selectedBar = ''
this.showBox = false
}
}
}
}
</script>
<style lang="stylus">
.drap-pop
display flex
position relative
// 1.如盒子无高度,高度自动充满竖屏
align-items stretch
// 2.子元素从上到下,竖屏排队
flex-direction column
// 3.如有多余宽度,元素所占面积自动膨胀到最大
flex-shrink 0
box-shadow 0rem 0.4rem 0.4rem 0rem #eee
z-index 99
.bar
height 4.4rem
display flex
width 100%
flex-shrink 0
background-color #ffffff
.bar-item
flex-grow 1
display flex
justify-content center
align-items center
.bar-label
color rgb(51, 51, 51)
font-size 1.5rem
.icon-jiantou
font-size 1rem
color #cccccc
margin-left .2rem
&.current
.bar-label
color #FA705B
.icon-jiantou
transform rotate(180deg)
color #FA705B
.selecter-box
display flex
align-items stretch
flex-direction column
position absolute
width 100%
left 0
// 4.元素最贴近用户
z-index 999
// 5.如有高度,0.5秒速率逐渐展示。
transition height 0.5s
// 6.超出内容滚动
overflow-y auto
.selecter-content
display flex
position relative
flex-grow 1
background #fff
box-shadow 0rem .4rem .9rem -0.3rem #EEE inset
overflow hidden
.footer
height 5rem
background-color #fff
flex-shrink 0
display flex
align-items center
justify-content space-between
padding 0rem 1.5rem
box-shadow 0rem .4rem .9rem 0.3rem #EEE
div
width 8rem
height 3.6rem
border-radius .4rem
line-height 3.6rem
text-align center
font-size 1.4rem
&.confirm
color #fff
background-color rgb(250, 112, 91)
&.clear
color #999999
</style>