DragScroll component

Johny Kim·2021년 5월 20일
0

Custom Hooks

목록 보기
2/3
post-custom-banner

이 컴포넌트로 감싼 요소는 마우스로 드래그하여 스크롤 할 수 있음.

<DragScroll>
  ...
</DragScroll>

Dragscroll.tsx

import React, { useRef } from 'react';
import useDragScroll from '@src/Hooks/useDragScroll';
import { DefaultProps } from 'iscrim_ui/lib/types';
import styled, { StyledComponent } from 'styled-components';

export interface DragScrollProps extends DefaultProps {
  maxHeight?: string;
  forceScrollTo?: { x?:number, y?:number }
  isTextDraggable?: boolean;
  isHideScrollBar?: boolean;
};

interface ContainerProps extends DragScrollProps {
  isDragging: boolean;
}


const Container: StyledComponent<'div', DefaultProps, ContainerProps> = styled.div`
  max-height: ${(props:any) => props.maxHeight};
  overflow: scroll;
  -webkit-overflow-scrolling: touch;
  -moz-overflow-scrolling: touch;
  -ms-overflow-scrolling: touch;
  -ms-overflow-style: none;

  ${({ isTextDraggable }: ContainerProps)=> !isTextDraggable && `
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
  `};

  ${({ isHideScrollBar }: ContainerProps)=> isHideScrollBar && `
    &::-webkit-scrollbar{ display:none; }
    -ms-overflow-style: none;
  `};

  cursor: grab;
  ${(props:any) => props.isDragging && `
    cursor: grabbing !important;
    * {
      pointer-events: none;
    }
  `};
`;


const DragScroll: React.FC<DragScrollProps> = ({ maxHeight, forceScrollTo, isTextDraggable, isHideScrollBar, className, style, children }) => {

  const bracketContainer = useRef<HTMLDivElement>(null);
  const { isDragging, handleMouseMove, handleMouseDown, handleMouseUp, handleMouseLeave } = useDragScroll({ ref: bracketContainer, forceScrollTo });

  return (
    <Container 
      ref={bracketContainer}
      style={style}
      className={className}
      isDragging={isDragging}
      isTextDraggable={isTextDraggable}
      isHideScrollBar={isHideScrollBar}
      onMouseDown={handleMouseDown}
      onMouseMove={handleMouseMove}
      onMouseUp={handleMouseUp}
      onMouseLeave={handleMouseLeave}
      maxHeight={maxHeight}
    >
      {children}
    </Container>
  )
}

export default DragScroll;

useDragScroll.tsx

import { MutableRefObject, useEffect, useState } from "react"

interface Options {
  ref: MutableRefObject<any>;
  forceScrollTo?: { x?:number, y?:number };
}

const getLimit = (value:number, limit:number) => {
  if (value < 0) return 0;
  else if (value > limit) return limit;
  else return value;
}

export default ({ ref, forceScrollTo }: Options) => {
  const [state, setState] = useState({ x: 0, y: 0 });
  const [drag, setDrag] = useState({ x: 0, y: 0 });
  const [dragPoint, setDragPoint] = useState({ x: 0, y: 0 });
  const [grapPoint, setGrapPoint] = useState<null | {x:number, y:number}>(null);

  const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
    e.persist();

    const target = e.currentTarget;
    const rect = target.getBoundingClientRect();

    const x = e.clientX - Math.floor(rect.x);
    const y = e.clientY - Math.floor(rect.y);

    setState(state => ({ ...state, x, y }));
    
    if ( grapPoint ) {
      const dragOffsetX = grapPoint.x - x;
      const dragOffsetY = grapPoint.y - y;

      const dragX = getLimit(dragOffsetX, target.scrollWidth + rect.width);
      const dragY = getLimit(dragOffsetY, target.scrollHeight + rect.height);

      setDrag({ x: dragX, y: dragY });
    }
  }

  const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
    e.stopPropagation();
    const top = e.currentTarget.scrollTop;
    const left = e.currentTarget.scrollLeft;
    setDragPoint(drag);
    setGrapPoint({ x: state.x + left, y: state.y + top });
  }

  const handleMouseUp = (e: React.MouseEvent<HTMLDivElement>) => {
    setDragPoint(drag);
    setGrapPoint(null);
  }

  const handleMouseLeave = (e: React.MouseEvent<HTMLDivElement>) => {
    setDragPoint(drag);
    setGrapPoint(null);
  }

  useEffect(() => {
    if ( ref && ref.current ) {
      ref.current.scrollTo(drag.x, drag.y);
    }
  }, [drag]);

  useEffect(() => {
    if ( forceScrollTo !== undefined ) {
      setDrag({
        x: forceScrollTo.x === undefined ? drag.x : forceScrollTo.x,
        y: forceScrollTo.y === undefined ? drag.y : forceScrollTo.y,
      });
    }
  }, [forceScrollTo]);


  return {
    drag: drag,
    isDragging: drag.x !== dragPoint.x || drag.y !== dragPoint.y,
    handleMouseMove,
    handleMouseDown,
    handleMouseUp,
    handleMouseLeave,
  }
}
profile
작고 단단한 컴포넌트를 만들자.
post-custom-banner

0개의 댓글