移动端悬浮按钮组件的解析(suspend-button)

这是我参与更文挑战的第22天,活动详情查看:更文挑战

前言

工作上需要添加个悬浮按钮,但是一般的悬浮按钮都很丑,而且占用固定的位置不是很好用,会感觉很突兀。所以想能不能实现一个可以拖动的悬浮按钮,并且可以在靠近屏幕边缘的时候就自动靠上去。想法觉得还可以,接下来就是实现。

实现

因为项目时间有限,所以直接找现成的会比较好。果然在github上找到现成的组件,直接拿过来用也很不错,效果很好。地址

解析

既然自己不用写了,就看一下是怎么实现的。

其实一个拖拽的悬浮按钮主要由三部分构成:

  1. 移动触发
  2. 移动中
  3. 移动结束

先来看需要哪几个变量来控制这个按钮的移动:

1.构造函数

constructor(props) {
    super(props)
    this.state = {
        oLeft: "", // 相对视窗的左边距离
        oTop: "" // 相对视窗的顶部距离
    }
    this.$vm = null // 悬浮按钮
    this.moving = false // 移动状态

    this.oW = null // 悬钮距离
    this.oH = null

    this.htmlWidth = null // 页面宽度
    this.htmlHeight = null

    this.bWidth = null // 悬钮宽度
    this.bHeight = null

    this.click = null
}
复制代码

2.移动触发

// 移动触发
onTouchStart(e) {
    e = e.touches[0]
    this.click = true

    // e.clientX ==> 触点相对于可见视区(visual viewport)左边沿的的X坐标. 不包括任何滚动偏移.
    // this.$vm.getBoundingClientRect().left ==> 相对于视窗的左边位置
    this.oW = e.clientX - this.$vm.getBoundingClientRect().left
    this.oH = e.clientY - this.$vm.getBoundingClientRect().top

    this.htmlWidth = document.documentElement.clientWidth
    this.htmlHeight = document.documentElement.clientHeight

    this.bWidth = this.$vm.offsetWidth
    this.bHeight = this.$vm.offsetHeight

    let oLeft = e.clientX - this.oW
    let oTop = e.clientY - this.oH

    // 记录按钮移动开始的初始位置
    this.setState({
        oLeft,
        oTop
    })

    this.moving = true
}
复制代码

3.移动中

// 开始移动
onTouchMove(e) {
    this.$vm.className = "t-suspend-button"
    this.moving && this.onMove(e)
}

// 移动中
onMove(e) {
    e = e.touches[0]
    this.click = false

    // 左侧距离
    let oLeft = e.clientX - this.oW
    let oTop = e.clientY - this.oH

    // 左边和右边临界点的重置
    if (oLeft < 0) {
        oLeft = 0
    } else if (oLeft > this.htmlWidth - this.bWidth) {
        oLeft = this.htmlWidth - this.bWidth
    }
    // 上边和右边临界点的重置
    if (oTop < 0) {
        oTop = 0
    } else if (oTop > this.htmlHeight - this.bHeight) {
        oTop = this.htmlHeight - this.bHeight
    }

    this.setState({
        oLeft,
        oTop
    })
}
复制代码

4.移动结束

// 移动结束
onTouchEnd(e) {
    this.moving = false

    // 添加了一个延迟动画,让靠边看上去没那么突兀
    this.$vm.className = this.$vm.className + " t-suspend-button-animate"

    // 左侧距离
    // 计算与左边的距离,如果超过一半则靠到右边,少于一半则返回左边
    let oLeft = this.state.oLeft
    if (oLeft < (this.htmlWidth - this.bWidth) / 2) {
        oLeft = 0
    } else {
        oLeft = this.htmlWidth - this.bWidth
    }

    if (this.click) {
        this.props.onClick()
    }
    // }
    // 和左边同理,计算上面的距离
    // if(oTop < 0) {
    //   oTop = 0
    // } else if (oTop > this.htmlHeight - this.bHeight) {
    //   oTop = this.htmlHeight - this.bHeight
    // }

    this.setState({
        oLeft
        // oTop
    })
}
复制代码

5.渲染组件

// 通过fix定位来显示按钮的最终位置
<span
    className="t-suspend-button"
    ref={$vm => (this.$vm = $vm)}
    onTouchStart={e => this.onTouchStart(e)}
    onTouchMove={e => this.onTouchMove(e)}
    onTouchEnd={e => this.onTouchEnd(e)}
    style={{
      left: `${this.state.oLeft}px`,
      top: `${this.state.oTop}px`,
      ...style
    }}
  >
    {img && <img src={img} alt="" />}
</span>
复制代码

6.最后

// 给该dom的监听事件添加passive属性使得动画效果更佳丝滑
this.$vm.addEventListener(
  "touchmove",
  e => {
    if (e.cancelable) {
      e.preventDefault()
    }
  },
  {
    passive: false
  }
)
复制代码

最终效果

111.gif

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享