[vue组件] 自定义下拉模态框

238 阅读2分钟

首先,布局上分为显示和隐藏两个部分。 隐藏块是没有初始高度的,通过触发显示计算出高度

接下来,我们看计算高度需要考虑的因素有哪些。(异形屏,页面组件外边上下部分)

实现思路

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>