diff --git a/src/assets/css/components/common/hide-scrollbar.scss b/src/assets/css/components/common/hide-scrollbar.scss index 9d8ffe5..a7f1b6c 100644 --- a/src/assets/css/components/common/hide-scrollbar.scss +++ b/src/assets/css/components/common/hide-scrollbar.scss @@ -1,16 +1,66 @@ +@use '@/assets/css/constants' as constants; + .hide-scrollbar-mask { width: 100%; height: 100%; overflow: hidden; -} -.hide-scrollbar-selection { - position: relative; - overflow: scroll; - scrollbar-width: none; - -ms-overflow-style: none; -} + .hide-scrollbar-selection { + position: relative; + overflow: scroll; + scrollbar-width: none; + -ms-overflow-style: none; -.hide-scrollbar-selection::-webkit-scrollbar { - display: none; + .hide-scrollbar-content { + display: inline-block; + } + } + + ::-webkit-scrollbar { + display: none; + } + + .scrollbar { + position: absolute; + z-index: 1000; + opacity: .5; + touch-action: none; + + .box { + position: relative; + width: 100%; + height: 100%; + border-radius: 8px; + overflow: hidden; + + + .block { + position: absolute; + width: 100%; + height: 100%; + border-radius: 8px; + background-color: constants.$font-secondary-color; + transition: background-color .2s; + } + :hover { + background-color: constants.$font-main-color; + } + } + } + + .vertical-scrollbar { + padding: 12px 4px; + width: 16px; + height: 100%; + right: 0; + top: 0; + } + + .horizontal-scrollbar { + padding: 4px 12px; + width: 100%; + height: 16px; + left: 0; + bottom: 0; + } } \ No newline at end of file diff --git a/src/assets/css/components/common/indicator.scss b/src/assets/css/components/common/indicator.scss index 85981c8..efe8e76 100644 --- a/src/assets/css/components/common/indicator.scss +++ b/src/assets/css/components/common/indicator.scss @@ -9,6 +9,7 @@ .item { flex: auto; + cursor: pointer; .dot { width: 10px; @@ -19,10 +20,15 @@ color: constants.$font-secondary-color; style: solid; }; + transition: all .2s; + } + + :hover { + background-color: constants.$focus-color; } } - .active>* { - background-color: constants.$font-secondary-color; + .active > * { + background-color: constants.$font-secondary-color !important; } } \ No newline at end of file diff --git a/src/assets/css/constants.scss b/src/assets/css/constants.scss index 78451a4..2cacf14 100644 --- a/src/assets/css/constants.scss +++ b/src/assets/css/constants.scss @@ -2,3 +2,4 @@ $main-color: #00D4FF; $background-color: #F5F5F5; $font-main-color: #4D4D4D; $font-secondary-color: #9E9E9E; +$focus-color: #DDDDDD diff --git a/src/components/common/HideScrollbar.tsx b/src/components/common/HideScrollbar.tsx index 1fcdaa7..ded9e3b 100644 --- a/src/components/common/HideScrollbar.tsx +++ b/src/components/common/HideScrollbar.tsx @@ -43,124 +43,6 @@ export interface HideScrollbarElement { } const HideScrollbar = forwardRef((props, ref) => { - const rootRef = useRef(null) - const lastTouchPosition = useRef<{ x: number; y: number }>({ x: -1, y: -1 }) - const [verticalScrollbarWidth, setVerticalScrollbarWidth] = useState(0) - const [horizontalScrollbarWidth, setHorizontalScrollbarWidth] = useState(0) - - const { - isPreventScroll, - isPreventVerticalScroll, - isPreventHorizontalScroll, - isShowVerticalScrollbar, - isShowHorizontalScrollbar, - ..._props - } = props - - const handleDefaultWheel = useCallback( - (event: WheelEvent) => { - if (!event.altKey && !event.ctrlKey) { - if (isPreventScroll) { - event.preventDefault() - return - } - if (isPreventVerticalScroll && !event.shiftKey && !event.deltaX) { - event.preventDefault() - return - } - if (isPreventHorizontalScroll && (event.shiftKey || !event.deltaY)) { - event.preventDefault() - return - } - } - }, - [isPreventScroll, isPreventHorizontalScroll, isPreventVerticalScroll] - ) - - const handleDefaultTouchStart = useCallback( - (event: TouchEvent) => { - if (event.touches.length !== 1 || isPreventScroll) { - lastTouchPosition.current = { x: -1, y: -1 } - return - } - - const { clientX, clientY } = event.touches[0] - lastTouchPosition.current = { x: clientX, y: clientY } - }, - [isPreventScroll] - ) - - const handleDefaultTouchmove = useCallback( - (event: TouchEvent) => { - event.preventDefault() - if (event.touches.length !== 1 || isPreventScroll) { - lastTouchPosition.current = { x: -1, y: -1 } - return - } - const { clientX, clientY } = event.touches[0] - - if (!isPreventVerticalScroll) { - rootRef.current?.scrollTo({ - top: rootRef.current?.scrollTop + (lastTouchPosition.current.y - clientY), - behavior: 'instant' - }) - } - - if (!isPreventHorizontalScroll) { - rootRef.current?.scrollTo({ - left: rootRef.current?.scrollLeft + (lastTouchPosition.current.x - clientX), - behavior: 'instant' - }) - } - - lastTouchPosition.current = { x: clientX, y: clientY } - }, - [isPreventScroll, isPreventHorizontalScroll, isPreventVerticalScroll] - ) - - const handleDefaultClickMiddleMouseButton = useCallback((event: MouseEvent) => { - if (event.button === 1) { - event.preventDefault() - } - }, []) - - const handleDefaultKeyDown = useCallback( - (event: KeyboardEvent) => { - if ( - isPreventScroll && - [ - 'ArrowUp', - 'ArrowDown', - 'ArrowLeft', - 'ArrowRight', - ' ', - '', - 'PageUp', - 'PageDown', - 'Home', - 'End' - ].find((value) => value === event.key) - ) { - event.preventDefault() - } - if ( - isPreventVerticalScroll && - ['ArrowUp', 'ArrowDown', ' ', '', 'PageUp', 'PageDown', 'Home', 'End'].find( - (value) => value === event.key - ) - ) { - event.preventDefault() - } - if ( - isPreventHorizontalScroll && - ['ArrowLeft', 'ArrowRight'].find((value) => value === event.key) - ) { - event.preventDefault() - } - }, - [isPreventScroll, isPreventHorizontalScroll, isPreventVerticalScroll] - ) - useImperativeHandle( ref, () => { @@ -247,72 +129,378 @@ const HideScrollbar = forwardRef((prop [] ) - useEffect(() => { - const hideScrollbarElement = rootRef.current + const maskRef = useRef(null) + const rootRef = useRef(null) + const contentRef = useRef(null) + const wheelListenerRef = useRef<(event: WheelEvent) => void>(() => undefined) + const lastScrollbarClickPositionRef = useRef({ x: -1, y: -1 }) + const lastScrollbarTouchPositionRef = useRef({ x: -1, y: -1 }) + const lastTouchPositionRef = useRef({ x: -1, y: -1 }) + const [verticalScrollbarWidth, setVerticalScrollbarWidth] = useState(0) + const [verticalScrollbarLength, setVerticalScrollbarLength] = useState(100) + const [verticalScrollbarPosition, setVerticalScrollbarPosition] = useState(0) + const [verticalScrollbarOnClick, setVerticalScrollbarOnClick] = useState(false) + const [verticalScrollbarOnTouch, setVerticalScrollbarOnTouch] = useState(false) + const [horizontalScrollbarWidth, setHorizontalScrollbarWidth] = useState(0) + const [horizontalScrollbarLength, setHorizontalScrollbarLength] = useState(100) + const [horizontalScrollbarPosition, setHorizontalScrollbarPosition] = useState(0) + const [horizontalScrollbarOnClick, setHorizontalScrollbarOnClick] = useState(false) + const [horizontalScrollbarOnTouch, setHorizontalScrollbarOnTouch] = useState(false) + const { + isPreventScroll, + isPreventVerticalScroll, + isPreventHorizontalScroll, + isShowVerticalScrollbar, + isShowHorizontalScrollbar, + ..._props + } = props + + const isPreventAnyScroll = + isPreventScroll || isPreventVerticalScroll || isPreventHorizontalScroll + + const handleDefaultTouchStart = useCallback( + (event: React.TouchEvent) => { + if (event.touches.length !== 1 || isPreventScroll) { + lastTouchPositionRef.current = { x: -1, y: -1 } + return + } + + const { clientX, clientY } = event.touches[0] + lastTouchPositionRef.current = { x: clientX, y: clientY } + }, + [isPreventScroll] + ) + + const handleDefaultTouchmove = useCallback( + (event: React.TouchEvent) => { + if (event.touches.length !== 1 || isPreventScroll) { + lastTouchPositionRef.current = { x: -1, y: -1 } + return + } + const { clientX, clientY } = event.touches[0] + + if (!isPreventVerticalScroll) { + rootRef.current?.scrollTo({ + top: rootRef.current?.scrollTop + (lastTouchPositionRef.current.y - clientY), + behavior: 'instant' + }) + } + + if (!isPreventHorizontalScroll) { + rootRef.current?.scrollTo({ + left: rootRef.current?.scrollLeft + (lastTouchPositionRef.current.x - clientX), + behavior: 'instant' + }) + } + + lastTouchPositionRef.current = { x: clientX, y: clientY } + }, + [isPreventHorizontalScroll, isPreventScroll, isPreventVerticalScroll] + ) + + const handleDefaultMouseDown = (event: React.MouseEvent) => { + if (isPreventAnyScroll) + if (event.button === 1) { + event.preventDefault() + } + } + + const handleDefaultKeyDown = useCallback( + (event: React.KeyboardEvent) => { + if ( + isPreventScroll && + [ + 'ArrowUp', + 'ArrowDown', + 'ArrowLeft', + 'ArrowRight', + ' ', + '', + 'PageUp', + 'PageDown', + 'Home', + 'End' + ].find((value) => value === event.key) + ) { + event.preventDefault() + } + if ( + isPreventVerticalScroll && + ['ArrowUp', 'ArrowDown', ' ', '', 'PageUp', 'PageDown', 'Home', 'End'].find( + (value) => value === event.key + ) + ) { + event.preventDefault() + } + if ( + isPreventHorizontalScroll && + ['ArrowLeft', 'ArrowRight'].find((value) => value === event.key) + ) { + event.preventDefault() + } + }, + [isPreventHorizontalScroll, isPreventScroll, isPreventVerticalScroll] + ) + + const handleScrollbarMouseEvent = (eventFlag: string, scrollbarFlag: string) => { + return (event: React.MouseEvent) => { + switch (eventFlag) { + case 'down': + lastScrollbarClickPositionRef.current = { x: event.clientX, y: event.clientY } + switch (scrollbarFlag) { + case 'vertical': + setVerticalScrollbarOnClick(true) + break + case 'horizontal': + setHorizontalScrollbarOnClick(true) + break + } + break + case 'up': + case 'leave': + setVerticalScrollbarOnClick(false) + setHorizontalScrollbarOnClick(false) + break + case 'move': + if (verticalScrollbarOnClick) { + rootRef.current?.scrollTo({ + top: + rootRef.current?.scrollTop + + ((event.clientY - lastScrollbarClickPositionRef.current.y) / + (rootRef.current?.clientHeight ?? 1)) * + (contentRef.current?.clientHeight ?? 0), + behavior: 'instant' + }) + } + if (horizontalScrollbarOnClick) { + rootRef.current?.scrollTo({ + left: + rootRef.current?.scrollLeft + + ((event.clientX - lastScrollbarClickPositionRef.current.x) / + (rootRef.current?.clientWidth ?? 1)) * + (contentRef.current?.clientWidth ?? 0), + behavior: 'instant' + }) + } + lastScrollbarClickPositionRef.current = { + x: event.clientX, + y: event.clientY + } + } + } + } + + const handleScrollbarTouchEvent = (eventFlag: string, scrollbarFlag: string) => { + return (event: React.TouchEvent) => { + switch (eventFlag) { + case 'start': + if (event.touches.length !== 1) { + return + } + lastScrollbarTouchPositionRef.current = { + x: event.touches[0].clientX, + y: event.touches[0].clientY + } + switch (scrollbarFlag) { + case 'vertical': + setVerticalScrollbarOnTouch(true) + break + case 'horizontal': + setHorizontalScrollbarOnTouch(true) + break + } + break + case 'end': + case 'cancel': + setVerticalScrollbarOnTouch(false) + setHorizontalScrollbarOnTouch(false) + break + case 'move': + if (event.touches.length !== 1) { + return + } + if (verticalScrollbarOnTouch) { + rootRef.current?.scrollTo({ + top: + rootRef.current?.scrollTop + + ((event.touches[0].clientY - + lastScrollbarClickPositionRef.current.y) / + (rootRef.current?.clientHeight ?? 1)) * + (contentRef.current?.clientHeight ?? 0), + behavior: 'instant' + }) + } + if (horizontalScrollbarOnTouch) { + rootRef.current?.scrollTo({ + left: + rootRef.current?.scrollLeft + + ((event.touches[0].clientX - + lastScrollbarClickPositionRef.current.x) / + (rootRef.current?.clientWidth ?? 1)) * + (contentRef.current?.clientWidth ?? 0), + behavior: 'instant' + }) + } + lastScrollbarClickPositionRef.current = { + x: event.touches[0].clientX, + y: event.touches[0].clientY + } + } + } + } + + const handleDefaultScroll = () => { + setVerticalScrollbarPosition( + ((rootRef.current?.scrollTop ?? 0) / (contentRef.current?.clientHeight ?? 1)) * 100 + ) + setHorizontalScrollbarPosition( + ((rootRef.current?.scrollLeft ?? 0) / (contentRef.current?.clientWidth ?? 1)) * 100 + ) + } + + useEffect(() => { const windowResizeListener = () => { setVerticalScrollbarWidth( - (hideScrollbarElement?.offsetWidth ?? 0) - (hideScrollbarElement?.clientWidth ?? 0) + (rootRef.current?.offsetWidth ?? 0) - (rootRef.current?.clientWidth ?? 0) ) setHorizontalScrollbarWidth( - (hideScrollbarElement?.offsetHeight ?? 0) - - (hideScrollbarElement?.clientHeight ?? 0) + (rootRef.current?.offsetHeight ?? 0) - (rootRef.current?.clientHeight ?? 0) ) return windowResizeListener } - setTimeout(() => { windowResizeListener() }, 1000) - window.addEventListener('resize', windowResizeListener()) - if (isPreventScroll || isPreventVerticalScroll || isPreventHorizontalScroll) { + + rootRef.current?.removeEventListener('wheel', wheelListenerRef.current) + if (isPreventAnyScroll) { + const handleDefaultWheel = (event: WheelEvent) => { + if (!event.altKey && !event.ctrlKey) { + if (isPreventScroll) { + event.preventDefault() + return + } + if (isPreventVerticalScroll && !event.shiftKey && !event.deltaX) { + event.preventDefault() + return + } + if (isPreventHorizontalScroll && (event.shiftKey || !event.deltaY)) { + event.preventDefault() + return + } + } + } + wheelListenerRef.current = handleDefaultWheel rootRef.current?.addEventListener('wheel', handleDefaultWheel, { passive: false }) - rootRef.current?.addEventListener('touchstart', handleDefaultTouchStart, { - passive: false - }) - rootRef.current?.addEventListener('touchmove', handleDefaultTouchmove, { - passive: false - }) - rootRef.current?.addEventListener('mousedown', handleDefaultClickMiddleMouseButton) - rootRef.current?.addEventListener('keydown', handleDefaultKeyDown) - } else { - rootRef.current?.removeEventListener('wheel', handleDefaultWheel) - rootRef.current?.removeEventListener('touchstart', handleDefaultTouchStart) - rootRef.current?.removeEventListener('touchmove', handleDefaultTouchmove) - rootRef.current?.removeEventListener('mousedown', handleDefaultClickMiddleMouseButton) - rootRef.current?.removeEventListener('keydown', handleDefaultKeyDown) } + rootRef.current && + setVerticalScrollbarLength( + (rootRef.current.clientHeight / (contentRef.current?.clientHeight ?? 0)) * 100 + ) + + rootRef.current && + setHorizontalScrollbarLength( + (rootRef.current.clientWidth / (contentRef.current?.clientWidth ?? 0)) * 100 + ) + return () => { window.removeEventListener('resize', windowResizeListener) } - }, [ - handleDefaultClickMiddleMouseButton, - handleDefaultKeyDown, - handleDefaultTouchStart, - handleDefaultTouchmove, - handleDefaultWheel, - isPreventHorizontalScroll, - isPreventScroll, - isPreventVerticalScroll - ]) + }, [isPreventAnyScroll, isPreventHorizontalScroll, isPreventScroll, isPreventVerticalScroll]) return ( <> -
+
- {props.children} +
+ {props.children} +
+
+ ) diff --git a/src/pages/MainFramework.tsx b/src/pages/MainFramework.tsx index 27a8284..d2b9da6 100644 --- a/src/pages/MainFramework.tsx +++ b/src/pages/MainFramework.tsx @@ -13,17 +13,33 @@ export const MainFrameworkContext = createContext<{ preventScroll: boolean setPreventScroll: (newValue: boolean) => void } + showVerticalScrollbarState: { + showVerticalScrollbar: boolean + setShowVerticalScrollbar: (newValue: boolean) => void + } + showHorizontalScrollbarState: { + showHorizontalScrollbar: boolean + setShowHorizontalScrollbar: (newValue: boolean) => void + } hideScrollbarRef: React.RefObject }>({ navbarHiddenState: { navbarHidden: false, setNavbarHidden: () => undefined }, - hideScrollbarRef: createRef(), preventScrollState: { preventScroll: false, setPreventScroll: () => undefined - } + }, + showVerticalScrollbarState: { + showVerticalScrollbar: false, + setShowVerticalScrollbar: () => undefined + }, + showHorizontalScrollbarState: { + showHorizontalScrollbar: false, + setShowHorizontalScrollbar: () => undefined + }, + hideScrollbarRef: createRef() }) const MainFramework: React.FC = () => { @@ -36,6 +52,8 @@ const MainFramework: React.FC = () => { const [navbarHidden, setNavbarHidden] = useState(true) const [preventScroll, setPreventScroll] = useState(false) + const [showVerticalScrollbar, setShowVerticalScrollbar] = useState(false) + const [showHorizontalScrollbar, setShowHorizontalScrollbar] = useState(false) useEffect(() => { setNavbarHidden(false) @@ -43,7 +61,11 @@ const MainFramework: React.FC = () => { return ( <> - +