前言
大家好,我是馋嘴的猫。在上一篇React-Draggable的实战教程中,我们学会了如何实现 IDE 的拖动面板效果。
在这一篇文章中,我们将会继续学习另外一个 IDE 的高频交互操作:画布滑动。
相信开发同学在实现前端需求时,对 figma 应该不陌生。
在使用 figma 的时候,我们可以通过鼠标滚轮或者触控板来实现画布的滑动,这种交互方式非常的流畅,也对画布容器放置多画布,提供了更好的支持,完美支持画布区域比屏幕更高更宽的场景。
今天,我们将会借助 CSS 的 matrix 属性,在页面实现上述效果,请跟随下文,请一起学起来吧~
需求
通过 CSS 的 matrix 属性,在 Next.js 项目,实现一个可滑动的 IDE 画布。
使用框架
仓库地址
在线演示地址
实现步骤
- 我们这次的教程,会以上篇 React-Draggable 的工程为基础工程,所以,需先 clone 下此 demo 的仓库,重命名目录,安装依赖,并执行 dev 服务,命令如下所示:
bash
git clone git@github.com:rqzheng2015/react-draggable-nextjs-demo.git
mv react-draggable-nextjs-demo react-matrix-scroll-demo
cd react-matrix-scroll-demo
pnpm i
pnpm dev
- 打开
app/components/canvas_container.tsx
, 修改画布为固定宽高 375*667, 并将外层容器加上overflow:hidden
样式,以及加上 ref 引用。
tsx
'use client'
import { useCallback, useEffect, useRef, useState } from 'react'
const CANVAS_WIDTH = 375
const CANVAS_HEIGHT = 667
export default function CanvasContainer() {
const containerRef = useRef<HTMLDivElement | null>(null) // 创建一个ref
// 省略其他原有代码
return (
<div className={'flex-1 overflow-hidden relative p-[16px]'} ref={containerRef}> <div
className={'bg-white m-auto relative flex items-center justify-center'}
style={{
width: `${CANVAS_WIDTH}px`,
height: `${CANVAS_HEIGHT}px`,
}}
>
<p className={'text-[20px]'}>{`This is project's home page.`}</p>
</div>
</div>
)
}
此时可以看到画布已经是固定宽高了,但是因为 footer 高度太高,会阻拦画布的正常显示。
所以,我们接下来,要为画布容器添加滑动支持,使得画布可以正常显示出来~
- 在 div 容器下,新增一层 matrix 样式层,供后面画布滑动使用。
tsx
const [position, setPosition] = useState({ x: 0, y: 0 })
return (
<div className={'flex-1 overflow-hidden relative p-[16px]'} ref={containerRef}>
{/* 新增的 div 容器 */}
<div
className={'w-full relative'}
style={{
transform: `matrix(1, 0, 0, 1, ${position.x}, ${position.y})`,
}}
>
<div
className={'bg-white m-auto relative flex items-center justify-center'}
style={{
width: `${CANVAS_WIDTH}px`,
height: `${CANVAS_HEIGHT}px`,
}}
>
<p className={'text-[20px]'}>{`This is project's home page.`}</p>
</div>
</div>
</div>
)
在这一步骤中,我们正式引入了 matrix 样式,并使用 matrix(1, 0, 0, 1, ${position.x}, ${position.y})
来实现。
matrix的文档可点此查看
那这一段样式,和 matrix 属性 ,以及画布如何实现滑动,它们三者,又有什么关联呢,且听我细细解说:
a. matrix
函数接受六个参数,分别是:a
、b
、c
、d
、e
、f
。这些参数可以创建一个复杂的2D转换。
b. matrix(a, b, c, d, e, f)
的效果等同于应用以下变换:
tsx
newx = a * oldx + c * oldy + e
newy = b * oldx + d * oldy + f
c. 在我们的代码实现:matrix(1, 0, 0, 1, ${position.x}, ${position.y})
,这代表着:
- a 和 d 是 1,所以元素的宽度和高度不会改变。
- b 和 c 是 0,所以元素不会倾斜或旋转。
- e 和 f 是 <math xmlns="http://www.w3.org/1998/Math/MathML"> p o s i t i o n . x 和 {position.x} 和 </math>position.x和{position.y},所以元素会沿着 X 轴和 Y 轴移动,移动的距离取决于 position.x 和 position.y 的值。
d. 因此,这个 matrix 属性,就非常满足我们今天的需求了:滑动画布!
说到底,画布的滑动实现,就是让浏览器,一直根据鼠标的 X 轴和 Y 轴的滑动方向和距离,来移动画布嘛。
OK,让我们在下个步骤继续实现。
- 实现
handleWheel
函数,并在画布容器上挂载对wheel
事件的监听。
tsx
const handleWheel = useCallback((event: WheelEvent) => {
event.preventDefault()
const { deltaX, deltaY } = event
const MIN_X = -CANVAS_WIDTH / 2 - 50
const MAX_X = CANVAS_WIDTH / 2 + 50
const MIN_Y = -CANVAS_HEIGHT / 2 - 50
const MAX_Y = CANVAS_HEIGHT / 2 + 50
setPosition(prev => {
// 计算新的位置
const newX = prev.x - deltaX
const newY = prev.y - deltaY
// 检查新的位置是否在范围内
const inRangeX = newX >= MIN_X && newX <= MAX_X
const inRangeY = newY >= MIN_Y && newY <= MAX_Y
return {
x: inRangeX ? newX : newX < MIN_X ? MIN_X : MAX_X,
y: inRangeY ? newY : newY < MIN_Y ? MIN_Y : MAX_Y,
}
})
}, [])
useEffect(() => {
const container = containerRef.current
if (container) {
container.addEventListener('wheel', handleWheel) // 在元素上添加事件监听器
}
return () => {
if (container) {
container.removeEventListener('wheel', handleWheel) // 在元素上移除事件监听器
}
}
}, [containerRef.current])
上面的代码实现了以下几个功能点: a. 为画布容器的 wheel 事件挂上了监听,这样,当用户在画布容器的区域使用鼠标或触摸板滑动,则会触发 handleWheel事件 b. 在 handleWheel 事件获取 wheel 事件的 deltaX
, deltaY
c. 计算 MIN_X, MAX_X, MIN_Y, MAX_Y,用于限制画布的滑动范围 d. 根据 deltaX, deltaY 计算新的位置,并检查新的位置是否在范围内,如果不在范围内,则取边界值 e. 通过 setPosition 更新画布的滑动位置 f. 画布容器通过样式 transform: matrix(1, 0, 0, 1, ${position.x}, ${position.y})
来实现它的滑动
- 至此,全部流程完成,可以看下成果啦~
可以看到,画布已经可以通过鼠标滚轮或触控板来滑动了,效果非常流畅,和 figma 的效果几乎一样。
总结
在这篇教程中,我们学习了如何通过 CSS 的 matrix
属性,来实现画布容器的滑动效果。 它的好处有以下几点:
- 不用像传统的画布容器方案,配置一个超大宽高的 div 容器,去包含所有的子画布内容,而是直接使用原有的 flex: 1 容器,不限制宽高条件,然后配合 matrix 属性,来实现画布的滑动。
- 滑动动画效果流畅,和 figma 的效果几乎一样。
- 加入了边界范围的限制,使得画布的滑动不会离画布内容太远,减少用户交互的困惑。
- 代码实现简单,易于理解,易于维护。
这么强大的 matrix 属性,心动了吗?快去试一下吧~
欢迎点赞、收藏,评论~