"use client"; import { ReactElement, useEffect, useRef, useState } from "react"; import classnames from "classnames"; import _ from "lodash"; import { Draggable } from "./Draggable"; export default function DraggableWidget(props: DraggableWidgetType) { const { id, draggable, children, x, y, w, h } = props; const targetRef = useRef(null); const containerRef = useRef(null); const [size, setSize] = useState({ width: 16, height: 16 }); const [isResize, setIsResize] = useState(false); useEffect(() => { const element = targetRef.current; if (!element) return; // 创建 ResizeObserver 实例 let timer: any; const observer = new ResizeObserver( _.throttle((entries: any) => { for (const entry of entries) { const { width, height } = entry.contentRect; setSize({ width: nearestMultipleOf16(width), height: nearestMultipleOf16(height), }); if (timer) { clearTimeout(timer); } setIsResize(true); timer = setTimeout(() => { entry.target.style.width = nearestMultipleOf16(width) + "px"; entry.target.style.height = nearestMultipleOf16(height) + "px"; // syncSize(entry.contentRect, containerRef.current); setIsResize(false); }, 150); } }, 30), ); const syncSize = (contentRect, containerNode) => { const { x, y, height, width } = contentRect; const area = { rowStart: x / 16 + 1, columnStart: y / 16 + 1, rowEnd: width / 16 + x / 16 + 1, columnEnd: height / 16 + y / 16 + 1, }; containerNode.style.gridArea = `${area.rowStart} / ${area.columnStart} / ${area.rowEnd} / ${area.columnEnd}`; }; // 开始观察元素 observer.observe(element); // 组件卸载时断开连接 return () => { observer.disconnect(); }; }, []); // 空依赖数组确保只运行一次 const draggableWidgetClass = classnames( "block w-full h-full max-h-[1048px] max-w-[1888px] cursor-pointer overflow-hidden", { "hover:bg-red-200 hover:outline-2 hover:outline-dashed hover:outline-gray-300 hover:opacity-30 resize": draggable, }, ); const draggableWidgetIndicatorBoxClass = classnames( "absolute top-0 left-0 outline-2 outline-dashed outline-red-500 z-50 pointer-events-none", { invisible: !isResize, }, ); return (
{children}
{`${size.width}x${size.height}`}
); } export type DraggableWidgetType = { id: string; draggable: boolean; wdith?: number | string; height?: number | string; children: ReactElement; x: number; y: number; w: number; h: number; }; /** * 计算最接近的 16 的倍数 * @param n 输入的数字 * @returns 最近的 16 的倍数 */ function nearestMultipleOf16(n: number): number { return Math.floor((n + 8) / 16) * 16; }