React实现锚点跳转组件附带吸顶效果的示例代码

React实现锚点跳转组件附带吸顶效果

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;

到此这篇关于React实现锚点跳转组件附带吸顶效果的示例代码的文章就介绍到这了,更多相关React锚点跳转组件附带吸顶效果内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • react-native滑动吸顶效果的实现过程

    前言 最近公司开发方向偏向移动端,于是就被调去做RN(react-native),体验还不错,当前有个需求是首页中间吸顶的效果,虽然已经很久没写样式了,不过这种常见样式应该是so-easy,没成想翻车了,网上搜索换了几个方案都不行,最后去github上复制封装好的库来实现,现在把翻车过程记录下来. 需求效果 翻车过程 第一种方案 失败 一开始的思路是这样的,大众思路,我们需要监听页面的滚动状态,当页面滚动到要吸顶元素所处的位置的时候,我们设置它为固定定位,不过很遗憾,RN对于position属性

  • 浅谈react.js中实现tab吸顶效果的问题

    在react项目开发中有一个需求是,页面滚动到tab所在位置时,tab要固定在顶部. 实现的思路其实很简单,就是判断当滚动距离scrollTop大于tab距离页面顶部距离offsetTop时,将tab的position变为fixed. 在react中,我在state中设置一个navTop属性,切换这个属性的值为true或者false,然后tab标签使用classnames()这个方法来利用navTop的值添加类名fixed. 一开始我是这样写的: import cs from 'classnam

  • React实现锚点跳转组件附带吸顶效果的示例代码

    React实现锚点跳转组件附带吸顶效果 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 { att

  • Vue实现内部组件轮播切换效果的示例代码

    对于那些不需要路由的内部组件,在切换的时候希望增加一个轮播过渡的效果,效果如下: 我们可以引入一个轮播组件,但是有个问题,通常轮播组件都会把所有的slide都渲染出来再进行切换,这样就导致所有的资源都会触发加载,这可能不是我们所期待的,毕竟如果slide比较多的情况需要一次性加载的图片等资源太多了.所以我们可以手动简单地写一个,满足需求即可. 现在一步步来实现这个功能,先写一个实现基本切换的demo. 1. 实现切换 先用vue-cli搭建一个工程脚手架,使用以下命令: npm install

  • 使用JavaScript 实现时间轴与动画效果的示例代码(前端组件化)

    目录 代码整理 JavaScript 中的 "帧" 实现"帧"的方法 1. setInterval 2. setTimeout 3. requestAnimationFrame 实现 Timeline 时间轴 实现 start 函数 实现 Animation 类 设计时间线的更新 添加 Delay 属性支持 实现暂停和重启功能 实现 Pause 实现 Resume 上一篇文章<用 JSX 实现 Carousel 轮播组件>中,我们实现了一个 "

  • vue组件中使用iframe元素的示例代码

    本文介绍了vue组件中使用iframe元素的示例代码,分享给大家,具体如下: 需要在本页面中展示vue组件中的超链接,地址栏不改变的方法: <template> <div class="accept-container"> <div class="go-back" v-show="goBackState" @click="goBack">GoBack</div> <ul&g

  • iOS如何跳转到App Store下载评分页面示例代码

    前言 目前很多应用是要求点击事件直接跳转到App Store,最近工作中就遇到了一个跳转 App Store 评分或者下载更新的功能,网上查到很多跳转方法,这里记录一下,下面话不多说了,来一起看看详细的介绍吧. 主要跳转方法有两种 使用官方 StoreKit.framework 框架 应用间跳转直接跳到 App Store 应用,并携带自己 App 的 AppID. 使用官方框架 苹果提供了StoreKit.framework框架,工程中可以导入这个框架的主头文件 #import <StoreK

  • vue.js自定义组件实现v-model双向数据绑定的示例代码

    我们都清楚v-model其实就是vue的一个语法糖,用于在表单控件或者组件上创建双向绑定. //表单控件上使用v-model <template> <input type="text" v-model="name" /> <input type="checkbox" v-model="checked"/> <!--上面的input和下面的input实现的效果是一样的--> <

  • react+ant design实现Table的增、删、改的示例代码

    本人小白一名,第一次学习react ,该资料为本人原创,采用的是react+ant design的Tabled的一个小demo,暂时只实现了增加,删除单行,多行删除有Bug,查看详情,呕心沥血耗时一周完成,禁止抄袭,转载请先留言, 1.main.jsx import React from 'react'; import ReactDom from 'react-dom'; import ExampleTable from './ExampleTable.jsx' ReactDom.render(

  • 使用Vue3+Vant组件实现App搜索历史记录功能(示例代码)

    最近在开发一款新的app项目,我自己也是第一次接触app开发,经过团队的一段时间研究调查,决定使用Vue3+Vant前端组件的模式进行开发,vue2开发我们已经用过几个项目了,所以决定这一次尝试使用Vue3来进行前段开发. 我刚开始负责搜索功能的开发,有历史搜索记录的需求,一开始我认为这是记录的存储信息也会放在一个数据库表里面,但经过一番调查,发现并不是这样,而是要存储在本地.但是网上的方法也并没有完全解决问题,经过一番尝试,终于给搞好了,话不多说,直接上效果图. 初始化不显示历史搜索记录 回车

  • Anroid四大组件service之本地服务的示例代码

    服务是Android四大组件之一,与Activity一样,代表可执行程序.但Service不像Activity有可操作的用户界面,它是一直在后台运行.用通俗易懂点的话来说: 如果某个应用要在运行时向用户呈现可操作的信息就应该选择Activity,如果不是就选择Service. Service的生命周期如下: Service只会被创建一次,也只会被销毁一次.那么,如何创建本地服务呢? 实现代码如下: package temp.com.androidserivce; import android.a

  • Vue中组件之间数据的传递的示例代码

    Vue中组件的作用域是隔离的,父组件中的数值子组件看不到!也就是说,用angular作比喻,组件的scope天生是scope:()的! 如果父组件需要往子组件中传数据,此时应该使用标签属性: <div id="app"> <my-compo c="886"></my-compo> </div> 子组件中,用props声明这个值即可.并且在template里面可以直接使用{{c}}来获得这个属性,而不需要写为{{this

随机推荐