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 是 <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,让我们在下个步骤继续实现。

  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 属性,心动了吗?快去试一下吧~

欢迎点赞、收藏,评论~

相关推荐
学习使我快乐0125 分钟前
JS进阶 3——深入面向对象、原型
开发语言·前端·javascript
bobostudio199525 分钟前
TypeScript 设计模式之【策略模式】
前端·javascript·设计模式·typescript·策略模式
黄尚圈圈1 小时前
Vue 中引入 ECharts 的详细步骤与示例
前端·vue.js·echarts
浮华似水2 小时前
简洁之道 - React Hook Form
前端
正小安4 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch6 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光6 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   6 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   6 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web6 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery