Bootstrap

微信小程序movable-area+movable-view,自动吸附两边

一、效果:拖动红框,中间位置释放,会判断吸附左边还是右边,会缓慢吸附两边,点击红块会触发事件,点击内容会触发点击内容,互不干扰。流畅度好,效果绝佳。

二、实现方法

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("触发红块状点击事件")
  },
})
;