赞
踩
本章代码结构: 主入口Test.tsx , 组件:ResizeControl.tsx
本章花费俩天时间完成代码例子, 单独抽离代码 封装好一个 ResizeControl 组件, 拿来即用。
代码中
const domObj = document.getElementById(
resize-item-${startPos.id})
这句是关键代码, 不然获取的dom节点有问题,导致多个红色div操作时候会重叠
/* eslint-disable no-case-declarations */ import { FC, useEffect, useRef, useState } from 'react'; import styles from './index.module.scss'; import type { Demo } from '../../pages/Test'; interface PropsType { children: JSX.Element | JSX.Element[]; value: Demo; emitData: (val: Demo) => void; } // 获取旋转角度参数 function getRotate(transform: string) { // 假设 transform 是 "rotate(45deg)" // console.info('transform', transform); if (!transform) return 0; const match = /rotate\(([^)]+)\)/.exec(transform); const rotate = match ? parseFloat(match[1]) : 0; // console.info(890, rotate); return rotate; } const ResizeControl: FC<PropsType> = (props: PropsType) => { const { children, value, emitData } = props; const points = ['lt', 'tc', 'rt', 'rc', 'br', 'bc', 'bl', 'lc']; const [startPos, setStartPos] = useState<Demo>(value); const resizeItemRef = useRef(null); const isDown = useRef(false); const [direction, setDirection] = useState(''); useEffect(() => { document.addEventListener('mouseup', handleMouseUp); document.addEventListener('mousemove', handleMouseMove); return () => { document.removeEventListener('mouseup', handleMouseUp); document.removeEventListener('mousemove', handleMouseMove); }; }, [isDown, startPos]); // 鼠标被按下 const onMouseDown = (e: React.MouseEvent<HTMLDivElement>) => { console.info('onMouseDown', e.currentTarget); e.stopPropagation(); // e.preventDefault(); // 获取 整个 resize-item 的dom节点 const domObj = document.getElementById(`resize-item-${startPos.id}`); if (!domObj) return; resizeItemRef.current = domObj; if (!resizeItemRef.current) return; const { width, height, transform } = resizeItemRef.current.style; // 获取当前操作 dom data-key const direction = e.currentTarget.getAttribute('data-key') || ''; console.log('元素方向', direction); setDirection(direction); // 获取旋转角度 const rotate = getRotate(transform); // 记录状态 isDown.current = true; setStartPos({ ...startPos, startX: e.clientX, startY: e.clientY, width: +width.replace(/px/, '') - 2, height: +height.replace(/px/, '') - 2, rotate, }); }; const handleMouseMove = (e: { clientX: number; clientY: number }) => { if (isDown.current && resizeItemRef.current) { const { rotate, startX, startY } = startPos; let { height, width, left, top } = startPos; // console.log('startPos', startPos); const curX = e.clientX; const curY = e.clientY; // 计算偏移量 const offsetX = curX - startX; const offsetY = curY - startY; // console.info('offsetX', offsetX, offsetY); const rect = resizeItemRef.current.getBoundingClientRect(); let nowRotate = 0; switch (direction) { // 右中 case 'rc': width += offsetX; break; // 左中 case 'lc': width -= offsetX; left += offsetX; break; // 底中 case 'bc': height += offsetY; break; // 顶中 case 'tc': height -= offsetY; top += offsetY; break; // 右上角 case 'rt': height -= offsetY; top += offsetY; width += offsetX; break; // 左上角 case 'lt': height -= offsetY; top += offsetY; width -= offsetX; left += offsetX; break; // 右下角 case 'br': height += offsetY; width += offsetX; break; // 左下角 case 'bl': height += offsetY; width -= offsetX; left += offsetX; break; case 'rotate': // 获取元素中心点位置 const centerX = rect.left + rect.width / 2; const centerY = rect.top + rect.height / 2; // 旋转前的角度 const rotateDegreeBefore = Math.atan2(startY - centerY, startX - centerX) / (Math.PI / 180); // 旋转后的角度 const rotateDegreeAfter = Math.atan2(curY - centerY, curX - centerX) / (Math.PI / 180); // 获取旋转的角度值 nowRotate = rotateDegreeAfter - rotateDegreeBefore + rotate; resizeItemRef.current.style.transform = `rotate(${nowRotate}deg)`; break; case 'move': left += offsetX; top += offsetY; // 获取父元素的边界 (打开注释, 可验证边界条件判断) // const parent = resizeItemRef.current.parentElement; // if (!parent) return; // const parentRect = parent.getBoundingClientRect(); // // 限制div不超过父元素的边界 // const maxTop = parentRect.height - height; // const maxLeft = parentRect.width - width; // left = Math.min(Math.max(left, 0), maxLeft); // top = Math.min(Math.max(top, 0), maxTop); break; } // console.log('-----', width, height); resizeItemRef.current.style.width = width + 'px'; resizeItemRef.current.style.height = height + 'px'; resizeItemRef.current.style.left = left + 'px'; resizeItemRef.current.style.top = top + 'px'; const newPos = { ...startPos, height, width, startX: curX, startY: curY, left, top, rotate: nowRotate, }; emitData(newPos); setStartPos(newPos); } }; if (!children) return null; const handleMouseUp = () => { console.info('clear。。。。'); isDown.current = false; resizeItemRef.current = null; }; return ( <div className={styles['resize-item']} style={{ left: `${startPos.left}px`, top: `${startPos.top}px`, width: `${startPos.width + 2}px`, height: `${startPos.height + 2}px`, }} id={`resize-item-${startPos.id}`} > {points.map((item, index) => ( <div key={index} data-key={item} onMouseDown={onMouseDown} className={[ styles['resize-control-btn'], styles[`resize-control-${item}`], ].join(' ')} ></div> ))} <div className={styles['resize-control-rotator']} onMouseDown={onMouseDown} data-key={'rotate'} > 转 </div> <div data-key={'move'} onMouseDown={(e) => onMouseDown(e)} style={{ width: '100%', height: '100%', background: 'yellow' }} > {/* <span style={{ wordBreak: 'break-all', fontSize: '10px' }}> {JSON.stringify(startPos)} </span> */} {children} </div> </div> ); }; export default ResizeControl;
.resize-item { cursor: move; position: absolute; z-index: 2; border: 1px dashed yellow; box-sizing: border-box; } $width_height: 4px; // 建议偶数 .resize-control-btn { position: absolute; width: $width_height; height: $width_height; background: yellow; // user-select: none; // 注意禁止鼠标选中控制点元素,不然拖拽事件可能会因此被中断 z-index: 1; } .resize-control-btn.resize-control-lt { cursor: nw-resize; top: $width_height / -2; left: $width_height / -2; } .resize-control-btn.resize-control-tc { cursor: ns-resize; top: $width_height / -2; left: 50%; margin-left: $width_height / -2; } .resize-control-btn.resize-control-rt { cursor: ne-resize; top: $width_height / -2; right: $width_height / -2; } .resize-control-btn.resize-control-rc { cursor: ew-resize; top: 50%; margin-top: $width_height / -2; right: $width_height / -2; } .resize-control-btn.resize-control-br { cursor: se-resize; bottom: $width_height / -2; right: $width_height / -2; } .resize-control-btn.resize-control-bc { cursor: ns-resize; bottom: $width_height / -2; left: 50%; margin-left: $width_height / -2; } .resize-control-btn.resize-control-bl { cursor: sw-resize; bottom: $width_height / -2; left: $width_height / -2; } .resize-control-btn.resize-control-lc { cursor: ew-resize; top: 50%; margin-top: $width_height / -2; left: $width_height / -2; } .resize-control-rotator { position: absolute; cursor: pointer; bottom: -20px; left: 50%; margin-left: -10px; width: 20px; text-align: center; font-size: 10px; background: red; }
import React, { useState, DragEvent, useRef, useEffect } from 'react'; import ResizeControl from '../../components/ResizeControl'; export interface Demo { id: number; left: number; top: number; startX: number; startY: number; width: number; height: number; rotate: number; } // 获取元素旋转角度 function getDomRotate(transform: string) { // 假设 transform 是 "rotate(45deg)" // console.info('transform', transform); const match = /rotate\(([^)]+)\)/.exec(transform); const rotate = match ? parseFloat(match[1]) : 0; // console.info(890, rotate); return rotate; } const App: React.FC = () => { const [demos, setDemos] = useState<Demo[]>([]); const divRef = useRef<HTMLDivElement | null>(null); const [activeDomId, setActiveDomId] = useState(0); const handleDragStart = (e: DragEvent<HTMLDivElement>, id: number) => { e.dataTransfer.setData('id', id.toString()); // 鼠标偏移量 const offsetX = e.clientX - e.currentTarget.getBoundingClientRect().left; const offsetY = e.clientY - e.currentTarget.getBoundingClientRect().top; e.dataTransfer.setData('offsetX', offsetX.toString()); e.dataTransfer.setData('offsetY', offsetY.toString()); divRef.current = e.currentTarget; }; const handleDrop = (e: DragEvent<HTMLDivElement>) => { e.preventDefault(); console.info('鼠标释放位置', e.clientX, e.clientY); const contentDom = document.getElementById('content'); if (!contentDom) return; const contentStyle = contentDom.getBoundingClientRect(); // console.info('contentStyle', contentStyle); const { left, top } = contentStyle; const offsetX = +e.dataTransfer.getData('offsetX') || 0; const offsetY = +e.dataTransfer.getData('offsetY') || 0; // console.info('offsetX', offsetX, offsetY); const newLeft = e.clientX - left - offsetX; const newTop = e.clientY - top - offsetY; if (!divRef.current) return; const { width, height, transform } = divRef.current.style; // 元素旋转角度 let rotate = 0; if (!transform) { rotate = 0; } else { rotate = getDomRotate(transform); } const newDemo: Demo = { id: e.dataTransfer.getData('id'), startX: e.clientX, startY: e.clientY, left: newLeft, top: newTop, width: +width.replace(/px/, ''), height: +height.replace(/px/, ''), rotate, }; setDemos([...demos, newDemo]); }; const handleDragOver = (e: DragEvent<HTMLDivElement>) => { e.preventDefault(); }; return ( <div> <div id="demo" draggable onDragStart={(e) => handleDragStart(e, +new Date())} style={{ width: '100px', height: '100px', backgroundColor: 'red', margin: '30px', cursor: 'pointer', }} > demo2 </div> <div id="content" onDrop={handleDrop} onDragOver={handleDragOver} style={{ width: '300px', height: '300px', margin: '30px', backgroundColor: 'blue', position: 'relative', }} > {demos.map((demo) => ( <ResizeControl key={demo.id} value={demo} emitData={(data) => { setDemos((prevDemos) => prevDemos.map((a) => { return a.id == data.id ? data : a; }) ); }} > {/* 当前 div 组件 */} <div style={{ backgroundColor: 'red', width: '100%', height: '100%', }} > {/* <span style={{ wordBreak: 'break-all', fontSize: '10px' }}> {JSON.stringify(demo)} </span> */} </div> </ResizeControl> ))} </div> </div> ); }; export default App;
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。