这几天玩保卫萝卜3,对里面的滑杆很感兴趣,在想能不能用vue来实现一个,等我把这个滑杆组件做完,突然想到还可以做成一个算盘
先来看看效果
因为还不懂算盘的用法,而且感觉也很难,目前只做了个样子,并不能用来计算,这里主要讲讲这个算盘的这个珠子在横杠上滑动的组件
组件有下面几种功能:
- 可以自定义珠子的个数
- 可以水平或者垂直来滑动
- 可以调节珠子的大小、颜色,横杠的粗细、颜色
- 在珠子滑动的方向,可以推动剩余的珠子一起滑动,并且会限制滑动的边界
珠子用一个数组来表示
ini
<Drag-Link
direction="column"
beads={[100, 120, 140, 160, 180]} // 传入珠子的位置
bodLength={200}
/>
也可以传入一个数字,用珠子的高度算出数组每一项的位置
typescript
const beads: any = ref([])
if (Array.isArray(props.beads)) {
beads.value = props.beads
} else {
beads.value = [...Array(props.beads).keys()].map(
(i) => i * props.beadsHeight
)
}
接下来就是用绝对定位,把珠子数组的位置定位在横杠上
typescript
// 设置珠子定位
const headsMove = (val: number) => {
const fixed = `${props.direction === 'row' ? 'top' : 'left'}:-${
(props.beadsWidth - props.bodSize) / 2
}px`
const move = `${props.direction === 'row' ? 'left' : 'top'}:${val}px`
return `${move};${fixed}`
}
{beads.value.map((item: number, index: number) => {
return (
<div
class="beads"
style={[headsMove(item)]}
></div>
)
})}
然后就是珠子在横杠上的滑动,通过设置定位的left或者top值就可以
珠子滑动鼠标事件
ini
const onMousedown = (e: any, index: number) => {
e.preventDefault()
const startX = e.clientX
const startY = e.clientY
const offset = beads.value[index] // 初始位置
const onMousemove = (event: any) => {
const dx = event.clientX - startX
const dy = event.clientY - startY
}
}
超出范围判断
这里是判断滑动的距离是不是大于左右两边边界的位置加上左右两边珠子的宽度,比如左边有两个珠子,每个珠子20像素宽,那往左滑40像素就停止了
typescript
const isValidMovement = (
offset: number,
delta: number,
index: number,
beadsHeight: number,
bodLength: number
) => {
const minPosition = index * beadsHeight
const maxPosition = bodLength - (beads.value.length - index) * beadsHeight
return offset + delta >= minPosition && offset + delta <= maxPosition
}
const isValid = isValidMovement(
offset, // 初始位置
props.direction === 'row' ? dx : dy, // 滑动的距离
index, // 当前珠子的坐标
props.beadsHeight, // 珠子沿横杠方向的高度
props.bodLength // 横杠的长度
)
if (!isValid) return
相邻珠子位置推动
首先是判断一下滑动的方向,然后循环一下滑动方向外的珠子,然后把当前滑动的珠子的位置累计在推动的珠子上
ini
const adjustAdjacentBeads = (
index: number,
dx: number,
dy: number
): void => {
const isAdd = props.direction === 'row' ? dx > 1 : dy > 1
const len = isAdd ? beads.value.length - index - 1 : index
for (let i = 0; i < len; i++) {
const adjacentIndex = isAdd ? index + i : i
const adjacentBead = beads.value[adjacentIndex]
if (isAdd) {
if (
adjacentBead >
beads.value[adjacentIndex + 1] - props.beadsHeight
) {
beads.value[adjacentIndex + 1] = adjacentBead + props.beadsHeight
}
} else {
if (
beads.value[index] <
adjacentBead + props.beadsHeight * (index - adjacentIndex)
) {
beads.value[adjacentIndex] =
beads.value[index] - (index - adjacentIndex) * props.beadsHeight
}
}
}
}
// 相邻珠子位置判断
adjustAdjacentBeads(index, dx, dy)
完整代码
typescript
import { defineComponent, ref, PropType } from 'vue'
import './index.scss'
import { useStyle } from './useStyle'
export default defineComponent({
components: {},
props: {
// 方向
direction: {
type: String as PropType<'row' | 'column'>,
default: 'column',
validator: function (value: string) {
// 自定义验证逻辑
return ['row', 'column'].includes(value)
}
},
// 珠子个数
beads: {
type: Number || Array,
default: 2
},
// 珠子宽度
beadsWidth: {
type: Number,
default: 30
},
// 珠子高度
beadsHeight: {
type: Number,
default: 20
},
beadsStyle: {
type: String,
default: ''
},
beadsColor: {
type: String,
default: '#666'
},
bodColor: {
type: String,
default: '#000'
},
// 横轴的宽度
bodSize: {
type: Number,
default: 10
},
// 横轴的长度
bodLength: {
type: Number,
default: 200
},
bodStyle: {
type: String,
default: ''
}
},
emits: [''],
setup(props) {
const { bodCss, bodWrapCss, beadsCss } = useStyle(props)
const beads: any = ref([])
if (Array.isArray(props.beads)) {
beads.value = props.beads
} else {
beads.value = [...Array(props.beads).keys()].map(
(i) => i * props.beadsHeight
)
}
/**
* @description: 设置珠子的定位
* @Date: 2023-11-27 11:23:43
* @param {number} val
*/
const headsMove = (val: number) => {
const fixed = `${props.direction === 'row' ? 'top' : 'left'}:-${
(props.beadsWidth - props.bodSize) / 2
}px`
const move = `${props.direction === 'row' ? 'left' : 'top'}:${val}px`
return `${move};${fixed}`
}
// 判断是否滑动到头
const isValidMovement = (
offset: number,
delta: number,
index: number,
beadsHeight: number,
bodLength: number
) => {
console.log(offset, delta, index, beadsHeight, bodLength)
const minPosition = index * beadsHeight
const maxPosition = bodLength - (beads.value.length - index) * beadsHeight
return offset + delta >= minPosition && offset + delta <= maxPosition
}
// 鼠标按下
const onMousedown = (e: any, index: number) => {
e.preventDefault()
const startX = e.clientX
const startY = e.clientY
const offset = beads.value[index]
const onMousemove = (event: any) => {
const dx = event.clientX - startX
const dy = event.clientY - startY
const isValid = isValidMovement(
offset,
props.direction === 'row' ? dx : dy,
index,
props.beadsHeight,
props.bodLength
)
if (!isValid) return
beads.value[index] = offset + (props.direction === 'row' ? dx : dy)
// 相邻珠子位置判断
adjustAdjacentBeads(index, dx, dy)
}
const onMouseup = () => {
document.removeEventListener('mousemove', onMousemove)
document.removeEventListener('mouseup', onMouseup)
}
document.addEventListener('mousemove', onMousemove)
document.addEventListener('mouseup', onMouseup)
}
/**
* @description: 相邻珠子位置判断
* @Date: 2023-11-27 15:25:05
*/
const adjustAdjacentBeads = (
index: number,
dx: number,
dy: number
): void => {
const isAdd = props.direction === 'row' ? dx > 1 : dy > 1
const len = isAdd ? beads.value.length - index - 1 : index
for (let i = 0; i < len; i++) {
const adjacentIndex = isAdd ? index + i : i
const adjacentBead = beads.value[adjacentIndex]
if (isAdd) {
if (
adjacentBead >
beads.value[adjacentIndex + 1] - props.beadsHeight
) {
beads.value[adjacentIndex + 1] = adjacentBead + props.beadsHeight
}
} else {
if (
beads.value[index] <
adjacentBead + props.beadsHeight * (index - adjacentIndex)
) {
beads.value[adjacentIndex] =
beads.value[index] - (index - adjacentIndex) * props.beadsHeight
}
}
}
}
return () => {
return (
<div class="bodWrap" style={bodWrapCss.value}>
<div class="bod" style={[bodCss.value, props.bodStyle]}>
{beads.value.map((item: number, index: number) => {
return (
<div
class="beads"
style={[beadsCss.value, props.beadsStyle, headsMove(item)]}
onMousedown={(e: any) => {
onMousedown(e, index)
}}
></div>
)
})}
</div>
</div>
)
}
}
})