Skip to content

自定义微信小程序 picker 组件

约 780 字大约 3 分钟

微信小程序

2025-04-29

微信小程序中的 picker 是一个从底部弹起的滚动选择器,虽然能够满足大多数应用场景, 但如果我们需要让内容不局限于选择器,而是要在这个 抽屉 里面放各式各样的内容, 那官方 api 就不太能够满足这个需求了,我们需要自定义一个 picker 组件。这个效果实现起来并不难,但是涉及到一些基础知识,因此稍加记录。

组件结构

picker 译为 选择器,但我们想要完成的这个组件的内容可能是一些表单、一些文档等,它可以像抽屉一样弹出,因此我们将它 命名为 drawer

为了模仿原本 picker 的效果,可以将 drawer 写为 两个 view ,一个为 黑色蒙版,一个为 抽屉内容,其中内容需要留一个 slot。在弹出抽屉 时,黑色蒙版缓慢渐显,抽屉内容缓慢从底部弹出,所以需要先设定两者动画时间。

/* drawer.wxss */
/* 黑色蒙版 */
.mask {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.5);
  transition: opacity 0.3s ease; /* 透明度时间 */
  z-index: 999;
}
/* 内容 */
.content {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: 40%;
  background: white;
  padding: 20rpx;
  border-radius: 20rpx 20rpx 0 0;
  transition: transform 0.2s ease;  /* 底部弹出时间 */
  z-index: 1000;
}

需要点击黑色蒙版时,关闭抽屉,使用 bindtap 绑定父视图的点击事件,同时使用 catchtap 绑定子视图的点击事件(空事件),阻止事件冒泡到父视图。

<!-- drawer.wxml -->
<!--黑色蒙版-->
<view class="mask" style="opacity: {{maskOpacity}};" wx:if="{{isShow}}" bindtap="closeDrawerHandler">
  <!-- 内容区域 -->
  <view class="content" style="transform: translateY({{transformValue}});" catchtap="handleChildTap">
    <slot></slot>
  </view>
</view>

在 js 中定义 isShow 用老控制 drawer 是否渲染,maskOpacity 用来控制蒙版透明度,transformValue 用来控制抽屉内容移动的距离。

小程序中的 setData 是异步的,因此可以用到 setData 的第二个参数,回调函数( setData 成功之后执行的函数)来执行组件被渲染后的 DOM 操作。或者使用 wx.nextTick() 确保在数据变更引起的视图渲染完成后,再执行 DOM 操作。同时,因为在 wxss 中定义的动画时间为 300 毫秒,因此在关闭抽屉时,需要在动画执行结束后 300 毫秒后, 再设置 isShowfalse

// drawer.js
Component({
  data: {
    isShow: false,  // 控制显示
    maskOpacity: 0,
    transformValue: '100%'
  },

  methods: {
    // 显示 picker
    showDrawerHandler() {
      this.setData({
        isShow: true
      })
      wx.nextTick(() => {
        this.showAnimation();
      });
    },
    closeDrawerHandler() {
      this.hideAnimation();
      setTimeout(() => {
        this.setData({
          isShow: false
        })
      }, 300)
    },

    // 显示动画
    showAnimation() {
      this.setData({
        maskOpacity: 0,
        transformValue: '100%'
      }, () => {
        setTimeout(() => {
          this.setData({
            maskOpacity: 1,
            transformValue: '0'
          })
        }, 50)
      })
    },

    // 隐藏动画
    hideAnimation() {
      this.setData({
        maskOpacity: 0,
        transformValue: '100%'
      })
    }, 
    handleChildTap(){}
  },
})

外部引用

在父视图的 .json 中定义 usingComponents ,将 drawer 组件引入:

  "usingComponents": {
    "drawer": "/components/drawer/drawer"
  }

wxml 中使用 drawer 组件,并给组件一个 ID:

<drawer id="custom-drawer">
    <view>
        抽屉内容
    </view>
</drawer>
<button type="primary" bindtap="showDrawer"></button>

js 中使用 selectComponent 获取组件实例,并调用组件的 showDrawerHandler 方法:

showDrawer(){
    let drawer = this.selectComponent("#custom-drawer")
    picker.showDrawerHandler()
}

最终效果

img.png