"use client"; import { CSSProperties, ReactElement, useEffect, useRef, useState } from "react"; import classnames from 'classnames'; import _ from 'lodash'; export default function DraggableWidget(props: DraggableWidgetType) { const { 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( 'hover:bg-red-200 hover:outline-2 hover:outline-dashed hover:outline-gray-300 hover:opacity-30 resize block rounded-lg w-64 h-64 max-h-[1048px] max-w-[1888px] cursor-pointer overflow-hidden ', { } ); 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 = { 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; }