|
|
@@ -1,61 +1,33 @@
|
|
|
import React, { useState, useCallback, useEffect, useRef, useMemo } from 'react'
|
|
|
import CodeCanvasView from './code-canvas.jsx'
|
|
|
|
|
|
+/** 视口外多渲染的 chunk 层数,接近边界时提前出现,避免白边 */
|
|
|
const CHUNK_MARGIN = 1
|
|
|
|
|
|
-/** 可拖拽画布:chunk 制,只渲染视口+边距内的块,视觉上无限 */
|
|
|
+/** 可拖拽画布:chunk 制,只渲染视口+边距内的块,视觉上无限、省内存 */
|
|
|
function CodeCanvas({ show }) {
|
|
|
const [translate, setTranslate] = useState({ x: 0, y: 0 })
|
|
|
const [dragStart, setDragStart] = useState(null)
|
|
|
const [containerSize, setContainerSize] = useState({ width: 0, height: 0 })
|
|
|
const containerRef = useRef(null)
|
|
|
|
|
|
- useEffect(() => {
|
|
|
- if (!containerRef.current) return
|
|
|
- const el = containerRef.current
|
|
|
- const observer = new ResizeObserver((entries) => {
|
|
|
- const { width, height } = entries[0].contentRect
|
|
|
- setContainerSize({ width, height })
|
|
|
- })
|
|
|
- observer.observe(el)
|
|
|
- return () => observer.disconnect()
|
|
|
- }, [show])
|
|
|
-
|
|
|
- const handleMouseDown = useCallback((e) => {
|
|
|
- e.preventDefault()
|
|
|
- setDragStart({ x: e.clientX, y: e.clientY, tx: translate.x, ty: translate.y })
|
|
|
- }, [translate])
|
|
|
-
|
|
|
- useEffect(() => {
|
|
|
- if (!dragStart) return
|
|
|
- const onMove = (e) => {
|
|
|
- setTranslate({
|
|
|
- x: dragStart.tx + e.clientX - dragStart.x,
|
|
|
- y: dragStart.ty + e.clientY - dragStart.y,
|
|
|
- })
|
|
|
- }
|
|
|
- const onUp = () => setDragStart(null)
|
|
|
- document.addEventListener('mousemove', onMove)
|
|
|
- document.addEventListener('mouseup', onUp)
|
|
|
- return () => {
|
|
|
- document.removeEventListener('mousemove', onMove)
|
|
|
- document.removeEventListener('mouseup', onUp)
|
|
|
- }
|
|
|
- }, [dragStart])
|
|
|
-
|
|
|
+ /** ① 每轮渲染先算:平移量、可见 chunk、box 位置(供 return 用) */
|
|
|
const { panTransform, visibleChunks, boxPosition } = useMemo(() => {
|
|
|
const { width: cw, height: ch } = containerSize
|
|
|
if (!cw || !ch) {
|
|
|
return { panTransform: { x: 0, y: 0 }, visibleChunks: [], boxPosition: null }
|
|
|
}
|
|
|
|
|
|
+ /** 平移层 transform:世界中心 (0,0) 对准容器中心,再叠加拖拽位移 */
|
|
|
const panX = cw / 2 + translate.x
|
|
|
const panY = ch / 2 + translate.y
|
|
|
+ /** 视口在世界坐标系中的范围 */
|
|
|
const viewportLeft = -panX
|
|
|
const viewportTop = -panY
|
|
|
const viewportRight = -panX + cw
|
|
|
const viewportBottom = -panY + ch
|
|
|
|
|
|
+ /** 与视口相交的 chunk 索引范围(含 CHUNK_MARGIN 边距) */
|
|
|
const minChunkX = Math.floor(viewportLeft / cw) - CHUNK_MARGIN
|
|
|
const maxChunkX = Math.ceil(viewportRight / cw) + CHUNK_MARGIN
|
|
|
const minChunkY = Math.floor(viewportTop / ch) - CHUNK_MARGIN
|
|
|
@@ -68,6 +40,7 @@ function CodeCanvas({ show }) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /** box 固定在世界原点 (0,0),尺寸为容器 10%,居中故取负半宽高为 left/top */
|
|
|
const boxW = cw * 0.1
|
|
|
const boxH = ch * 0.1
|
|
|
const boxLeft = -boxW / 2
|
|
|
@@ -80,6 +53,42 @@ function CodeCanvas({ show }) {
|
|
|
}
|
|
|
}, [containerSize, translate])
|
|
|
|
|
|
+ /** ② 鼠标按下时执行:记录拖拽起点与当前 translate */
|
|
|
+ const handleMouseDown = useCallback((e) => {
|
|
|
+ e.preventDefault()
|
|
|
+ setDragStart({ x: e.clientX, y: e.clientY, tx: translate.x, ty: translate.y })
|
|
|
+ }, [translate])
|
|
|
+
|
|
|
+ /** ③ 挂载/展示变化后执行:ResizeObserver 同步容器宽高 */
|
|
|
+ useEffect(() => {
|
|
|
+ if (!containerRef.current) return
|
|
|
+ const el = containerRef.current
|
|
|
+ const observer = new ResizeObserver((entries) => {
|
|
|
+ const { width, height } = entries[0].contentRect
|
|
|
+ setContainerSize({ width, height })
|
|
|
+ })
|
|
|
+ observer.observe(el)
|
|
|
+ return () => observer.disconnect()
|
|
|
+ }, [show])
|
|
|
+
|
|
|
+ /** ④ dragStart 变化后执行:在 document 上监听 mousemove/mouseup,更新 translate */
|
|
|
+ useEffect(() => {
|
|
|
+ if (!dragStart) return
|
|
|
+ const onMove = (e) => {
|
|
|
+ setTranslate({
|
|
|
+ x: dragStart.tx + e.clientX - dragStart.x,
|
|
|
+ y: dragStart.ty + e.clientY - dragStart.y,
|
|
|
+ })
|
|
|
+ }
|
|
|
+ const onUp = () => setDragStart(null)
|
|
|
+ document.addEventListener('mousemove', onMove)
|
|
|
+ document.addEventListener('mouseup', onUp)
|
|
|
+ return () => {
|
|
|
+ document.removeEventListener('mousemove', onMove)
|
|
|
+ document.removeEventListener('mouseup', onUp)
|
|
|
+ }
|
|
|
+ }, [dragStart])
|
|
|
+
|
|
|
return React.createElement(CodeCanvasView, {
|
|
|
show,
|
|
|
panTransform,
|