5分钟,用 CSS Matrix 做一个可自由滑动的 IDE 画布容器

前言

大家好,我是馋嘴的猫。在上一篇React-Draggable的实战教程中,我们学会了如何实现 IDE 的拖动面板效果。

在这一篇文章中,我们将会继续学习另外一个 IDE 的高频交互操作:画布滑动

相信开发同学在实现前端需求时,对 figma 应该不陌生。

在使用 figma 的时候,我们可以通过鼠标滚轮或者触控板来实现画布的滑动,这种交互方式非常的流畅,也对画布容器放置多画布,提供了更好的支持,完美支持画布区域比屏幕更高更宽的场景。

今天,我们将会借助 CSS 的 matrix 属性,在页面实现上述效果,请跟随下文,请一起学起来吧~

需求

通过 CSS 的 matrix 属性,在 Next.js 项目,实现一个可滑动的 IDE 画布。

使用框架

  1. Next.js
  2. Tailwindcss

仓库地址

点此查看

在线演示地址

点此查看

实现步骤

  1. 我们这次的教程,会以上篇 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
  1. 打开 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 高度太高,会阻拦画布的正常显示。

所以,我们接下来,要为画布容器添加滑动支持,使得画布可以正常显示出来~

  1. 在 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函数接受六个参数,分别是:abcdef。这些参数可以创建一个复杂的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 是 p o s i t i o n . x 和 {position.x} 和 position.x和{position.y},所以元素会沿着 X 轴和 Y 轴移动,移动的距离取决于 position.x 和 position.y 的值。

d. 因此,这个 matrix 属性,就非常满足我们今天的需求了:滑动画布

说到底,画布的滑动实现,就是让浏览器,一直根据鼠标的 X 轴和 Y 轴的滑动方向和距离,来移动画布嘛。

OK,让我们在下个步骤继续实现。

  1. 实现 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}) 来实现它的滑动

  1. 至此,全部流程完成,可以看下成果啦~

可以看到,画布已经可以通过鼠标滚轮或触控板来滑动了,效果非常流畅,和 figma 的效果几乎一样。

总结

在这篇教程中,我们学习了如何通过 CSS 的 matrix 属性,来实现画布容器的滑动效果。 它的好处有以下几点:

  1. 不用像传统的画布容器方案,配置一个超大宽高的 div 容器,去包含所有的子画布内容,而是直接使用原有的 flex: 1 容器,不限制宽高条件,然后配合 matrix 属性,来实现画布的滑动。
  2. 滑动动画效果流畅,和 figma 的效果几乎一样。
  3. 加入了边界范围的限制,使得画布的滑动不会离画布内容太远,减少用户交互的困惑。
  4. 代码实现简单,易于理解,易于维护。

这么强大的 matrix 属性,心动了吗?快去试一下吧~

欢迎点赞、收藏,评论~

相关推荐
子兮曰1 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰1 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万1 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝1 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋1 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁1 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
汉堡大王95271 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大1 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师1 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端
沙蒿同学1 天前
我用 Go 搭了一条 AI Agent 流水线:从 1 张商品图到一整套淘宝详情页
前端·javascript·后端