用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>
      )
    }
  }
})
相关推荐
杨荧2 分钟前
【JAVA毕业设计】基于Vue和SpringBoot的服装商城系统学科竞赛管理系统
java·开发语言·vue.js·spring boot·spring cloud·java-ee·kafka
minDuck4 分钟前
ruoyi-vue集成tianai-captcha验证码
java·前端·vue.js
小政爱学习!25 分钟前
封装axios、环境变量、api解耦、解决跨域、全局组件注入
开发语言·前端·javascript
魏大帅。30 分钟前
Axios 的 responseType 属性详解及 Blob 与 ArrayBuffer 解析
前端·javascript·ajax
花花鱼36 分钟前
vue3 基于element-plus进行的一个可拖动改变导航与内容区域大小的简单方法
前端·javascript·elementui
k093340 分钟前
sourceTree回滚版本到某次提交
开发语言·前端·javascript
EricWang13581 小时前
[OS] 项目三-2-proc.c: exit(int status)
服务器·c语言·前端
September_ning1 小时前
React.lazy() 懒加载
前端·react.js·前端框架
web行路人1 小时前
React中类组件和函数组件的理解和区别
前端·javascript·react.js·前端框架
超雄代码狂2 小时前
ajax关于axios库的运用小案例
前端·javascript·ajax