1. 微信小程序自定义气泡菜单组件
微信小程序 - 实现气泡菜单组件,点击一个元素在附近弹出一个气泡弹框功能效果(仿微信气泡弹框显示菜单),支持自定义气泡框内的内容,自动计算气泡定位。
1.1. 效果图
本文 实现了微信小程序中,根据元素内容的宽高自动计算气泡的定位,并且气泡的内容项可以灵活的修改。当点击一个按钮或元素时,会从附近弹出一个小气泡一样的菜单列表,如下所示。
1.2. popover组件
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 -->