一、效果:拖动红框,中间位置释放,会判断吸附左边还是右边,会缓慢吸附两边,点击红块会触发事件,点击内容会触发点击内容,互不干扰。流畅度好,效果绝佳。
二、实现方法
1、wxml:
<view class="cont" bindtap="click_cont">
点击会触发点击事件
</view>
<movable-area class="movable-area" id="movableArea">
<movable-view class="movable-view" id="movableView" x="{{x}}px" y="{{y}}px" direction="all" animation="{{movableViewAnimation}}" bindtouchend="movableViewTouchEndHandler" bindchange="movableViewChangeHandler">
<view class="movable-view-main" bindtap="click_fk"></view>
</movable-view>
</movable-area>
2、wxss:
page{
width: 100%;
min-height: 100%;
}
.cont{
padding-top: 180rpx;
width: 100%;
}
.movable-area{
pointer-events:none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.movable-view{
pointer-events:auto;
width: 120rpx;
height: 120rpx;
}
.movable-view-main{
width: 120rpx;
height: 120rpx;
background: #f00;
}
3、js
const app = getApp()
// 这里随便初始化一个变量,记录滑块滑动的时候的 X,Y,之所以这样弄,是因为 setData 性能原因,写这里会好一些,不需要一直 setData 了
let movableTmpX = 0,movableTmpY = 0,movableChangeFlag = false // 是否移动
Page({
data: {
x: 0,
y: 0,
viewportInfo: null, // 视口样式信息
movableAreaInfo: null, // movableArea 样式信息
movableViewInfo: null, // movableView 样式信息
movableViewAnimation: false // movableView 动画是否开启
},
onReady: function () {
// 之所以写在这里,是因为毕竟是初始化样式嘛,写在 onLoad 和 onShow 里边都不能确保样式已经渲染完毕,页面已经准备完毕了,所以写在 onReady 里边初始化
const query = wx.createSelectorQuery().in(this) // 这个 in(this) 在自定义组件内部使用相当有用,这里有没有都行
query.selectViewport().boundingClientRect()
query.selectAll('#movableArea,#movableView').boundingClientRect()
query.exec(res => { // 统一获取样式
let viewportInfo = res[0] // 这里可以获取当前屏幕视口的宽高,如果设置 movableArea 区域的话,这个相当有用,这里没用上
let movableAreaInfo = res[1][0] // 这是获取的 movableArea 样式
let movableViewInfo = res[1][1] // 这里获取的 movableView 样式
let x = movableAreaInfo.width - movableViewInfo.width
let y = movableAreaInfo.height - movableViewInfo.height-60
this.setData({
x,
y,
viewportInfo,
movableAreaInfo,
movableViewInfo
})
setTimeout(() => {
this.setData({
movableViewAnimation: true // 初始化 x y 之后把动画打开,注意:一定要和上边的设置 x y 初始值区分开,不然设置初始值的时候还是会过渡过去,这个就没意义了
})
})
})
},
// 滑动滑块 记录滑块当前的 X,Y
movableViewChangeHandler: function ({
detail
}) {
let {
x,
y,
source
} = detail
if (source === 'touch') { // 这里是只记录手指滑动的 X Y
movableChangeFlag = true // 代表移动过
movableTmpX = x // 记录滑块当前的 X
movableTmpY = y // 记录滑块当前的 Y
}
},
// 手指滑动结束事件
movableViewTouchEndHandler(){
if(movableChangeFlag){ // 移动过才触发,不移动单纯点击不触发
let {
width: movableAreaWidth,
height: movableAreaHeight
} = this.data.movableAreaInfo
let {
width: movableViewWidth,
height: movableViewHeight
} = this.data.movableViewInfo
let tmpX = 0,tmpY = movableTmpY
// 计算当前 X 偏向于左还是右
if (movableTmpX + movableViewWidth / 2 > movableAreaWidth / 2) { // 如果偏向于右侧,则向右靠拢
tmpX = movableAreaWidth - movableViewWidth // 设置 X 为最右侧
} else { // 如果偏向于左侧,则向左靠拢
tmpX = 0 // 设置 X 为最左侧
}
// 第一次设置 X,Y
this.setData({
x: tmpX,
y: tmpY
})
// 第二次设置 X,Y ,实际上这里只需要设置 Y 就行了,因为有问题的是 Y,不过这里把 X 也设置了,防止 X 也有问题
setTimeout(() => {
this.setData({
x: tmpX,
y: tmpY
})
// 其实这里延迟不给就行,但是为了确保可行性,还是协商了 20ms 的延迟,基本上没啥感知
},20)
movableChangeFlag = false // 重置为未移动过
}
},
click_cont(){
console.log("触发内容点击事件")
},
click_fk(){
console.log("触发红块状点击事件")
},
})