用vue3实现一个模拟算盘

这几天玩保卫萝卜3,对里面的滑杆很感兴趣,在想能不能用vue来实现一个,等我把这个滑杆组件做完,突然想到还可以做成一个算盘

先来看看效果

因为还不懂算盘的用法,而且感觉也很难,目前只做了个样子,并不能用来计算,这里主要讲讲这个算盘的这个珠子在横杠上滑动的组件

组件有下面几种功能:

  1. 可以自定义珠子的个数
  2. 可以水平或者垂直来滑动
  3. 可以调节珠子的大小、颜色,横杠的粗细、颜色
  4. 在珠子滑动的方向,可以推动剩余的珠子一起滑动,并且会限制滑动的边界

珠子用一个数组来表示

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>
      )
    }
  }
})
相关推荐
y先森13 分钟前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy13 分钟前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu108301891116 分钟前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿1 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡2 小时前
commitlint校验git提交信息
前端
天天进步20153 小时前
Vue+Springboot用Websocket实现协同编辑
vue.js·spring boot·websocket
虾球xz3 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇3 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒3 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员3 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js