React实现锚点跳转组件附带吸顶效果的示例代码
绘绘~ 人气:0React实现锚点跳转组件附带吸顶效果
import React, { useRef, useState, useEffect } from 'react'; import styles from './index.less'; import classnames from 'classnames'; function AnchorTabber(props) { const root = useRef(null); const header = useRef(null); const { attrbute = 'data-anchor', offsetY = 60, list = [], initialKey = list[0]?.key, children, } = props; const [state, setState] = useState({ activeKey: initialKey, }); const [key2Bottom, setKey2Bottom] = useState({ key2Bottom: {}, }); /** @type { HTMLDivElement } */ const scrollElement = document.querySelector('#root').firstChild; function scrollTo(key) { // if(!scrollElement) { // scrollElement = document.querySelector('#root').firstChild; // } const attribute = attrbute; /** @type { HTMLDivElement } */ const targetEl = root.current?.querySelector(`[${attribute}=${key}]`); if (targetEl) { const clientRect = targetEl.getBoundingClientRect(); const top = scrollElement.scrollTop + clientRect.top - offsetY; scrollElement.scrollTo({ top, behavior: 'smooth', }); // targetEl.scrollIntoView({ behavior: 'smooth', block: 'start' }); } } function updateElementsPosition() { const elements = document.querySelectorAll(`[${attrbute}]`); Array.from(elements).forEach(element => { const targetAttr = element.getAttribute(`${attrbute}`); const clientRect = element.getBoundingClientRect(); const bottom = clientRect.top + scrollElement.scrollTop; key2Bottom[targetAttr] = bottom; }); setKey2Bottom(key2Bottom); } function handleScroll() { const top = scrollElement.scrollTop + offsetY; // eslint-disable-next-line no-unused-vars const target = Object.entries(key2Bottom) .sort(([, v1], [, v2]) => v2 - v1) .find(([, v]) => v <= top); if (target) { setState({ activeKey: target[0], }); } } useEffect(() => { updateElementsPosition(); // document.addEventListener('touchstart', updateElementsPosition); scrollElement.addEventListener('scroll', handleScroll); return () => { // document.removeEventListener('touchstart', updateElementsPosition); scrollElement.removeEventListener('scroll', handleScroll); }; }); function handleItemClick(tabber) { scrollTo(tabber.key); } function render() { let { activeKey } = state; if(typeof(activeKey) === "undefined") { activeKey = initialKey } return ( <div className={styles.mAnchorTabber}> <div className={styles.header} ref={header}> {list?.map(tabber => ( <div className={classnames(styles.item, { [styles.active]: activeKey === tabber.key })} onClick={() => handleItemClick(tabber)} > {tabber.name} </div> ))} </div> <div className={styles.container} ref={root}> {children} </div> </div> ); } return render(); } export default AnchorTabber;
对应样式(less)
.mAnchorTabber { .header { display: flex; align-items: center; position: sticky; top: 0; height: .len(46) []; box-sizing: border-box; box-shadow: 0 .len(2) [] .len(2) [] .len(1) [] #c4c4c4; background-color: @basecolor; .item { height: 100%; flex-grow: 1; flex-shrink: 0; display: flex; align-items: center; justify-content: center; font-weight: 400; font-size: @font-size-base-normal; line-height: .len(20) []; &.active { background-color: @bgc-product-detail; color: @basecolor; } } } }
.len(46) []这个改成对应 px单位即可,本单位是为了移动端适配转换
测试test
对应测试文件
import React from 'react'; import { connect } from 'dva'; import AnchorTabber from '@/components/m/AnchorTabber'; @connect(({ common }) => ({ common, })) class ApplicationsRecord extends React.Component { constructor(props) { super(props); this.state = { list: [ { name: 'test1', key: 'test1', }, { name: 'test2', key: 'test2', }, { name: 'test3', key: 'test3', }, ], }; } render() { const { list } = this.state; const index2Attr = { 0: 'test1', 100: 'test2', 200: 'test3', }; return ( <AnchorTabber list={list}> <ul className="list"> {new Array(1000).fill(null).map((_item, index) => ( <li data-anchor={index2Attr[index]}>{index}</li> ))} </ul> </AnchorTabber> ); } } export default ApplicationsRecord;
加载全部内容