阅读 81

react组件库系列:实现Anchor组件

组件基本样式

image.png

因为滚动到了基础锚点这个标题上,所以上方的Anchor组件中“基础锚点”字体高亮了

image.png

基本用法

<Anchor targetOffset={150}>         <AnchorItem href="#基础锚点" title="基础锚点" />         <AnchorItem href="#多级锚点" title="多级锚点" />         <AnchorItem href="#指定容器锚点" title="指定容器锚点" />         <AnchorItem href="#特定交互锚点" title="特定交互锚点" />         <AnchorItem href="#尺寸" title="尺寸"></AnchorItem> </Anchor> 复制代码

我们主要讲解思路:

  • 首先,如何判断,此时有标题已经进入了可视区域(浏览器窗口),然后把对应的AnchorItem组件颜色改成蓝色,表示正在预览此区域

  • 然后,如何在点击AnchorItem的时候,滚动条滑动至对应的区域

初始化组件,判断浏览器窗口是否有锚点进入

我们的要跳转到的标题,需要id名字跟

<AnchorItem href="#基础锚点" title="基础锚点" /> 复制代码

上的href相同,例如

image.png

所以我们可以通过document.querySelector(href),来获取到不同锚点对应的dom的标题是哪个。

接着,我们用一个intervalRef来收集所有锚点的href属性,目的是通过document.querySelector(href)来获取到锚所有锚点对应的dom标题.

所以整个架构是外层有一个context收集AnchorItem的信息,如下:

  <AnchorContext.Provider         value={{           onClick: handleClick,           activeItem,           registerItem,           unregisterItem,         }}       >           {children}         </div>       </AnchorContext.Provider> 复制代码

registerItem就是注册函数

AnchorItem组件注册代码如下:

  useEffect(() => {     registerItem(href);     return () => unregisterItem(href);   }, [href, registerItem, unregisterItem]); 复制代码

在AnchorItem组件上还绑定了onClick事件,在AnchorContext上传给子组件的,用来滚动到对应标题上,如下:

<div     >       <a         href={href}         title={titleAttr}         target={target}         onClick={(e) => handleClick(e)}       >         {title}       </a>     </div> 复制代码

然后我们继续看registerItem是个什么函数:

 const intervalRef = useRef<IntervalRef>({       items: [],       scrollContainer: canUseDocument ? window : null,       handleScrollLock: false,     });     /**      * 注册锚点      * @param href 链接      */     const registerItem = (href: string): void => {       const { items } = intervalRef.current;       if (/#(\S+)$/.test(href) && items.indexOf(href) < 0) items.push(href);     }; 复制代码

可以看出来intervalRef.current.items负责收集所有的子节点href信息。

因为父节点的context收集了这个信息才在初始化的时候,能够用dom的api获取到href对应的dom,然后计算其是否自己距离浏览器窗口顶部

  useEffect(() => {       // 这里intervalRef.current.scrollContainer就是window       const { scrollContainer } = intervalRef.current;       handleScroll();       scrollContainer.addEventListener('scroll', handleScroll);       return () => {         scrollContainer.removeEventListener('scroll', handleScroll);       };     }, [container, handleScroll]); 复制代码

所以这里最关键的代码在handleScroll,最关键的在于就算每一个标题的getBoundingClientRect().top的值,就是当前dom跟浏览器顶部高度的值,然后其中小于等于0的中,最大的那个就是当前锚点应该选中的值

具体代码如下:

 // 这段代码一看就是新手写的,没办法,这是源码,就硬着头皮解读一下吧  const handleScroll = useCallback(() => {       // 获取window元素       const { scrollContainer  } = intervalRef.current;       // 获取到所有注册的herf       const { items } = intervalRef.current;       const filters: { top: number; href: string }[] = [];       let active = '';       // 找出所有当前 top 小于预设值       items.forEach((href) => {         const anchor = document.querySelector(href);         if (!anchor) return;         // anchor.getBoundingClientRect().top是指元素到浏览器窗口顶部的距离         // document.documentElement.clientTop是指html文档上边框的高度,一般都是0         const top = anchor.getBoundingClientRect().top - document.documentElement.clientTop;         // bounds + targetOffset可以理解为想要到浏览器顶部的空白区域         // bounds默认是5,targetOffset默认是0         if (top <= bounds + targetOffset) {           filters.push({             href,             top,           });         }       });              // 找出小于预设值集合中top最大的       if (filters.length) {         const latest = filters.reduce((prev, cur) => (prev.top > cur.top ? prev : cur));         active = latest.href;       }       // 将当前需要激活的锚点通过setActiveItem更新       if (active !== activeItem) {         onChange?.(active, activeItem);         setActiveItem(active);       }     }, [activeItem, bounds, onChange, targetOffset]); 复制代码

是不是很简单啊,哈哈,就是一个getBoundingClientRect().top API的运用而已。

然后来个小插曲,就是我们知道哪个锚点被激活了,所以对应锚点上的样式就需要改变,比如颜色变为蓝色,这个咋做呢?

useEffect监听被激活的锚点属性,然后更改样式即可。

如何在点击AnchorItem的时候,滚动条滑动至对应的区域

首先我们在锚点上注册一个点击事件,点击就触发滚动

    const handleClick = (item: Item, e: React.MouseEvent<HTMLDivElement>) => {       onClick?.({ e, ...item });       handleScrollTo(item.href);     }; 复制代码

接着我们看看handleScrollTo是如何处理的,传参注意是传的href.

这里的核心逻辑是,利用 document.documentElement.scrollTop = 锚点标题距离页面顶端的距离;

来实现定位的效果

    const handleScrollTo = (link: string) => {         // 找到锚点对应的标题的dom       const anchor = document.querySelector(link);       if (!anchor) return;       onChange?.(link, activeItem);       setActiveItem(link);       const { scrollContainer } = intervalRef.current;       // 这里因为scrollContainer是window,所以scrollTop是pageXOffset,意思是滚动多少距离       // 如果是普通dom则scrollTop就是这个dom的scrollTop属性       const scrollTop = getScroll(scrollContainer);       // 因为这里是window,所以offsetTop = anchor.getBoundingClientRect().top - document.documentElement.clientTop;             const offsetTop = getOffsetTop(anchor, scrollContainer);       // 所以真正滚动的距离就是滚动条的距离 + 锚点标题到浏览器视口的距离,减去targetOffset(锚点的偏移量)       const top = scrollTop + offsetTop - targetOffset;       document.documentElement.scrollTop = top;     }; 复制代码

好了,讲完,回家!


作者:孟祥_成都
链接:https://juejin.cn/post/7169176663782064135


文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐