关于锚点功能的相关设计与实现
Vue锚点组件实现一些设计
锚点组件 通常用于页面内部的导航和跳转功能,它能够帮助解决以下几个问题:
-
页面内部导航: 当页面内容较长或者包含多个章节时,用户可能需要快速定位到感兴趣的内容区域。使用锚点组件可以创建类似目录的导航结构,用户点击导航链接后,页面会平滑滚动到相应的内容区域
-
页面间跳转:在单页应用(Single Page Application)中,页面往往通过不同的路由进行切换。锚点组件可以与路由配合使用,实现页面内部的平滑滚动切换效果,提供更好的用户体验。
-
快速定位应用:在长文档或博客文章中,可能存在需要引用其他部分内容的情况。使用锚点组件可以为特定内容区域生成唯一的锚点链接,方便其他页面引用该内容,并直接跳转到相关位置。
-
SEO 优化:搜索引擎优化(SEO)是网站开发中重要的一环。使用锚点组件可以为每个页面部分生成独立的 URL 锚点,使搜索引擎能够更好地索引和理解页面的结构,提升页面的可搜索性和排名。
其实可以看到Elemnet UI
中其实就没有锚点组件,使用起来再长文档中想找哪项得示例确实体验不是很好。 像ant.design、Iview 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();
锚点组件设计
锚点组件一些设计与实现功能,咱们就实现核心功能主要是了解
锚点组件
应该怎么实现
- 点击跳转(使用a标签默认事件即可)。
- 根据界面滚动得高度选中高亮锚点(监听滚动轴)。
- 实现高亮滑块是圆点、还是线(结合
ant.design
、Iview 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.$slots
api获取到所有的子组件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