自动获取锚点的导航栏——vue版本

组件

<template>
  <div class="tkCatalogue">
    <CataItem
      v-for="(item, index) in anchorList"
      :key="item.id"
      :cataData="item"
      :currentTarget="currentTarget"
      @toAnchor="toAnchor"
    />
  </div>
</template>

<script>
import CataItem from './cataItem.vue';
import { throttle } from 'lodash';
export default {
  name: 'TkCatalogue',
  components: {
    CataItem
  },
  props: {},
  data() {
    return {
      currentTarget: '',
      anchorList: [],
      tagList: [],
      mutationObserver: null
    };
  },
  mounted() {
    this.observer();
  },
  beforeDestroy() {
    window.onscroll = null;
    if (this.mutationObserver) {
      this.mutationObserver.disconnect();
      this.mutationObserver = null;
    }
  },
  watch: {
    // anchorList: {
    //   handler: function(val) {
    //     this.currentTarget = (val && val[0]?.id) || '';
    //   },
    //   immediate: true
    // }
  },
  methods: {
    // 监听dom节点变化
    observer() {
      const haoroomsId = document.getElementById('root');
      if (!haoroomsId) {
        setTimeout(() => {
          this.observer();
        }, 200);
        return;
      }
      // haoroomsId.addEventListener('scroll', this.handleScroll, true);
      const MutationObserver =
        window.MutationObserver ||
        window.webkitMutationObserver ||
        window.MozMutationObserver;

      let recordHeight = 0;
      const selfGetNodeList = this.getNodeList;
      this.mutationObserver = new MutationObserver(function(mutations) {
        const height = window
          .getComputedStyle(haoroomsId)
          .getPropertyValue('height');
        if (height === recordHeight) {
          return;
        }
        recordHeight = height;
        selfGetNodeList();
      });

      this.mutationObserver.observe(haoroomsId, {
        childList: true, // 子节点的变动(新增、删除或者更改)
        characterData: true, // 节点内容或节点文本的变动
        subtree: true // 是否将观察器应用于该节点的所有后代节点
      });
    },
    getNodeList() {
      this.tagList = Array.from(document.getElementsByClassName('anchorTitle'));
      this.anchorList = this.tagList.map((item, index) => {
        return {
          content: item.childNodes[0].innerText,
          id: index,
          instance: item,
          offsetTop: item.offsetTop
        };
      });

      this.currentTarget = (this.anchorList && this.anchorList[0]?.id) || '';
    },
    handleScroll() {
      const haoroomsId = document.getElementById('app');
      //   this.getNodeList();
      const scrollTop = haoroomsId.pageYOffset || haoroomsId.scrollTop;

      const clientHeight =
        document.documentElement.clientHeight || document.body.clientHeight;
      const scrollHeight =
        document.documentElement.scrollHeight || document.body.scrollHeight;
      for (let index = 0; index < this.anchorList.length; index++) {
        const item = this.anchorList[index];
        // 如果是第一个节点就直接显示第一个
        if (scrollTop <= this.anchorList[0].offsetTop) {
          this.currentTarget = this.anchorList[0].id;
        } else if (
          scrollHeight > clientHeight &&
          scrollTop + clientHeight === scrollHeight
        ) {
          // 如果到底了直接显示最后一个
          this.currentTarget = this.anchorList[this.anchorList.length - 1].id;
        } else if (this.anchorList[index + 1]) {
          if (
            scrollTop >= item.offsetTop - 50 &&
            scrollTop < this.anchorList[index + 1].offsetTop - 50
          ) {
            this.currentTarget = item.id;
            break;
          }
        } else {
          this.currentTarget = item.id;
        }
      }
    },
    toAnchor(id) {
      this.currentTarget = id;
    }
  }
};
</script>

<style lang="scss" scoped>
.tkCatalogue {
  background: #ffffff;
  box-shadow: 0px 4px 8px 0px rgba(0, 0, 0, 0.2);
  border-radius: 4px;
  position: fixed;
  right: 50px;
  bottom: 15%;
  padding: 20px 20px 20px 14px;
  z-index: 999999;
}
</style>

子组件

<template>
  <div class="cataItem" @click="toAnchor">
    <div
      :class="
        currentTarget == cataData.id
          ? 'currentTarget contentWrap'
          : 'contentWrap'
      "
    >
      <div class="circle">
        <div class="realCircle"></div>
      </div>
      <p class="context">{{ cataData.content }}</p>
    </div>
    <div class="coluGray"></div>
  </div>
</template>

<script>
export default {
  name: 'CataItem',
  props: {
    cataData: {
      type: Object,
      default: {}
    },
    currentTarget: {
      type: String,
      default: 0
    }
  },
  methods: {
    toAnchor() {
      const id = this.cataData.id;
      this.cataData?.instance?.scrollIntoView({
        behavior: 'smooth'
      });
      this.$emit('toAnchor', id);
    }
  }
};
</script>

<style lang="scss" scoped>
p {
  margin: 0;
}
.cataItem {
  padding-bottom: 12px;
  position: relative;
  &:last-child {
    padding-bottom: 0;
    .coluGray {
      display: none;
    }
  }
  .currentTarget {
    .realCircle {
      width: 12px !important;
      height: 12px !important;
      background: #ff7a2a !important;
    }
    .context {
      color: #ff7a2a !important;
    }
  }
  .contentWrap {
    display: flex;
    align-items: center;
    cursor: pointer;
    .circle {
      width: 12px;
      height: 12px;
      display: flex;
      align-items: center;
      justify-content: center;
      .realCircle {
        width: 8px;
        height: 8px;
        border-radius: 50%;
        background: #fad6b2;
      }
    }
    .context {
      ont-size: 14px;
      color: #333333;
      font-weight: 400;
      margin-left: 4px;
      padding: 6px 8px;
      &:hover {
        background: #f2f2f3;
        border-radius: 2px;
      }
    }
  }
  .coluGray {
    width: 2px;
    height: 36px;
    background-color: #d7d8da;
    position: absolute;
    left: 5px;
    top: 19px;
    z-index: -1;
  }
}
</style>

使用方式

组件引入

import TkCatalogue from '@components/tk-catalogue';
  <TkCatalogue />

要定位的段落标题添加class anchorTitle

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容