模拟antd-mobile的slide滑块效果附三种实现方式(高手勿喷)
第一种ts方式(tsx)
import './index.css'
export default class index extends Component {
//定义数据
state = {
//记录当前点击的小球
active: 0,
//小球相关的数据
ballData: [
{
//一号小球距离盒子边的位置
toBoxLeft: 0,
//一号小球的位置(绝对定位)
left: 0,
},
{
toBoxLeft: 0,
left: 0,
},
],
}
render() {
//结构出state中的数据
const { active, ballData } = this.state
//求出两个球中的left值最小值,作为slide滑块的left值
let slideLeft = Math.min.apply(
Math,
ballData.map((tim) => tim.left)
)+10
//求出两个球之间的绝对值作为滑块的长度
let slideWidth = Math.abs(ballData[0].left - ballData[1].left)
return (
<div className='app'>
<div className='top'>
{/* 设置滑块的left值和长度 */}
<div className='slide' style={{ left: slideLeft, width: slideWidth }}></div>
{/* 遍历生成小球 */}
{ballData.map((tim, i) => {
return (
<div
// 根据点击的小球设置其层级,并且设置小球的left值
style={{ zIndex: active === i ? 500 : 5, left: ballData[i].left }}
className='ball'
//点击小球的事件
onTouchStart={(e) => {
//将事件源类型断言成div类型
let ev = e.target as HTMLDivElement
//计算距离小球边的距离
let x = e.changedTouches[0].pageX - ev.offsetLeft
//深拷贝一份小球的数据
let newBallData = [...ballData]
//改变对应的点击位置相对于小球边的距离
newBallData[i].toBoxLeft = x
//将当前点击的索引和更改完后的小球数据设给状态
this.setState({
active: i,
ballData: [...newBallData],
})
}}
//移动小球的回调
onTouchMove={(e) => {
//深拷贝一份小球的数据
let newBallData = [...ballData]
//动态计算出当前操作小球的left(绝对定位)值
let x = e.changedTouches[0].pageX - newBallData[i].toBoxLeft
newBallData[i].left = x < 0 ? 0 : x > 300 ? 300 : x
//将计算完毕的值设置给小球数据
this.setState({
ballData: [...newBallData],
})
}}
>
{/* 设置数值 */}
{((tim.left / 300) * 100) | 0}
</div>
)
})}
</div>
</div>
)
}
}
第一种ts方式(css)
body {
width: 100%;
height: 100%;
}
* {
margin: 0;
padding: 0;
}
.app {
position: absolute;
width: 100%;
height: 100%;
}
.top {
position: relative;
margin-left: 20px;
margin-top: 50px;
width: 80%;
height: 10px;
border-radius: 5px;
background-color: #eee;
}
.slide {
position: absolute;
left: 0;
top: 0;
height: 10px;
width: 0;
background-color: blue;
}
.ball {
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 20px;
height: 20px;
background-color: pink;
border-radius: 10px;
text-align: center;
line-height: 20px;
}
第二种jsx方式(有刻度)jsx部分
import './App.css'
export default class App extends Component {
state = {
//用于遍历小球的依赖数组
ballArr: [1, 2],
//保存当前选中的小球的下标
active: 0,
//保存li的数组
arr: [],
//保存li距离父级的左侧位置
liArr: [],
//保存两只小球所处的下标
indexArr: { ball1: 0, ball2: 0 },
//li最右侧的值
width: 339,
//鼠标距小球左侧的距离
x: 0,
//小球移动时距左侧的位置
left: 0,
//小球1的具体位置(绝对定位值)
boxLeft1: 0,
//小球2的具体位置(绝对定位值)
boxLeft2: 0,
//用于遍历li的依赖数组
initLiArr: [1, 2, 3, 4, 5],
}
componentDidMount() {
//获取所有的li计算出li距其父级的left值
let arr = [...this.refs.top.children].slice(0, 5)
let liArr = arr.map((e) => {
return e.offsetLeft
})
//把获取的li和li距其父级的left值存入状态中
this.setState({ arr, liArr: [...liArr, this.state.width] })
}
render() {
//结构出state中的数据方便使用
const { x, left, boxLeft1, boxLeft2, initLiArr, liArr, arr, ballArr, active, indexArr } =
this.state
return (
<div className='app'>
{/* ref标记父盒子div,用于获取其中的子元素 */}
<div ref={'top'} className='top'>
{/* 遍历渲染所有li */}
{initLiArr.map((e) => (
<li></li>
))}
{/* 遍历渲染小球 */}
{ballArr.map((e, i) => {
return (
<div
style={{
// 点击小球后提高它的层级,使他可以拖动
zIndex: active === i ? 500 : 5,
//设置小球的位置
left: (i === 0 ? boxLeft1 : boxLeft2) - 10,
}}
className='ball'
//触摸小球事件
onTouchStart={(e) => {
this.setState({
//动态计算出鼠标距离小球左边的边距
x: e.changedTouches[0].pageX - e.target.offsetLeft,
//设置当前索引值区分点击的小球
active: i,
})
}}
//小球移动事件
onTouchMove={(e) => {
this.setState({
//移动时记录小球距app盒子的边距
left: e.changedTouches[0].pageX - x,
})
//计算每个盒子与刚刚记录的left值的差的绝对值数组
let arr2 = liArr.map((e) => Math.abs(e - left))
//查出最小绝对值的下标
let index = arr2.findIndex((item) => item === Math.min.apply(Math, arr2))
let newArr = []
this.setState(
(e) => {
return {
//通过刚才的下标动态设置两只小球的位置
[active === 0 ? 'boxLeft1' : 'boxLeft2']: liArr[index],
//通过刚才的下标动态设置两只小球所处的相对下标
indexArr: { ...indexArr, [active === 0 ? 'ball1' : 'ball2']: index },
}
},
() => {
//将两只小球的下标进行排序
newArr = [this.state.indexArr.ball1, this.state.indexArr.ball2].sort(
(a, b) => {
return a - b
}
)
//遍历li的元素数组,把两只小球下标之间的li设置颜色
arr.forEach((e, i) => {
if (i >= newArr[0] && i < newArr[1]) e.style.backgroundColor = '#409eff'
else e.style.backgroundColor = 'pink'
})
}
)
}}
></div>
)
})}
</div>
</div>
)
}
}
第二种jsx(有刻度的部分)css部分
body {
width: 100%;
height: 100%;
}
* {
margin: 0;
padding: 0;
}
.app {
position: absolute;
width: 100%;
height: 100%;
}
.top {
margin: 0 auto;
position: relative;
margin-top: 20px;
width: 90%;
height: 10px;
border: 1px solid #000;
display: flex;
align-items: center;
justify-content: space-around;
}
li {
list-style: none;
width: 18%;
height: 10px;
border-radius: 5px;
background-color: pink;
}
.ball {
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 20px;
height: 20px;
background-color: blue;
border-radius: 50%;
z-index: 5;
}
第三种带小球碰撞效果(tsx部分)
import React, { Component } from 'react'
import './index.css'
export default class index extends Component {
state = {
active: 0,
data: [
{ left: 0, to: 0 },
{ left: 0, to: 0 },
],
}
render() {
const { active, data } = this.state
let left =
Math.min.apply(
Math,
data.map((e) => e.left)
) + 10
let width = Math.abs(data[0].left - data[1].left)
return (
<div className='app'>
<div className='top'>
{/* 将滑块的样式动态赋值 */}
<div className='slide' style={{ width, left }}></div>
{/* 遍历生成小球 */}
{data.map((tim, i) => {
return (
<div
className='ball'
// 将小球的索引和位置动态设置进来
style={{ left: tim.left, zIndex: active === i ? 500 : 5 }}
//触摸小球触发事件
onTouchStart={(e) => {
//拿到小球的dom
let ev = e.target as HTMLDivElement
//动态计算出触摸点距小球边的距离
tim.to = e.changedTouches[0].pageX - ev.offsetLeft
//将计算完的距离更新至状态里
this.setState({ active: i, data: [...data] })
}}
onTouchMove={(e) => {
//动态计算出当前触摸小球的位置
let x = e.changedTouches[0].pageX - tim.to
//约束当前触摸小球的left值
tim.left = x > 300 ? 300 : x < 0 ? 0 : x
//判断碰撞
if (i === 0 && tim.left + 20 > data[1].left) data[1].left = tim.left + 20
if (i === 1 && tim.left - 20 < data[0].left) data[0].left = tim.left - 20
//将当前小球的位置更新值状态里面
this.setState({ data: [...data] })
}}
>
{/* 动态计算出每个小球的进度值 */}
{((tim.left / 300) * 100) | 0}
</div>
)
})}
</div>
</div>
)
}
}
css部分
body {
width: 100%;
height: 100%;
}
* {
margin: 0;
padding: 0;
}
.app {
position: absolute;
width: 100%;
height: 100%;
}
.top {
position: relative;
margin-left: 20px;
margin-top: 50px;
width: 80%;
height: 10px;
border-radius: 5px;
background-color: #eee;
}
.slide {
position: absolute;
left: 0;
top: 0;
height: 10px;
width: 0;
background-color: blue;
}
.ball {
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 20px;
height: 20px;
background-color: pink;
border-radius: 10px;
text-align: center;
line-height: 20px;
}