我正在参加「掘金·启航计划」
前言
创建该组件的缘由:有一天晚上夜不能寐,想着想着就到了如何做项目中来,然后我就思考如何在自己的博客中,比较特别的展示曾经做过的项目呢。然后就在那构思几个卡片怎么放在一起,然后可以推拉什么的,然后突然脑中灵光一闪,就想到了创建一个拼图滑块,又能玩,又能展示项目。
使用技术:react
+ ts
组件文档:slider-puzzle
组件源码:lhh-ui
这是我的个人博客(起步阶段,还不是很完善)
码上掘金效果展示
实现过程
我这里将整个组件拆分成三个模块: 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 作者:滑动变滚动的蜗牛