直接上代码
import React, { useState, useEffect, useRef, useCallback } from 'react';
import styles from './index.module.scss';
const ResizableDiv = () => {
// 8个点,为 left/right/top/bottom 的组合值
const points = ['lt', 'tc', 'rt', 'rc', 'br', 'bc', 'bl', 'lc'];
const isDown = useRef(false);
const resizeItemRef = useRef(null);
const [startPos, setStartPos] = useState({
startX: 0,
startY: 0,
width: 0,
height: 0,
direction: '',
});
useEffect(() => {
document.addEventListener('mouseup', handleMouseUp);
document.addEventListener('mousemove', handleMouseMove);
return () => {
document.removeEventListener('mouseup', handleMouseUp);
document.removeEventListener('mousemove', handleMouseMove);
};
}, [isDown, startPos]);
const handleMouseUp = () => {
isDown.current = false;
};
const handleMouseMove = (e: { clientX: number; clientY: number }) => {
if (isDown.current && resizeItemRef.current) {
const { direction } = startPos;
let { height, width, startX, startY } = startPos;
const offsetX = e.clientX - startX;
const offsetY = e.clientY - startY;
switch (direction) {
case 'rc':
// 向右拖拽添加宽度
width += offsetX;
break;
case 'lc':
// 增加宽度、位置同步左移
width -= offsetX;
startX += offsetX;
break;
case 'bc':
height += offsetY;
break;
case 'tc':
height -= offsetY;
startY += offsetY;
break;
case 'rt':
height -= offsetY;
startY += offsetY;
width += offsetX;
break;
case 'lt':
height -= offsetY;
startY += offsetY;
width -= offsetX;
startX += offsetX;
break;
case 'br':
height += offsetY;
width += offsetX;
break;
case 'bl':
height += offsetY;
width -= offsetX;
startX += offsetX;
break;
}
resizeItemRef.current.style.width = width + 'px';
resizeItemRef.current.style.height = height + 'px';
}
};
// 鼠标被按下
const onMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
console.info('onMouseDown', e);
if (!e.currentTarget) return;
resizeItemRef.current = e.currentTarget;
if (!resizeItemRef.current) return;
const { offsetWidth: width, offsetHeight: height } = resizeItemRef.current;
const direction = e.target.getAttribute('data-key');
console.log(direction, width, height);
isDown.current = true;
setStartPos({
startX: e.clientX,
startY: e.clientY,
width,
height,
direction,
});
};
return (
<div className={styles['resize-content']}>
<div className={styles['resize-item']} onMouseDown={onMouseDown}>
{points.map((item, index) => (
<div
key={index}
data-key={item}
className={[
styles['resize-control-btn'],
styles[`resize-control-${item}`],
].join(' ')}
></div>
))}
</div>
</div>
);
};
export default ResizableDiv;
.resize-content {
width: 500px;
height: 500px;
border: 1px dashed red;
margin: 30px auto;
position: relative;
}
.resize-item {
cursor: move;
width: 100px;
height: 100px;
background-color: #ccc;
position: absolute;
// top: 200px;
// left: 200px;
}
$width_height: 6px;
.resize-control-btn {
position: absolute;
width: $width_height;
height: $width_height;
background: #000;
user-select: none; // 注意禁止鼠标选中控制点元素,不然拖拽事件可能会因此被中断
}
.resize-control-btn.resize-control-lt {
cursor: nw-resize;
top: 0;
left: 0;
}
.resize-control-btn.resize-control-tc {
cursor: ns-resize;
top: 0;
left: 50%;
// transform: translateX(-50%);
margin-left: $width_height / -2;
}
.resize-control-btn.resize-control-rt {
cursor: ne-resize;
top: 0;
right: 0;
}
.resize-control-btn.resize-control-rc {
cursor: ew-resize;
top: 50%;
margin-top: $width_height / -2;
right: 0;
}
.resize-control-btn.resize-control-br {
cursor: se-resize;
bottom: 0;
right: 0;
}
.resize-control-btn.resize-control-bc {
cursor: ns-resize;
bottom: 0;
left: 50%;
margin-left: $width_height / -2;
}
.resize-control-btn.resize-control-bl {
cursor: sw-resize;
bottom: 0;
left: 0;
}
.resize-control-btn.resize-control-lc {
cursor: ew-resize;
top: 50%;
margin-top: $width_height / -2;
left: 0;
}