微信小程序开发--自定义底部弹框组件

3,979 阅读5分钟

「这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战

1、背景

在移动端开发的过程中经常会遇到需要在页面底部弹框进行一些操作,如下图所示:

底部弹框.gif

这个采用的是微信小程序提供的底部弹框组件 picker

基础库 1.0.0 开始支持,低版本需做兼容处理

从底部弹起的滚动选择器。

但是有些时候我们的底部弹框内容需要自定义,这个时候就需要自己去定义一个底部弹框组件了。

2、实现原理

2.1 实现效果图

GIF 2021-11-12 17-49-34.gif

通过上面的效果图可以看到,当我们点击弹出底部dialog的时候,类似于弹出了一个遮罩层覆盖在当前页面的上方,然后通过一些动画以及样式的效果来进行展示。

底部弹出的对话框已经跟微信官方提供的picker的效果比较接近了。

顶部提供了"取消"、"确定"按钮, 当点击页面空白处、"取消"、"确定" 按钮的时候,弹框都会消失。

至于弹框中间的内容完全可以自己定义,我这个地方放了一个设备列表,当选中设备列表的时候会打勾。

2.2 代码实现

在小程序中自定义组件介绍可以查看官方文档: 小程序官网自定义组件介绍

先看下实现的组件目录结构:

image.png

可以看到一个自定义组件也是由由 json wxml wxss js 4个文件组成,类似于页面。

下面说下自定义组件的步骤:

第一步

在 json 文件中进行自定义组件声明(将 component 字段设为 true 可将这一组文件设为自定义组件):

{
  "component": true
}

第二步

定义wxml、wxss、js,定义模板、样式以及逻辑文件。

bottomdialog.wxml 代码如下所示:

<!--pages/components/bottomdialog/bottomdialog.wxml-->
<view wx:if='{{flag}}'>
  <view class='wrap {{wrapAnimate}}' style='background:rgba(0,0,0,{{bgOpacity}});'></view>
  <view catchtap='hideFrame' class='frame-wrapper {{frameAnimate}}'>
    <view catchtap='catchNone' class='frame'>
      <!-- 头部 -->
      <view class='title-wrapper flex'>
        <view id='oncancle' catchtap='onCancle' class="title-cancle">取消</view>
        <view id='onConfirm' catchtap= 'onConfirm' class="title-confirm">{{frameTitle}}</view>
      </view>
      <!-- 内容 -->
      <slot></slot>
    </view>
  </view>
</view>

布局代码比较简单,定义了样式以及动画, 内容部分包含了头部内容两部分。

头部 主要定义了两个view 取消确定用于处理点击事件。

内容部分使用了一个'slot'插槽,用于自定义内容布局。

下面看下样式bottomdialog.wxss代码:

/* pages/components/bottomdialog/bottomdialog.wxss */
.wrapAnimate{animation: wrapAnimate 0.5s ease-in-out forwards}
@keyframes wrapAnimate{
  0%{}
  100%{background:rgba(0, 0, 0, 0.35);}
}
.wrapAnimateOut{animation: wrapAnimateOut 0.4s ease-in-out forwards}
@keyframes wrapAnimateOut{
  0%{background:rgba(0,0,0,0.35);}
  100%{background:rgba(0, 0, 0, 0);}
}
.frameAnimate{animation: frameAnimate 0.5s ease forwards;}
@keyframes frameAnimate{
  0%{}
  100%{opacity: 1;top:0vh;}
}
.frameAnimateOut{animation: frameAnimateOut 0.4s ease forwards;}
@keyframes frameAnimateOut{
  0%{opacity: 1;top:0vh;}
  100%{opacity: 0;top:100vh;}
}
.frame-wrapper{position: fixed;height:100vh;width:100vw;z-index: 2;top: 50vh;}
.frame{background: #fff;position: absolute;bottom: 0;width: 88.2vw;padding: 5.9vw 5.9vw 0;border-top-left-radius: 20rpx;border-top-right-radius: 20rpx;z-index: 3;}
.title-wrapper{justify-content: space-between;font-size: 4.9vw;color: #4a4a4a;margin-bottom: 5.9vw;}
.title-wrapper>image{width:3.2vw;height:3.2vw;padding:0 5vw;margin-right:-5vw;}
.flex{display: flex;align-items: center;}
.wrap{position: fixed;z-index: 1;top: 0;left: 0;right: 0;bottom: 0;}

.title-cancle{
  font-size: 28rpx;
  color: #000000;
}

.title-confirm{
  font-size: 28rpx;
  color: #4063E7;
}

样式里面用到了一些CSS中的属性,比如:
keyframes

此属性与animation属性是密切相关的。keyframes翻译成中文,是"关键帧"的意思,animation属性则可以利用@keyframes将指定时间段内的动画划分的更为精细一些。

语法结构:

@keyframes animationname {keyframes-selector {css-styles;}}

参数解析:
1.animationname:声明动画的名称。
2.keyframes-selector:用来划分动画的时长,可以使用百分比形式,也可以使用 "from" 和 "to"的形式。
"from" 和 "to"的形式等价于 0% 和 100%。
建议始终使用百分比形式。

ease-in-out

规定以慢速结束的过渡效果(等于 cubic-bezier(0,0,0.58,1))(相对于匀速,开始时快,结束时候间慢,)

opacity

opacity属性指定了一个元素的不透明度。换言之,opacity属性指定了一个元素后面的背景的被覆盖程度。

好了更多关于样式的用法可以到 w3shool上面去查看。

bottomdialog.js文件

// pages/components/bottomdialog/bottomdialog.js
Component({
  properties: {

  },
  data: {
    flag:false,
    wrapAnimate:'wrapAnimate',
    bgOpacity:0,
    frameAnimate:'frameAnimate',
  },
  properties: {
    frameTitle: {
      type: String,
      value: '标题',
    }
  },
  
  methods: {
    showFrame() {
      this.setData({ flag: true, wrapAnimate: 'wrapAnimate', frameAnimate: 'frameAnimate' });
    },
    hideFrame(e) {
      const that= this;
      that.setData({ wrapAnimate: 'wrapAnimateOut', frameAnimate: 'frameAnimateOut' });
      setTimeout(()=>{
        that.setData({ flag: false})
      },400);
    },
    onCancle(e){
      this.hideFrame(e);
      this.triggerEvent('myCancel');
    },
    
    onConfirm(e){
      this.hideFrame(e);
      this.triggerEvent('myConfirm');
    },
    catchNone(){
      //阻止冒泡
    },
    _showEvent() {
      this.triggerEvent("showEvent");
    },
    _hideEvent() {
      this.triggerEvent("hideEvent");
    }
  }
})

逻辑代码主要控制了底部弹框的显示与隐藏,以及头部按钮的事件处理。

好了主要代码就这些,下面看下这个组件怎么使用。

3、组件使用

小程序中使用自定义组件也比较简单,只需要在页面的.json文件中引入自定义的组件就好了。

{
  "usingComponents": { "bottomdialog":"../../../../components/bottomdialog/bottomdialog"}
}

其中"bottomdialog" 可以自己定义,在wxml文件中使用这个组件的时候就用这个名字。

3.1 wxml文件中使用自定义组件

<!--pages/test02/test02.wxml-->

<button bindtap="onClick">点击弹出底部框</button>

<bottomdialog id="bottomFrame" frameTitle="确定" bindmyConfirm="onConfirm" bindmyCancel="onCancel">
<scroll-view scroll-y="true" style="height: 400rpx;" >
  <block wx:if="{{deviceList.length > 0}}">
    <block wx:for="{{deviceList}}">
    <view class="body-content" id="bodyitem" bindtap="chooseItem" data-index="{{index}}">
    <view></view>
      <text>{{item.name}}</text>
      <image wx:if="{{item.isCheck}}" src="/image/administrators_check.png" ></image>
      <image wx:else ></image>
    </view>
  </block>
  </block>
  <block wx:else>
    <view class="nodata">暂无数据</view>
  </block>
</scroll-view>
</bottomdialog>

可以看到在使用 bottomdialog的时候,添加了 frameTitle 属性,以及 bindmyConfirm、bindmyCancel事件处理,另外添加了一个scrool-view 组件进去,最终就会展示在自定义组件的内容部分。

备注:自定义组件中的事件需要在使用的地方用 bind+事件名称 的这种写法来处理,以便在使用自定义组件的页面中,去获取自定义组件中的事件。

3.2 逻辑文件中自定义组件的使用

// pages/test02/test02.js
Page({

    /**
     * 页面的初始数据
     */
    data: {
        deviceList:[{
            name:"设备01",
            isCheck: false
        },
        {
            name:"设备02",
            isCheck: false
        },
        {
            name:"设备03",
            isCheck: false
        },
        {
            name:"设备04",
            isCheck: false
        }
        ,   
        {
        name:"设备05",
        isCheck: false
     }],
    },

    /**
     * 生命周期函数--监听页面加载
     */
    onLoad: function (options) {

    },

    onClick: function(e){
        this.selectComponent('#bottomFrame').showFrame();
    },

    
  chooseItem: function(e){
    let index = e.currentTarget.dataset.index;
    this.setData({
      ['deviceList[' + index + '].isCheck']:!this.data.deviceList[index].isCheck
    })
    console.log("点击了:",this.data.deviceList[index].name)
  },

  //点击取消之后
  onCancel(){
    //恢复选中的数据
    wx.showToast({
        icon:"none",
        title: '点击了取消',
    })
  },
  //点击确定之后
  onConfirm(){
    wx.showToast({
        icon:"none",
        title: '点击了确定',
    })
  },
})

通过 this.selectComponent('#bottomFrame')就可以获取到自定义组件的实例了。

获取到实例之后就可以调用自定义组件中的方法了。

另外我们在页面代码中实现了 onCancelonConfirm方法。

3.3 样式代码

/* pages/test02/test02.wxss */

.body-content{
    display: flex;
    padding: 10rpx;
    height: 50rpx;
    align-items: center;
    justify-content: space-between;
  }

  
.nodata{
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 24rpx;
    color: #bebbbb;
    margin-top: 80rpx;
  }

  image{
    width: 20rpx;
    height:20rpx;
  }

最后工程中用到的图片资源:administrators_check.png

administrators_check.png

好了,希望上面的内容对你有所帮助。