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;
}
相关推荐
还是大剑师兰特43 分钟前
D3的竞品有哪些,D3的优势,D3和echarts的对比
前端·javascript·echarts
一只小白菜~1 小时前
web浏览器环境下使用window.open()打开PDF文件不是预览,而是下载文件?
前端·javascript·pdf·windowopen预览pdf
方才coding1 小时前
1小时构建Vue3知识体系之vue的生命周期函数
前端·javascript·vue.js
阿征学IT1 小时前
vue过滤器初步使用
前端·javascript·vue.js
王哲晓1 小时前
第四十五章 Vue之Vuex模块化创建(module)
前端·javascript·vue.js
发现你走远了1 小时前
『VUE』25. 组件事件与v-model(详细图文注释)
前端·javascript·vue.js
吖秧吖1 小时前
three.js 杂记
开发语言·前端·javascript
前端小超超1 小时前
vue3 ts项目结合vant4 复选框+气泡弹框实现一个类似Select样式的下拉选择功能
前端·javascript·vue.js
大叔是90后大叔1 小时前
vue3中查找字典列表中某个元素的值
前端·javascript·vue.js
IT大玩客1 小时前
JS如何获取MQTT的主题
开发语言·javascript·ecmascript