likes
comments
collection
share

关于锚点功能的相关设计与实现

作者站长头像
站长
· 阅读数 35

Vue锚点组件实现一些设计

锚点组件 通常用于页面内部的导航和跳转功能,它能够帮助解决以下几个问题:

关于锚点功能的相关设计与实现

  1. 页面内部导航: 当页面内容较长或者包含多个章节时,用户可能需要快速定位到感兴趣的内容区域。使用锚点组件可以创建类似目录的导航结构,用户点击导航链接后,页面会平滑滚动到相应的内容区域

  2. 页面间跳转:在单页应用(Single Page Application)中,页面往往通过不同的路由进行切换。锚点组件可以与路由配合使用,实现页面内部的平滑滚动切换效果,提供更好的用户体验。

  3. 快速定位应用:在长文档或博客文章中,可能存在需要引用其他部分内容的情况。使用锚点组件可以为特定内容区域生成唯一的锚点链接,方便其他页面引用该内容,并直接跳转到相关位置。

  4. SEO 优化:搜索引擎优化(SEO)是网站开发中重要的一环。使用锚点组件可以为每个页面部分生成独立的 URL 锚点,使搜索引擎能够更好地索引和理解页面的结构,提升页面的可搜索性和排名。

其实可以看到Elemnet UI中其实就没有锚点组件,使用起来再长文档中想找哪项得示例确实体验不是很好。 像ant.designIview UI等组件中得阅读感确实要方便不少。

关于锚点功能的相关设计与实现

那么怎么实现一个自己得锚点组件呢?有哪些需要注意得点呢?下面咱们一步一步来实现该组件。组件设计就参考Iview UI得组件模块划分了<Anchor /><AnchorLink />如下:

<Anchor :affix="false" show-ink>
  <AnchorLink href="#basic_usage" title="Basic Usage" />
  <AnchorLink href="#static_position" title="Static Position" />
  <AnchorLink href="#API" title="API">
    <AnchorLink href="#Anchor_props" title="Anchor props" />
    <AnchorLink href="#Anchor_events" title="Anchor events" />
    <AnchorLink href="#AnchorLink_props" title="AnchorLink props" />
  </AnchorLink>
</Anchor>

锚点功能简单实现

其实再一个项目上也有好多点击某一处,然后跳到某个位置(指的是都再一个界面上)。如下两种方式:

方式一使用a标签

<a name="top"></a>
<br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /> 
<a href="#top"></a>

方式二给元素绑定ID,使用js经行锚点跳转:(scrollIntoView)

const anchorLink = (id) => document.getElementById(id).scrollIntoView();

锚点组件设计

锚点组件一些设计与实现功能,咱们就实现核心功能主要是了解锚点组件应该怎么实现

  1. 点击跳转(使用a标签默认事件即可)。
  2. 根据界面滚动得高度选中高亮锚点(监听滚动轴)。
  3. 实现高亮滑块是圆点、还是线(结合ant.designIview UI就任意实现一个了)。

基础组件编写

大家应该都用过el-tabs组件用法如下,为啥要说el-tabs组件呢主要是让大家了解到他是怎么判断选中那个tab组件得并生成tabs导航的,跟Anchor 有那些区别呢?

<el-tabs v-model="activeName" @tab-click="handleClick">
  <el-tab-pane label="用户管理" name="first">用户管理</el-tab-pane>
  <el-tab-pane label="配置管理" name="second">配置管理</el-tab-pane>
</el-tabs>

el-tabs核心代码如下:

export default {
  name: 'Tabs',
 
  computed: {
    tabPanes() {
      if(this.$slots.default) {
        // 使用 this.$slots.default  or this.$children 都能获取到子组件数据信息
        return this.$slots.default
        .filter(vnode => vnode.tag &&
            vnode.componentOptions &&
            vnode.componentOptions.Ctor.options.name === 'ElTabPane');
      }
      return []
    },
  },
}

el-tabs主要是使用this.$slotsapi获取到所有的子组件el-tab-pane信息, Anchor组件由于可以一直嵌套所以这种方案是使用不了了,如下:

<Anchor :affix="false" show-ink>
  <AnchorLink href="#API" title="API"> // 这里
    <AnchorLink href="#Anchor_props" title="Anchor props" /> // 嵌套子组件
    <AnchorLink href="#Anchor_events" title="Anchor events" />
    <AnchorLink href="#AnchorLink_props" title="AnchorLink props" />
  </AnchorLink>
</Anchor>

那么有几种可以让父组件递归着收集子组件信息的方案:

  • 方案一:利用provide传下去一个注册子组件方法
  • 方案二:父组件Anchor准备一个registerAnchorLink方法,子组件也具备registerAnchorLink方法去一层一层使用this.$parent.registerAnchorLink({...info})去调用。
  • 方案三:使用发布订阅模式,利用$on、$emit调用registerAnchorLink方法去注册

几种方案没有太大区别,都是用来递归组件通讯的一些手段。

基础组件如下:

Anchor组件
<template>
  <div class="el-anchor">
    <slot />
  </div>
</template>

<script>
export default {
  name: 'ElAnchor',

  data() {
    return {
      links: [],
      scrollContainer: null,
    }
  },

  props: {
    container: {
      type: [String, HTMLElement],
      default: () =>  (document.documentElement || document.body),
      required: false,
    },

    showInk: {
      type: Boolean,
      default: false
    },
  },

  mounted() {
     this.$nextTick(() => {
      this.scrollContainer = this.container?.addEventListener ? this.container : document.querySelector(this.container);
      this.scrollContainer?.addEventListener('scroll', this.scrollHandLer);
     })
  },

  beforeDestroy() {
    this.scrollContainer?.removeEventListener('scroll', this.scrollHandLer);
  },
  
  methdos: {
    registerAnchorLink(link) {
      if (!this.links.includes(link)) {
        this.links.push(link); // 注册组件
      }
    },

    unregisterAnchorLink(link) {
      this.links = this.links.filter(item => item !== link); // 删除注册的组件
    },
    
    handleScroll() { // 处理scroll事件

    },
  }
}
</script>

AnchorLink组件

<template>
  <div class="el-anchor-link">
    <a
      :href="href"
      :target="target"
      :title="title"
      class="el-anchor-link__title"
      @click="onClick"
    >
        {{ title }}
    </a>
  </div>
</template>

<script>

const scrollIntoView = (id) => document.getElementById(id)?.scrollIntoView();

export default {
  name: 'ElAnchorLink',

  props: {
    title: {
      type:String,
      default: '',
      required: true,
    },

    href: {
      type:String,
      default: '',
      required: true,
    },

    target:{
      type: String,
      default:'',
      required: false,
    },
  },

  mounted() {
    this.registerAnchorLink(this.href);
  },

  beforeDestroy() {
    this.unregisterAnchorLink(this.href);
  },

  methods: {
    registerAnchorLink(link) {
      this.$parent?.registerAnchorLink(link);
    },

    unregisterAnchorLink(link) {
      this.$parent?.unregisterAnchorLink(link);
    },

    onClick(event) {
      scrollIntoView(this.href); // 这个时候点击已经能跳到对应的界面了
      this.$emit('click', event, { title: this.title, href: this.href });
    },
  }
}
</script>

这个时候子组件再点击事件中已经调用scrollIntoView方法经行界面快速定位应用了。

处理下绑定的handleScroll方法

这个时候咱们父组件<el-anchor />可是已经收集到了部分子组件信息的,咱们可以根据滚动的距离判断到是选中高亮那个了。

算法如下:

const links = ['#a1', '#a2', '#a3'];

function getHighlightAnchorLink() {
  const linkRange = [];
   links.forEach((item) => {
    const target = document.querySelector(item);
    // 使用getBoundingClientRect 获取容器的top值  和当前link的top值 然后判断范围再20以内的 找个最大值
     const containerRect = this.scrollContainer?.getBoundingClientRect();
     const targetRect = target?.getBoundingClientRect();
     const top = targetRect?.top - containerRect?.top;
     if(top < 20) {
       linkRange.push({top, link: item})
     }
   });

  return maxBy(linkRange, 'top')
}

结语

以上就是实现一个锚点组件需要注意的点,以及相关算法。如果对你由帮组欢迎点赞收藏。

转载自:https://juejin.cn/post/7267901913245188096
评论
请登录