1. 微信小程序自定义气泡菜单组件

  微信小程序 - 实现气泡菜单组件,点击一个元素在附近弹出一个气泡弹框功能效果(仿微信气泡弹框显示菜单),支持自定义气泡框内的内容,自动计算气泡定位。

1.1. 效果图

  本文 实现了微信小程序中,根据元素内容的宽高自动计算气泡的定位,并且气泡的内容项可以灵活的修改。当点击一个按钮或元素时,会从附近弹出一个小气泡一样的菜单列表,如下所示。
微信小程序 自定义气泡菜单组件-LMLPHP
微信小程序 自定义气泡菜单组件-LMLPHP

1.2. popover组件

微信小程序 自定义气泡菜单组件-LMLPHP

1.2.1. popover-item.js


Component({
  relations: {
    './popover': {
      type: 'parent'
    }
  },
  /**
   * 组件的属性列表
   */
  properties: {
    // 是否有底线
    hasline: {
      type: Boolean,
      value: false
    }
  },

  /**
   * 组件的初始数据
   */
  data: {
    // 每项的高度
    height: 40
  },

  /**
   * 组件的方法列表
   */
  methods: {
    onClick: function() {
      let { index } = this.properties;
      let eventDetail = {
        index: index
      };
      let eventOption = {};
      this.triggerEvent('tap', eventDetail, eventOption);
    }
  }
})

1.2.2. popover-item.json

{
  "component": true,
  "usingComponents": {}
}

1.2.3. popover-item.wxml

<view class='popover-item {{hasline ? "underline" : ""}}' hover-class='popover-item-hover' catchtap='onClick' style='height:{{height}}px;line-height:{{height}}px;'>
  <slot/>
</view>

1.2.4. popover-item.wxss

.popover-item {
  width: 100%;
  font-size: 14px;
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.popover-item-hover {
  background-color: #EEE;
}
.underline{
  border-bottom:1px #EEE solid;
}

1.2.5. popover.js

const { windowWidth, windowHeight } = wx.getSystemInfoSync();

// 三角形箭头的高度
const trangleHeight = 12;

Component({
  relations: {
    './popover-item': {
      type: 'child'
    }
  },

  data: {
    // 当前显隐状态
    visible: false,
    // popover 宽
    pw: 100,
    // popover 高
    ph: 120,
    // popover 距左距离
    px: 0,
    // popover 距上距离
    py: 0,
    // 垂直方向 top/bottom
    vertical: '',
    // 水平方向 left/center/right
    align: ''
  },

  methods: {
    onDisplay: function(e) {
      let self = this;

      if (self.last && self.last === e.id) {
        self.setData({
          visible: !self.data.visible
        });
      } else {
        wx.createSelectorQuery().selectViewport().scrollOffset(view => {
          let { pw, ph, px, py, vertical, align } = self.data;

          let pOverW = (pw - e.width) / 2;

          let offsetL = e.left,
            offsetR = windowWidth - e.right,
            offsetB = windowHeight - e.bottom;

          if (offsetL >= pOverW && offsetR >= pOverW) {
            align = 'center';
            px = e.left - pOverW;
          } else if (offsetL > pOverW && offsetR < pOverW) {
            align = 'left';
            px = windowWidth - (offsetR + pw);
            // 如果向右贴边了,设置一点距离
            if ((windowWidth - pw) == px) px -= 5;
          } else if (offsetL < pOverW && offsetR > pOverW) {
            align = 'right';
            px = e.left;
            // 如果向左贴边了,设置一点距离
            if (px == 0) px += 5;
          }

          if (offsetB >= (ph + trangleHeight)) {
            vertical = 'bottom';
            py = view.scrollTop + e.bottom + trangleHeight;
          } else {
            vertical = 'top';
            py = view.scrollTop + e.top - ph - trangleHeight;
          }

          self.setData({
            visible: true,
            px: px,
            py: py,
            ph: self.getItemsHeight(),
            vertical: vertical,
            align: align
          });
        }).exec();
      }
      // 记录上一次点击的元素
      self.last = e.id;
    },
    onHide: function() {
      this.setData({
        visible: false
      });
    },
    // 获取所有子元素
    getItems: function() {
      return this.getRelationNodes('./popover-item');
    },
    // 获取所有子元素的总高度
    getItemsHeight() {
      return this.getItems().map(item => item.data.height).reduce((a, b) => a + b, 0);
    }
  }

})

1.2.6. popover.json

{
  "component": true,
  "usingComponents": {}
}

1.2.7. popover.wxml

<view
  wx:if='{{visible}}'
  class='popover-view {{vertical}} {{align}}'
  style='width:{{pw}}px;height:{{ph}}px;left:{{px}}px;top:{{py}}px;'>
  <slot />
</view>

1.2.8. popover.wxss

.popover-view {
  position: absolute;
  background-color: white;
  box-shadow: 0 0 2px 2px #ddd;
  border-radius: 6rpx;
}
/* 实现三角形 */
.popover-view::before {
  position: absolute;
  display: inline-block;
  width: 0;
  height: 0px;
  content: '';
  border-style: solid;
  border-width: 6px;
  border-color: #fff #fff transparent transparent;
  box-shadow: 2px -2px 2px #ddd;
}
/* 上 */
.popover-view.top::before {
  bottom: -6px;
  transform: rotate(135deg);
}
/* 下 */
.popover-view.bottom::before {
  top: -6px;
  transform: rotate(-45deg);
}
/* 左 */
.popover-view.left::before {
  right: 20px;
}
/* 中 */
.popover-view.center::before {
  left: 47px;
}
/* 右 */
.popover-view.right::before {
  left: 20px;
}

1.3. 使用代码

1.3.1. bubbleMenu.js

Page({

  data: {},

  // 获取气泡菜单
  onReady() {
    this.popover = this.selectComponent('#popover')
  },

  /**
   * 打开气泡菜单
   * @description 获取当前元素计算位置
   * @param {Object} e - 元素标识(事件对象)
   * @return void
   */
  open(e) {
    // 获取元素的坐标信息
    wx.createSelectorQuery().select('#' + e.target.id).boundingClientRect(res => {
      this.popover.onDisplay(res)
    }).exec()
  },

  /**
   * 隐藏气泡菜单
   * @description 调用此即可完成隐藏
   * @retrun void
   */
  close() {
    this.popover.onHide()
  },

  /**
   * 自定义A
   * @description 示例用法
   * @param {Object} e - 事件对象
   * @return void 
   */
  onClickA(e) {
    console.log('onClick A ', e)
    // 业务逻辑
    wx.showToast({
      title: '菜单 A'
    })
    // 隐藏气泡菜单
    this.close()
  },

  /**
   * 自定义B
   * @description 示例用法
   * @param {Object} e - 事件对象
   * @return void 
   */
  onClickB(e) {
    console.log('onClick B ', e)
    // 业务逻辑
    wx.showToast({
      title: '菜单 B'
    })
    // 隐藏气泡菜单
    this.close()
  },

  /**
   * 自定义C
   * @description 示例用法
   * @param {Object} e - 事件对象
   * @return void 
   */
  onClickC(e) {
    console.log('onClick C ', e)
    // 业务逻辑
    wx.showToast({
      title: '菜单 C'
    })
    // 隐藏气泡菜单
    this.close()
  },

})

1.3.2. bubbleMenu.json

{
  "usingComponents": {
    "popover": "/component/popover/popover",
    "popover-item": "/component/popover/popover-item"
  }
}

1.3.3. bubbleMenu.wxml

<!-- 整体用法
1. 复制根目录下 components 文件夹内的 popover 文件夹到你自己的小程序代码里。
2. 在需要使用的小程序页面对应的 json 文件里添加引入组件,如下所示。
"usingComponents": {
    "popover": "/component/popover",
    "popover-item": "/component/popover-item"
}
3. 注意一定要给触发元素标记id,具体示例用法如下。
-->
<!-- 示例用法 -->
<view>
    <button id="btn" bindtap="open">弹出气泡菜单</button>
    <!-- 注意不要使用块级(如view)元素!!! -->
    <!-- 只要标记id便可在任意位置正常显示 -->
    <text id="txt" bindtap="open" style="margin: 100rpx">任意位置</text>
</view>
<!-- END -->
<!-- 气泡菜单 -->
<popover id='popover'>
    <!-- hasline: 是否有下划线 -->
    <popover-item bindtap="onClickA" hasline>菜单A</popover-item>
    <popover-item bindtap="onClickB" hasline>菜单B</popover-item>
    <popover-item bindtap="onClickC">菜单C</popover-item>
</popover>
<!-- END -->



04-25 20:36