react可视化编辑器 第四章 顶点的缩放功能

直接上代码

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;
}
相关推荐
天宇&嘘月1 小时前
web第三次作业
前端·javascript·css
小王不会写code2 小时前
axios
前端·javascript·axios
luckyext3 小时前
HBuilderX中,VUE生成随机数字,vue调用随机数函数
前端·javascript·vue.js·微信小程序·小程序
小小码农(找工作版)3 小时前
JavaScript 前端面试 4(作用域链、this)
前端·javascript·面试
WeiLai11124 小时前
CodeGPT 使用教程(适用于 VSCode)
ide·vscode·编辑器
鱼樱前端5 小时前
深入JavaScript引擎与模块加载机制:从V8原理到模块化实战
前端·javascript
yangjiajia1234565 小时前
vue3 ref和reactive的区别
前端·javascript·vue.js
诚信爱国敬业友善6 小时前
Vue 基础二(进阶使用)
前端·javascript·vue.js
なし.6 小时前
【Web前端开发精品课 HTML CSS JavaScript基础教程】第二十四章课后题答案
前端·javascript·css·html