用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>
      )
    }
  }
})
相关推荐
0思必得017 小时前
[Web自动化] Selenium处理动态网页
前端·爬虫·python·selenium·自动化
东东51618 小时前
智能社区管理系统的设计与实现ssm+vue
前端·javascript·vue.js·毕业设计·毕设
catino18 小时前
图片、文件的预览
前端·javascript
layman052820 小时前
webpack5 css-loader:从基础到原理
前端·css·webpack
半桔20 小时前
【前端小站】CSS 样式美学:从基础语法到界面精筑的实战宝典
前端·css·html
AI老李20 小时前
PostCSS完全指南:功能/配置/插件/SourceMap/AST/插件开发/自定义语法
前端·javascript·postcss
_OP_CHEN20 小时前
【前端开发之CSS】(一)初识 CSS:网页化妆术的终极指南,新手也能轻松拿捏页面美化!
前端·css·html·网页开发·样式表·界面美化
啊哈一半醒20 小时前
CSS 主流布局
前端·css·css布局·标准流 浮动 定位·flex grid 响应式布局
PHP武器库20 小时前
ULUI:不止于按钮和菜单,一个专注于“业务组件”的纯 CSS 框架
前端·css
电商API_1800790524720 小时前
第三方淘宝商品详情 API 全维度调用指南:从技术对接到生产落地
java·大数据·前端·数据库·人工智能·网络爬虫