使用react实现的一个非常常见的需求,锚点定位(锚点滚动),实现和使用都非常的简单,我这边采用组件的方式实现,方便复用;代码如下请食用。
AnchorScroll组件(核心组件)
文件路径:/anchor-scroll/AnchorScroll.tsx
import { debounce } from '@/utils'
import { Tabs } from 'antd'
import React, { useEffect, useRef, useState } from 'react'
const { TabPane } = Tabs
type AnchorScrollType = {
tabs: React.ReactNode[]
initActiveKey?: String
children: any
className?: string
height?: number // 滚动内容高度 (设置高度后就不能实现吸顶效果)
style?: React.CSSProperties
contentStyle?: React.CSSProperties
tabBarStyle?: React.CSSProperties
tabBarTop?: number // 距离顶部的距离
offsetTop?: number // 滚动偏移量
containerId?: string // 容器Id
onChange?: Function
}
export default function AnchorScroll(props: AnchorScrollType) {
let {
tabs = [],
initActiveKey = '0',
children,
className = '',
tabBarStyle = {},
style = {},
contentStyle = {},
height,
tabBarTop = 0,
offsetTop = 0,
containerId,
onChange,
} = props
const [activeKey, setActiveKey] = useState<any>(initActiveKey)
const isClick = useRef(false) // 是否是点击触发
contentStyle = height ? { ...contentStyle, height: height + 'px', overflow: 'auto' } : { ...contentStyle }
// 初始化
useEffect(() => {
setActiveKey(initActiveKey)
if (initActiveKey === '0') return
onChangeTabs(initActiveKey)
}, [initActiveKey])
// 点击跳转到锚点
const onChangeTabs = (e: any) => {
onChange && onChange(e)
setActiveKey(e)
isClick.current = true
const anchorElement = document.getElementById('anchor-scroll-item-' + e)
if (anchorElement) {
if (height) {
anchorElement.scrollIntoView({ behavior: 'smooth' })
} else {
const scrollBox = document.querySelector('#' + containerId)
scrollBox?.setAttribute('style', 'position: relative')
scrollBox?.scrollTo(0, anchorElement.offsetTop - offsetTop)
}
}
}
// 根据滚动定位tab
const onScroll = debounce(
(e: any) => {
let result: any[] = []
tabs.forEach((item, index) => {
const element = document.getElementById('anchor-scroll-item-' + index)
result.push({ key: index.toString(), top: element?.getBoundingClientRect().top })
})
if (isClick.current) {
isClick.current = false
return
}
result.forEach((item, index) => {
if (e.target.offsetTop >= item.top) {
setActiveKey(item.key)
} else {
isClick.current = false
}
})
},
30,
false
)
useEffect(() => {
window.addEventListener('scroll', onScroll, true)
return () => {
const scrollBox = document.querySelector('#' + containerId)
scrollBox?.scrollTo(0, 0)
scrollBox?.setAttribute('style', 'position: static')
window.removeEventListener('scroll', onScroll, true)
}
}, [])
return (
<div className={`anchor-scroll ${className}`} style={{ ...style }}>
<Tabs
tabBarStyle={{
padding: '0 24px',
...tabBarStyle,
}}
activeKey={activeKey}
destroyInactiveTabPane={true}
className="tabs"
style={{ top: tabBarTop + 'px' }}
onChange={onChangeTabs}
>
{tabs.map((item: any, index: any) => {
return <TabPane tab={item} key={index}></TabPane>
})}
</Tabs>
<div className="anchor-scroll-content" style={{ ...contentStyle }}>
{
// 只渲染符合规格的子组件
findChild(children).map((item, index) => {
return React.cloneElement(item, { key: index, index: index })
})
}
</div>
</div>
)
}
// 查找子组件
function findChild(children: any[]): any[] {
let result: any[] = []
React.Children.forEach(children, (child, index) => {
if (child?.type?.defaultProps?.name === 'AnchorScrollItem') {
result.push(child)
}
})
return result
}
AnchorScrollItem组件(组件目的就是绑定id)
文件路径:/anchor-scroll/AnchorScrollItem.tsx
import React from 'react'
export default function AnchorScrollItem(props: any) {
return <div id={'anchor-scroll-item-' + props.index}>{props.children}</div>
}
使用方式
import AnchorScroll from '@/components/anchor-scroll/AnchorScroll'
import AnchorScrollItem from '@/components/anchor-scroll/AnchorScrollItem'
import React from 'react'
export default function Test(props: any) {
return (
<AnchorScroll initActiveKey="1" tabs={['tab1', 'tab2', 'tab3']}>
<AnchorScrollItem>
<div
style={{
width: '100%',
height: '500px',
border: '1px solid red',
marginBottom: '20px',
}}
>
test1
</div>
</AnchorScrollItem>
<AnchorScrollItem>
<div
style={{
width: '100%',
height: '1000px',
border: '1px solid red',
marginBottom: '20px',
}}
>
test2
</div>
</AnchorScrollItem>
<AnchorScrollItem>
<div
style={{
width: '100%',
height: '200px',
border: '1px solid red',
}}
>
test3
</div>
</AnchorScrollItem>
</AnchorScroll>
)
}