Merge pull request 'Optimize scrolling. Add navbar auto hide and show.' (#11) from FatttSnake into dev

Reviewed-on: FatttSnake/fatweb-ui#11
This commit was merged in pull request #11.
This commit is contained in:
FatttSnake
2023-09-10 01:16:14 +08:00
4 changed files with 157 additions and 127 deletions

View File

@@ -1,12 +1,9 @@
import React from 'react'
interface FitFullscreenProps extends PropsWithChildren {
zIndex?: number
backgroundColor?: string
ref?: RefObject<HTMLDivElement>
}
const FitFullScreen: React.FC<FitFullscreenProps> = (props) => {
const FitFullScreen = forwardRef<HTMLDivElement, FitFullscreenProps>((props, ref) => {
return (
<>
<div
@@ -15,11 +12,12 @@ const FitFullScreen: React.FC<FitFullscreenProps> = (props) => {
zIndex: props.zIndex,
backgroundColor: props.backgroundColor
}}
ref={ref}
>
{props.children}
</div>
</>
)
}
})
export default FitFullScreen

View File

@@ -1,19 +1,130 @@
import React from 'react'
import '@/assets/css/hide-scrollbar.scss'
interface HideScrollbarProps extends PropsWithChildren {
getHideScrollbarRef: (hideScrollbarRef: RefObject<HTMLElement>) => void
export interface HideScrollbarElement {
scrollTo(x: number, y: number): void
scrollX(x: number): void
scrollY(y: number): void
scrollLeft(length: number): void
scrollRight(length: number): void
scrollUp(length: number): void
scrollDown(length: number): void
getX(): number
getY(): number
addEventListenerWithType<K extends keyof HTMLElementEventMap>(
type: K,
listener: (this: HTMLDivElement, ev: HTMLElementEventMap[K]) => never,
options?: boolean | AddEventListenerOptions
): void
addEventListener(
type: string,
listener: EventListenerOrEventListenerObject,
options?: boolean | AddEventListenerOptions
): void
removeEventListenerWithType<K extends keyof HTMLElementEventMap>(
type: K,
listener: (this: HTMLDivElement, ev: HTMLElementEventMap[K]) => never,
options?: boolean | EventListenerOptions
): void
removeEventListener(
type: string,
listener: EventListenerOrEventListenerObject,
options?: boolean | EventListenerOptions
): void
}
const HideScrollbar: React.FC<HideScrollbarProps> = (props) => {
const hideScrollbarRef = useRef<HTMLDivElement>(null)
const HideScrollbar = forwardRef<HideScrollbarElement, PropsWithChildren>((props, ref) => {
const rootRef = useRef<HTMLDivElement>(null)
const [verticalScrollbarWidth, setVerticalScrollbarWidth] = useState(0)
const [horizontalScrollbarWidth, setHorizontalScrollbarWidth] = useState(0)
props.getHideScrollbarRef(hideScrollbarRef)
useImperativeHandle<HideScrollbarElement, HideScrollbarElement>(
ref,
() => {
return {
scrollTo(x, y) {
rootRef.current?.scrollTo({
left: x,
top: y,
behavior: 'smooth'
})
},
scrollX(x) {
rootRef.current?.scrollTo({
left: x,
behavior: 'smooth'
})
},
scrollY(y) {
rootRef.current?.scrollTo({
top: y,
behavior: 'smooth'
})
},
scrollLeft(length) {
rootRef.current?.scrollTo({
left: rootRef.current?.scrollLeft - length,
behavior: 'smooth'
})
},
scrollRight(length) {
rootRef.current?.scrollTo({
left: rootRef.current?.scrollLeft + length,
behavior: 'smooth'
})
},
scrollUp(length) {
rootRef.current?.scrollTo({
top: rootRef.current?.scrollTop - length,
behavior: 'smooth'
})
},
scrollDown(length) {
rootRef.current?.scrollTo({
top: rootRef.current?.scrollTop + length,
behavior: 'smooth'
})
},
getX() {
return rootRef.current?.scrollLeft ?? 0
},
getY() {
return rootRef.current?.scrollTop ?? 0
},
addEventListenerWithType<K extends keyof HTMLElementEventMap>(
type: K,
listener: (this: HTMLDivElement, ev: HTMLElementEventMap[K]) => never,
options?: boolean | AddEventListenerOptions
): void {
rootRef.current?.addEventListener<K>(type, listener, options)
},
addEventListener(
type: string,
listener: EventListenerOrEventListenerObject,
options?: boolean | AddEventListenerOptions
): void {
rootRef.current?.addEventListener(type, listener, options)
},
removeEventListenerWithType<K extends keyof HTMLElementEventMap>(
type: K,
listener: (this: HTMLDivElement, ev: HTMLElementEventMap[K]) => never,
options?: boolean | EventListenerOptions
): void {
rootRef.current?.removeEventListener<K>(type, listener, options)
},
removeEventListener(
type: string,
listener: EventListenerOrEventListenerObject,
options?: boolean | EventListenerOptions
): void {
rootRef.current?.removeEventListener(type, listener, options)
}
}
},
[]
)
useEffect(() => {
const hideScrollbarElement = hideScrollbarRef.current
const hideScrollbarElement = rootRef.current
const windowResizeListener = () => {
setVerticalScrollbarWidth(
@@ -41,7 +152,7 @@ const HideScrollbar: React.FC<HideScrollbarProps> = (props) => {
return (
<>
<div
ref={hideScrollbarRef}
ref={rootRef}
id={'hide-scrollbar'}
style={{
width: `calc(100vw + ${verticalScrollbarWidth}px)`,
@@ -52,6 +163,6 @@ const HideScrollbar: React.FC<HideScrollbarProps> = (props) => {
</div>
</>
)
}
})
export default HideScrollbar

View File

@@ -7,7 +7,8 @@ import { MainFrameworkContext } from '@/pages/MainFramework.tsx'
const Home: React.FC = () => {
const {
controllers: { scrollY }
hideScrollbarRef,
navbarHiddenState: { navbarHidden, setNavbarHidden }
} = useContext(MainFrameworkContext)
const fitFullScreenRef = useRef<HTMLDivElement>(null)
@@ -33,9 +34,32 @@ const Home: React.FC = () => {
}
const handleScrollDown = () => {
scrollY(1000)
hideScrollbarRef.current?.scrollY(fitFullScreenRef.current?.offsetHeight ?? 0)
}
useEffect(() => {
const hideScrollbarDOM = hideScrollbarRef.current
const scrollListener = () => {
if (
hideScrollbarDOM &&
fitFullScreenRef.current &&
hideScrollbarDOM.getY() < fitFullScreenRef.current?.offsetHeight
) {
if (!navbarHidden) {
setNavbarHidden(true)
}
} else {
if (navbarHidden) {
setNavbarHidden(false)
}
}
}
hideScrollbarDOM?.addEventListener('scroll', scrollListener)
return () => {
hideScrollbarDOM?.removeEventListener('scroll', scrollListener)
}
}, [hideScrollbarRef, navbarHidden, setNavbarHidden])
return (
<>
<FitFullScreen zIndex={100} backgroundColor={'#FBFBFB'} ref={fitFullScreenRef}>
@@ -55,6 +79,7 @@ const Home: React.FC = () => {
</FitCenter>
</FitFullScreen>
<FitFullScreen />
<FitFullScreen />
</>
)
}

View File

@@ -2,136 +2,32 @@ import React from 'react'
import '@/assets/css/header.scss'
import LoadingMask from '@/components/LoadingMask.tsx'
import router from '@/router'
import HideScrollbar from '@/components/HideScrollbar.tsx'
import HideScrollbar, { HideScrollbarElement } from '@/components/HideScrollbar.tsx'
export const MainFrameworkContext = createContext<{
navbarHiddenState: {
navbarHidden: boolean
setNavbarHidden: (newValue: boolean) => void
}
controllers: {
scrollX(x: number): void
scrollY(y: number): void
scrollTo(x: number, y: number): void
getX(): number | undefined
getY(): number | undefined
scrollLeft(length: number): void
scrollRight(length: number): void
scrollUp(length: number): void
scrollDown(length: number): void
}
hideScrollbarRef: RefObject<HideScrollbarElement>
}>({
navbarHiddenState: {
navbarHidden: false,
setNavbarHidden: () => undefined
},
controllers: {
scrollX: function (): void {
throw new Error('Function not implemented.')
},
scrollY: function (): void {
throw new Error('Function not implemented.')
},
scrollTo: function (): void {
throw new Error('Function not implemented.')
},
getX: function (): number | undefined {
throw new Error('Function not implemented.')
},
getY: function (): number | undefined {
throw new Error('Function not implemented.')
},
scrollLeft: function (): void {
throw new Error('Function not implemented.')
},
scrollRight: function (): void {
throw new Error('Function not implemented.')
},
scrollUp: function (): void {
throw new Error('Function not implemented.')
},
scrollDown: function (): void {
throw new Error('Function not implemented.')
}
}
hideScrollbarRef: createRef()
})
const MainFramework: React.FC = () => {
const [navbarHidden, setNavbarHidden] = useState(false)
const [navbarHidden, setNavbarHidden] = useState(true)
const [hideScrollbarRef, setHideScrollbarRef] = useState<RefObject<HTMLElement>>()
const hideScrollbarRef = useRef<HideScrollbarElement>(null)
const routeId = useMatches()[1].id
const routeChildren = router.routes[0].children?.find((value) => value.id === routeId)?.children
const getHideScrollbarRef = (ref: RefObject<HTMLElement>) => {
setHideScrollbarRef(ref)
}
const hideScrollbarRefController = {
scrollX(x: number): void {
hideScrollbarRef?.current?.scrollTo({
left: x,
top: hideScrollbarRef?.current?.scrollTop,
behavior: 'smooth'
})
},
scrollY(y: number): void {
hideScrollbarRef?.current?.scrollTo({
left: hideScrollbarRef?.current?.scrollLeft,
top: y,
behavior: 'smooth'
})
},
scrollLeft(length: number): void {
hideScrollbarRef?.current?.scrollTo({
left: (hideScrollbarRef?.current?.scrollLeft ?? 0) - length,
behavior: 'smooth'
})
},
scrollRight(length: number): void {
hideScrollbarRef?.current?.scrollTo({
left: (hideScrollbarRef?.current?.scrollLeft ?? 0) + length,
behavior: 'smooth'
})
},
scrollUp(length: number): void {
hideScrollbarRef?.current?.scrollTo({
top: (hideScrollbarRef?.current?.scrollTop ?? 0) - length,
behavior: 'smooth'
})
},
scrollDown(length: number): void {
hideScrollbarRef?.current?.scrollTo({
top: (hideScrollbarRef?.current?.scrollTop ?? 0) + length,
behavior: 'smooth'
})
},
scrollTo(x: number, y: number): void {
hideScrollbarRef?.current?.scrollTo({
left: x,
top: y,
behavior: 'smooth'
})
},
getX(): number | undefined {
return hideScrollbarRef?.current?.scrollLeft
},
getY(): number | undefined {
return hideScrollbarRef?.current?.scrollTop
}
}
return (
<>
<HideScrollbar getHideScrollbarRef={getHideScrollbarRef}>
<HideScrollbar ref={hideScrollbarRef}>
<div className={'body'}>
<header className={'nav ' + (navbarHidden ? 'hide' : '')}>
<a className={'logo'} href={'https://fatweb.top'}>
@@ -160,7 +56,7 @@ const MainFramework: React.FC = () => {
<MainFrameworkContext.Provider
value={{
navbarHiddenState: { navbarHidden, setNavbarHidden },
controllers: hideScrollbarRefController
hideScrollbarRef
}}
>
<Suspense