1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
| import React, { ReactNode, useEffect, useRef, useState } from 'react' import classNames from 'classnames'
import { If } from 'babel-plugin-jsx-control-statements' import styles from './index.less' import { useScroll } from 'ahooks'
interface ISidebar { // 宽度,默认270 width?: number // 是否展示顶部栏,默认true showLine?: boolean // 是否需要fixed定位,默认true needFixed?: boolean // 页面位置,默认右侧 position?: 'left' | 'right' title?: ReactNode footer?: ReactNode children?: ReactNode wrapClassName?: string className?: string // 相邻内容元素 nextElement?: HTMLElement }
const Index = (props: ISidebar) => { const { width, showLine, needFixed, position, title, footer, children, wrapClassName, className, nextElement, } = props const [fixed, setFixed] = useState<boolean>(false) const sidebarWrapRef = useRef(undefined) const sidebarRef = useRef(undefined) const sidebarOffsetTopRef = useRef<number>(undefined) const sidebarOffsetHeightRef = useRef<number>(undefined) const documentScroll = useScroll() const [isReachFooter, setIsReach] = useState(false)
const nextElementHeightRef = useRef<number>(undefined) const topValueWhenAbsolute = useRef<number>(undefined) const pageTotalHeightRef = useRef<number>(undefined) // 侧边栏内容长度是否能盖到底部 const [contentOverFooter, setIsContentOverFooter] = useState(false) useEffect(() => { if (sidebarOffsetTopRef.current === undefined) { setSideBarContentInfo() } }, [])
useEffect(() => { if (!needFixed) { return } if (documentScroll?.top > sidebarOffsetTopRef.current) { setFixed(true) } else { setFixed(false) } // 因为存在异步数据渲染 如果滚动中发现高度变化及时修正 if (getMaxHeightOfNextElements() !== nextElementHeightRef.current) { setWrapHeightByParent() getTotalHeightOfPage() // setSideBarContentInfo() setIsContentOverFooter(isContentOverFooter()) } setIsReach(getIsReachFooter()) }, [documentScroll.top])
const setSideBarContentInfo = () => { sidebarOffsetTopRef.current = sidebarRef.current.offsetTop sidebarOffsetHeightRef.current = sidebarRef.current.offsetHeight }
// 记录相邻内容元素高度 方便计算到footer距离 const setWrapHeightByParent = () => { // 如果传入 优先用传入 if (nextElement) { nextElementHeightRef.current = nextElement?.offsetHeight return } const maxHeight = getMaxHeightOfNextElements() nextElementHeightRef.current = maxHeight } // 获取相邻元素中最大高度 默认 const getMaxHeightOfNextElements = () => { const children = [...sidebarWrapRef.current?.parentElement.children] const heightArr = children.map((item) => item?.offsetHeight) const maxHeight = Math.max(...heightArr)
return maxHeight } // critical value 获取到页脚时距离顶部距离 const getTopValueWhenReachFooter = (sidebarOffsetHeight) => { topValueWhenAbsolute.current = getTopPlusBodyHeight() - sidebarOffsetHeight return topValueWhenAbsolute.current }
// 头+内容主体高度 const getTopPlusBodyHeight = () => { return nextElementHeightRef.current + sidebarOffsetTopRef.current } const getFooterHeight = () => { return pageTotalHeightRef.current - getTopPlusBodyHeight() }
const isContentOverFooter = () => { const isOver = getFooterHeight() + sidebarRef.current.offsetHeight > window.innerHeight return isOver } const getContentStyle = () => { const style = { width } if (needFixed) { style['top'] = isReachFooter ? topValueWhenAbsolute.current : 0 } return style } const contentCls = classNames(styles.sidebar__content, { [styles.fixed]: needFixed && fixed && !isReachFooter, [styles.showLine]: showLine, [className]: className, [styles.absolute]: isReachFooter && needFixed && contentOverFooter, })
const getIsReachFooter = () => { const topValueWhenAbsolute = getTopValueWhenReachFooter(sidebarRef.current.offsetHeight) const isReach = documentScroll.top >= topValueWhenAbsolute return isReach }
const getTotalHeightOfPage = () => { const nextWrapElement = document.querySelector('#__next') const layout = nextWrapElement.firstElementChild // @ts-ignore pageTotalHeightRef.current = layout?.offsetHeight return pageTotalHeightRef.current }
return ( <div ref={sidebarWrapRef} className={classNames(styles.sidebar, { [styles.atLeft]: position === 'left', [wrapClassName]: wrapClassName, })} style={{ width }} > {fixed && <div className={styles.sidebar__shadow} />} <div className={contentCls} ref={sidebarRef} style={getContentStyle()}> <If condition={title}> <div className={styles.sidebar__title}>{title}</div> </If> <If condition={children}> <div className={styles.sidebar__body}>{children}</div> </If> <If condition={footer}> <div className={styles.sidebar__footer}>{footer}</div> </If> </div> </div> ) }
Index.defaultProps = { showLine: true, needFixed: true, width: 270, position: 'right', }
export default Index
|