用 react 封装一个拼图滑块组件

我正在参加「掘金·启航计划」

前言

创建该组件的缘由:有一天晚上夜不能寐,想着想着就到了如何做项目中来,然后我就思考如何在自己的博客中,比较特别的展示曾经做过的项目呢。然后就在那构思几个卡片怎么放在一起,然后可以推拉什么的,然后突然脑中灵光一闪,就想到了创建一个拼图滑块,又能玩,又能展示项目。

使用技术:react + ts

组件文档:slider-puzzle

组件源码:lhh-ui

这是我的个人博客(起步阶段,还不是很完善)

用 react 封装一个拼图滑块组件

码上掘金效果展示

实现过程

我这里将整个组件拆分成三个模块: SilderPuzzle, SliderPuzzle.Item, SliderPuzzle.Canvas

  • SliderPuzzle 作为整个组件的最外层,负责处理拼图块的位置,格子二维数组等信息。

  • SliderPuzzle.Item 代表的是每个拼图块,用来控制拼图块的移动,可以在其中填入 children

  • SliderPuzzle.Canvas 所展示出来的拼图(可以是默认的随机字母,或者是传入的图片)。

SliderPuzzle的初始化

计算出拼图块的随机位置坐标,以及保存格子的二维信息数组。

const initPuzzle = () => {
  // 根据 n*n 拼图算出总数
  const total = size * size
  // 创建拼图块的随机数组
  const randomArr = randomNumberArray(size)
  // 获取空格索引
  const initSpaceIndex = randomArr.findIndex(v => v === total)
  const nullRow = Math.floor(initSpaceIndex / size), nullCol = initSpaceIndex % size
  setPuzzleGridArr(randomArr)
  // 生成当前的格子的二维数组信息
  setGridArr(createTwoArray(size, size, (rowNum, colNum) => (
    rowNum === nullRow && nullCol === colNum ? 0 : randomArr[Math.floor(rowNum * size + colNum)]
  )))
  setCtxState({ initSpaceIndex })
}

创建拼图块的位置不是随便打乱就行的,还需要判断是否能重新组成拼图块。

而计算方式大致就是先把 n*n 网格铺平,然后计算逆序数之和(N),当 n 是奇数时 N 必须为偶数,当 n 为偶数时,(N + 空格所在的行数) 必须为偶数。更详细的介绍建议搜搜别的文章好点,这里不过多叙述。

组件间传递数据

这里使用 useContext 向其他的组件传递上面初始化的数据,下面简单书写一个例子模拟代码中的结构。

// 这里是简单例子
const SliderPuzzleCtx = React.createContext({
  gridArr: undefined,
  puzzleGridArr: undefined,
});
const SliderPuzzle = () => {
  return (
    <SliderPuzzleCtx.Provider 
      value={{
        gridArr, // 格子的二维数组信息
        puzzleGridArr, // 拼图块的随机数组
      }}
    >
      {children}
    </SliderPuzzleCtx.Provider>
  )
}

const SliderPuzzleItem = () => {
  // 获取传递进来的数据
  const { gridArr } = useContext(SliderPuzzleCtx)
  return (
    <div></div>
  )
}

const SliderPuzzleCanvas = () => {
  // 获取传递进来的数据
  const { puzzleGridArr } = useContext(SliderPuzzleCtx)
  return (
    <div></div>
  )
}

SliderPuzzle.Item 控制拼图块的移动

这里采用封装好的 useTouchEvent 钩子来处理触摸事件(兼容 pc移动端)。

该钩子的具体描述可以看这篇往期文章

import useTouchEvent from '../hooks/use-touch-event';

const SliderPuzzleItem = () => {
  /** 当前可移动的方向 */
  const moveDirection = useMemo(() => (
    checkDirectionVal(gridArr!, info.rowNum, info.colNum)
  ), [gridArr, info.rowNum, info.colNum])

  const {info: _info, onTouchFn} = useTouchEvent({
    onTouchStart() {
      // 触摸开始记录起始位置
      setInfo({startX: info.x, startY: info.y, duration: 0})
    },
    onTouchMove() {
      // 根据拼图块所处的位置,计算出拼图块的某一方向是否可以移动
      const {directionX, directionY} = checkDirectionXY(_info.deltaX, _info.deltaY)
      if(directionX === moveDirection) {
        setInfo({x: range(_info.deltaX, -grid.w - gap, grid.w + gap) + info.startX})
      }
      if(directionY === moveDirection) {
        setInfo({y: range(_info.deltaY, -grid.h - gap, grid.h + gap) + info.startY})
      }
    },
    onTouchEnd(e) {
      // 触摸结束,判断拼图的移动是去到新位置还是回归原位,并触发回调改变全局数据。
      // 代码较多...
    },
    isDisable: {
      all: !moveDirection
    },
    isStopPropagation: true
  })
  
  return (
    <div
      style={{
        transitionDuration: info.duration + 's',
        transform: `translate(${info.x}px, ${info.y}px)`,
      }}
    >
    </div>
  )
}

SliderPuzzle.Canvas 绘画拼图块

该组件是根据拼图块对应位置和自身的索引,算出对应的 x , y , w , h 并应该展示出那部分拼图块。

const SliderPuzzleCanvas = () => {
  // ...
  return (
    <div>
       <canvas
        ref={canvasRef}
        width={grid.w * ratio}
        height={grid.h * ratio}
        style={{
          display: 'block',
          width: grid.w + 'px',
          height: grid.h + 'px',
        }}
        onContextMenu={(e) => {
          e.preventDefault()
        }}
      />
    </div>
  )
}

原文链接:https://juejin.cn/post/7246416857309085753 作者:滑动变滚动的蜗牛

(0)
上一篇 2023年6月20日 上午10:15
下一篇 2023年6月20日 上午10:26

相关推荐

发表回复

登录后才能评论