这是我参与更文挑战的第22
天,活动详情查看:更文挑战
前言
工作上需要添加个悬浮按钮,但是一般的悬浮按钮都很丑,而且占用固定的位置不是很好用,会感觉很突兀。所以想能不能实现一个可以拖动的悬浮按钮,并且可以在靠近屏幕边缘的时候就自动靠上去。想法觉得还可以,接下来就是实现。
实现
因为项目时间有限,所以直接找现成的会比较好。果然在github上找到现成的组件,直接拿过来用也很不错,效果很好。地址
解析
既然自己不用写了,就看一下是怎么实现的。
其实一个拖拽的悬浮按钮主要由三部分构成:
- 移动触发
- 移动中
- 移动结束
先来看需要哪几个变量来控制这个按钮的移动:
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
}
)
复制代码
最终效果
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END