DragResize

contant.ts

export const movePoints: MovePoint[] = [
  'topLeft',
  'topRight',
  'bottomLeft',
  'bottomRight',
  'middleTop',
  'middleBottom',
  'middleLeft',
  'middleRight',
];

export type MovePoint =
  | 'topLeft'
  | 'topRight'
  | 'middleLeft'
  | 'middleRight'
  | 'bottomLeft'
  | 'bottomRight'
  | 'middleTop'
  | 'middleBottom';

export interface MoveBlock {
  width: number;
  height: number;
  top: number;
  left: number;
}

export interface DragResizeProps extends React.HTMLAttributes<'div'> {
  canResize?: boolean; // 是否可缩放
  active?: boolean; // 是否是激活状态
  canMove?: boolean; // 是否可移动
  keepRatio?: boolean; // 是否保持比率
  confine?: boolean; // 是否限制在父元素内;
  moveBlockInit?: MoveBlock; // 初始位置
  onUpdate?: (data: MoveBlock) => any; // 更新数据时回调
}

export type UpdateBlock = Partial<MoveBlock>;

export interface Position {
  x: number;
  y: number;
}

export interface MoveEvent {
  (diff: Position, start: MoveBlock): UpdateBlock;
}

import React, { useEffect, useRef, useState } from 'react';
import styled from 'styled-components/macro';
import {
  DragResizeProps,
  MoveBlock,
  MoveEvent,
  MovePoint,
  movePoints,
  UpdateBlock,
} from './contant';

const pointSize = 10;
const position = (pointSize / 2 + 0.5) * -1;
const dragColor = 'red';

const DragResizeContainer = styled.div`
  position: relative;
  .canResize {
    &:hover {
      border-color: ${dragColor};

      .point {
        transform: scale(1.2, 1.2);
        transition: all 0.3s;
        opacity: 1;
      }
    }

    .point {
      background: white;
      z-index: 1;
    }

    .topLeft {
      cursor: nw-resize;
    }

    .topRight {
      cursor: ne-resize;
    }

    .middleTop {
      cursor: n-resize;
    }

    .middleBottom {
      cursor: s-resize;
    }

    .middleLeft {
      cursor: w-resize;
    }

    .middleRight {
      cursor: e-resize;
    }

    .bottomLeft {
      cursor: sw-resize;
    }

    .bottomRight {
      cursor: se-resize;
    }
  }

  .container {
    user-select: none; /* 不可复制 */
    background-color: transparent;
    position: absolute;
    border: 1px dashed transparent;

    .point {
      opacity: 0; // 调试用
      position: absolute;
      width: ${pointSize}px;
      height: ${pointSize}px;
      border-radius: ${pointSize}px;
      border: 1px solid ${dragColor};
      background: white;
    }

    .topLeft {
      top: ${position}px;
      left: ${position}px;
    }

    .topRight {
      top: ${position}px;
      right: ${position}px;
    }

    .middleTop {
      top: ${position}px;
      left: 0;
      right: 0;
      margin: auto;
    }

    .middleBottom {
      bottom: ${position}px;
      left: 0;
      right: 0;
      margin: auto;
    }

    .middleLeft {
      top: 0;
      bottom: 0;
      left: ${position}px;
      margin: auto;
    }

    .middleRight {
      top: 0;
      bottom: 0;
      right: ${position}px;
      margin: auto;
    }

    .bottomLeft {
      left: ${position}px;
      bottom: ${position}px;
    }

    .bottomRight {
      right: ${position}px;
      bottom: ${position}px;
    }
  }

  .active {
    border-color: ${dragColor};

    .point {
      opacity: 1;
    }
  }
`;

const classNames = (className: object) => {
  return Object.entries(className)
    .map(([key, val]) => {
      if (val) {
        return key;
      } else {
        return '';
      }
    })
    .join(' ');
};

const DragResize = (props: DragResizeProps) => {
  const {
    children,
    canResize = true,
    active = true,
    canMove = true,
    keepRatio,
    confine,
    onUpdate,
    moveBlockInit,
  } = props;
  const [moveBlock, setMoveBlock] = useState<MoveBlock>();
  const maxBlock = useRef({
    width: 0,
    height: 0,
  });
  const ratio = useRef<number>();
  useEffect(() => {
    if (moveBlockInit) {
      setMoveBlock(moveBlockInit);
    }
  }, [moveBlockInit]);
  useEffect(() => {
    if (!keepRatio) ratio.current = undefined;
    if (keepRatio && moveBlock && !ratio.current) {
      if (moveBlock.width) {
        ratio.current = moveBlock.height / moveBlock.width;
      } else {
        ratio.current = undefined;
      }
    }
  }, [keepRatio, moveBlock]);

  const prePosition = useRef<{
    dragType?: MovePoint | 'block';
    startX: number;
    startY: number;
  }>({
    dragType: undefined,
    startX: 0,
    startY: 0,
  });

  const eventMap: Record<MovePoint, MoveEvent> = {
    topLeft(diff, start) {
      if (keepRatio && ratio.current) {
        const dx = diff.x;
        return {
          width: start.width - dx,
          left: start.left + dx,
          height: start.height - dx * ratio.current,
          top: start.top + dx * ratio.current,
        };
      }
      return {
        width: start.width - diff.x,
        left: start.left + diff.x,
        height: start.height - diff.y,
        top: start.top + diff.y,
      };
    },
    topRight(diff, start) {
      if (keepRatio && ratio.current) {
        const dx = diff.x;
        return {
          width: start.width + dx,
          height: start.height + dx * ratio.current,
          top: start.top - dx * ratio.current,
        };
      }
      return {
        width: start.width + diff.x,
        height: start.height - diff.y,
        top: start.top + diff.y,
      };
    },
    bottomLeft(diff, start) {
      if (keepRatio && ratio.current) {
        const dx = diff.x;
        return {
          width: start.width - dx,
          left: start.left + dx,
          height: start.height - dx * ratio.current,
        };
      }
      return {
        width: start.width - diff.x,
        height: start.height + diff.y,
        left: start.left + diff.x,
      };
    },
    bottomRight(diff, start) {
      if (keepRatio && ratio.current) {
        const dx = diff.x;
        return {
          width: start.width + dx,
          height: start.height + dx * ratio.current,
        };
      }
      return {
        width: start.width + diff.x,
        height: start.height + diff.y,
      };
    },
    middleTop(diff, start) {
      if (keepRatio && ratio.current) {
        const dy = diff.y;
        return {
          height: start.height - dy,
          top: start.top + dy,
          width: start.width - dy / ratio.current,
          left: start.left + dy / ratio.current / 2,
        };
      }
      return {
        height: start.height - diff.y,
        top: start.top + diff.y,
      };
    },
    middleBottom(diff, start) {
      if (keepRatio && ratio.current) {
        const dy = diff.y;
        return {
          height: start.height + dy,
          width: start.width + dy / ratio.current,
          left: start.left - dy / ratio.current / 2,
        };
      }
      return {
        height: start.height + diff.y,
      };
    },
    middleLeft(diff, start) {
      if (keepRatio && ratio.current) {
        const dx = diff.x;
        return {
          height: start.height - dx * ratio.current,
          top: start.top + (dx * ratio.current) / 2,
          width: start.width - dx,
          left: start.left + dx,
        };
      }
      return {
        width: start.width - diff.x,
        left: start.left + diff.x,
      };
    },
    middleRight(diff, start) {
      if (keepRatio && ratio.current) {
        const dx = diff.x;
        return {
          height: start.height + dx * ratio.current,
          top: start.top - (dx * ratio.current) / 2,
          width: start.width + dx,
        };
      }
      return {
        width: start.width + diff.x,
      };
    },
  };
  /**
   * 更新状态
   */
  const updateBlock = (data: UpdateBlock) => {
    if (!moveBlock) return;
    const _data = { ...moveBlock, ...data };

    if (_data.width < 0) return;
    if (_data.height < 0) return;
    if (confine) {
      if (_data.top < 0) return;
      if (_data.left < 0) return;
      if (_data.left + _data.width > maxBlock.current.width) return;
      if (_data.top + _data.height > maxBlock.current.height) return;
    }
    console.log('ratio: ', ratio.current);
    setMoveBlock(_data);
    onUpdate?.(_data);
  };

  /**
   * 拖拽时
   */
  const onBlockMouseMove = (e: MouseEvent) => {
    if (!canMove || !moveBlock) {
      return;
    }
    const { startX, startY } = prePosition.current;
    const diff = {
      x: e.clientX - startX,
      y: e.clientY - startY,
    };
    const { top, left } = moveBlock;

    updateBlock({
      top: top + diff.y,
      left: left + diff.x,
    });
  };

  const onBlockMouseDown = (e: React.MouseEvent<any, MouseEvent>) => {
    setMoveBlock({
      width: e.currentTarget.offsetWidth,
      height: e.currentTarget.offsetHeight,
      top: e.currentTarget.offsetTop,
      left: e.currentTarget.offsetLeft,
    });
    if (!prePosition.current.dragType) {
      prePosition.current = {
        dragType: 'block',
        startX: e.clientX,
        startY: e.clientY,
      };
    }
  };

  const onPointMouseMove = (e: MouseEvent, type: MovePoint) => {
    if (!prePosition.current.dragType || !moveBlock || !canResize) return;
    const { startX, startY } = prePosition.current;
    const diff = {
      x: e.clientX - startX,
      y: e.clientY - startY,
    };

    const setFn = eventMap[type];
    updateBlock(setFn(diff, moveBlock));
  };

  const onPointMouseDown = (
    e: React.MouseEvent<HTMLDivElement, MouseEvent>,
    type: MovePoint,
  ) => {
    // 记录鼠标点击开始位置
    prePosition.current = {
      dragType: type,
      startX: e.clientX,
      startY: e.clientY,
    };
  };

  const onMouseUp = () => {
    prePosition.current.dragType = undefined;
  };

  const onMouseMove = (e: MouseEvent) => {
    if (!prePosition.current.dragType) return;
    if (prePosition.current.dragType === 'block') {
      onBlockMouseMove(e);
    } else {
      onPointMouseMove(e, prePosition.current.dragType);
    }
    prePosition.current.startX = e.clientX;
    prePosition.current.startY = e.clientY;
  };

  useEffect(() => {
    document.addEventListener('mousemove', onMouseMove);
    document.addEventListener('mouseup', onMouseUp);
    return () => {
      document.removeEventListener('mousemove', onMouseMove);
      document.removeEventListener('mouseup', onMouseUp);
    };
  }, [moveBlock]);

  return (
    <DragResizeContainer
      style={{ backgroundColor: 'transparent' }}
      onMouseDown={e => {
        maxBlock.current = {
          width: e.currentTarget.offsetWidth,
          height: e.currentTarget.offsetHeight,
        };
      }}
    >
      <div
        className={classNames({ container: true, canResize, active, canMove })}
        style={moveBlock}
        onMouseDown={onBlockMouseDown}
        onDrag={e => e.preventDefault()}
      >
        {children}
        {movePoints.map(val => (
          <div
            key={val}
            className={classNames({ point: true, [val]: true })}
            onMouseDown={e => onPointMouseDown(e, val)}
          ></div>
        ))}
      </div>
    </DragResizeContainer>
  );
};

export default DragResize;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值