www.129028.com金沙实现一个 Vue 吸顶锚点组件方法_vue.js_脚本之家

日期:2020-05-07编辑作者:Web前端

时间: 2019-09-07阅读: 207标签: 项目场景

前言

React页面中,不确定有多少个需要定位的块,根据元素块的个数,生成对应数量的锚点,点击锚点后页面滚动到指定的块。 页面滚动到指定的块,对应的锚点高亮。

近期产品小哥哥给我提了一个新需求,在一个页面的滚动区中添加一组锚点定位按钮,点击按钮将对应的元素显示在页面的可视区中。当按钮组超出页面可视区的时候将其固定在滚动区域的头部,当滚动区滚动时,高亮距离滚动区顶部最近的元素所匹配的锚点按钮。

github地址:-demo

拆分功能点

锚点

现在我们已经明确需求了,接下来我们总结一下这个需求有哪些功能点:

超链接的一种形式,快速定位到想要看的位置,常用在文章目录等位置。

按钮组要有吸顶效果 点击按钮要有锚点定位功能 滚动内容区需要找到对应的按钮并高亮

实现dom元素方面

吸顶组件

// domdiv onScrollCapture={() = this.onScrollEvent()} style={{ height: '200px', overflowY: 'scroll' }} ref={(c) = { this.scrollRef = c; }} div className="content" div  {contentOptions} /div /div div className="anchor-link-body"  div className="link-content-link" span / /div div  {LinkOptions} /div /div/div

要做一个吸顶效果最简单的方式是将 css 的 position 属性设置为 sticky, 这样就实现粘性布局。

构造对象,动态生成元素,对象从接口获取,数目不定,这是个demo

.sticky-container { position: sticky; top: 0px;}
/* ObjectList:[ {id: 1, name: '橘子'}, {id: 2, name: '苹果'}, {id: 3, name: '香蕉'}, {id: 4, name: '菠萝'},]*/// 根据数组生成对应的模块, 在render函数内面const contentOptions = [];const LinkOptions = [];ObjectList.forEach((item) = { LinkOptions.push(div id={`link-${item.id}`} className="link-content" onClick={this.scrollToAnchor.bind(this, item.id)}{item.name}/div) contentOptions .push( div className="content-child" span id={`${item.id}`}{item.name}/span div style={{ width: '100%', heigth: '500px' }} 我是内容,我是内容 /div /div )});

上面的示例仅仅用了两行 css 的代码就实现了粘性布局,但由于 IE 浏览器完全不支持粘性布局,而我的项目又需要支持一部分的 IE 浏览器,所以就需要手动去实现这样一个功能。

点击锚点,对于的块滑动到浏览器窗口的顶部方法

MDN 官方对粘性布局的解释是这样的,粘性布局元素默认是相对定位的,当粘性元素超出父元素的指定值,例如上面的示例,当元素粘性元素改为固定定位。关于父级元素 MDN 描述的不是很精确,这里的父级元素指的是父级滚动元素,如果没有父级滚动元素则将 `body` 元素作为父级元素。

// 这是滚动方法scrollToAnchor = (anchorName) = { if (anchorName || anchorName === 0) { // 找到锚点 const anchorElement = document.getElementById(anchorName); // 如果对应id的锚点存在,就跳转到锚点 if (anchorElement) { anchorElement.scrollIntoView({ block: 'start', behavior: 'smooth', }); } } };

既然需要自己实现一个吸顶的效果,思考到其他页面可能也会使用的吸顶的功能,所以决定将其单独抽离成一个通用组件。首先我们知道粘性布局是对父级滚动元素定位,所以我们要先找到父级滚动元素,这个功能我们可以通过两种方式实现,一种是向上查找,一种是通过 props 传递一个唯一标识的 css 选择器。

生命周期函数中获取所有元素的id集合

我觉得其他项目可能也会遇到这个功能,所以我定义组件 尽量向着开源靠拢,所以我这里同时支持两种方案。首先我们要实现一个查找父级滚动元素的功能,如何判断一个元素是滚动元素呢?很简单判断其 `overflow` 是否是 `auto` 或者 `scroll`。

 componentDidMount() { this.getBoxIds(); }/** * 1. 在React生命周期函数中执行函数 * 2. 获取每个块的正文内容初始距离浏览器边框的距离 offsetTop */ getBoxIds = () = { // 正文板块绑定的id数组 const linkIds = []; ObjectList.forEach((item, index) = { const top = document.getElementById(`${item.id}`); if (top) { linkIds.push({ key: item.id, offsetTop: top.getBoundingClientRect().top}); } }) this.setState({ linkIds }); };
// util.js 文件// 判断一个元素是否是滚动元素const scrollList = ['auto', 'scroll']export function hasScrollElement(el, direction = 'vertical') { if  return const style = window.getComputedStyle if (direction === 'vertical') { return scrollList.includes } else if (direction === 'horizontal') { return scrollList.includes }}// 获取第一个滚动元素export function getFirstScrollElement(el, direction = 'vertical') { if  return if (hasScrollElement { return el } else { return getFirstScrollElement(el && el.parentElement, direction) }}

监听页面滚动,操作dom,使导航的锚点高亮

这里说下实现吸顶效果所需要的一些基础知识:

/** * activeLink -- 高亮的类名,属性在css中自行设置 * linkIds -- 锚点对应div id集合的数组 * this.scrollRef.scrollTop 滚动条滚动的距离*/ onScrollEvent() { const { linkIds } = this.state; linkIds.forEach((item, index) = { if (this.scrollRef.scrollTop  item.offsetTop) { document.getElementById(`link-${item.key}`).classList.add('activeLink'); linkIds.forEach((k, v) = { if (item.key !== k.key) { document.getElementById(`link-${k.key}`).classList.remove('activeLink'); } }); } }); }

fixed 定位是相对于浏览器的可视区进行定位,这意味着即使页面滚动,它还是会固定在相同的位置 offsetTop 是一个只读的属性,它返回当前元素相对于距离它最近的父级定位元素顶部的距离。 scrollTop 属性可以获取或设置一个元素的内容垂直滚动的像素值,`scrollTop` 表示这个元素达到父级滚动元素顶部的距离。

   export default { props: { top: Number, parent: String, zIndex: Number }, data() { return { fixedClass: '', scrollElement: null } }, mounted() { this.initScrollElement { this.removeScrollEvent() }, methods: { handleScroll() { const scrollOffsetTop = this.$el.offsetTop - this.top if (this.scrollElement.scrollTop >= scrollOffsetTop) { this.fixedClass = 'top-fixed' } else { this.fixedClass = '' } }, initScrollElement() { const element = document.querySelector if  { this.removeScrollEvent() this.scrollElement = element this.scrollElement.addEventListener('scroll', this.handleScroll) } }, removeScrollEvent() { if  { this.scrollElement.removeEventListener('scroll', this.handleScroll) } } }}.cpt-sticky { .top-fixed { position: fixed; width: 100%; background: #fff; }}

就像上面的示例代码一样,短短几十行就实现了一个吸顶组件,不过它实现了吸顶的功能,但是还有一些缺陷。

在慢速滚动页面,吸顶组件在固定与非固定的时候有明显的卡顿现象。 由于我的需求有一些是需要做锚点定位功能,但是直接用锚点定位会改变路由所以改为了滚动定位。但是由于吸顶组件在 `fixed` 之后会脱离文档流,导致定位的元素会有一部分被卡在吸顶组件下方。就像下面这张图的效果,右边的锚点定位2区域的标题被隐藏了。

这些问题也很好解决,使用一个和吸顶组件相同大小的占位元素,当吸顶组件脱离文档流之后,占位元素插入吸顶组件原来的 DOM 位置中,然后顺便带上一些小优化。由于占位元素需要和组件高度一致,所以必须要保证 `slot` 插槽中的 DOM 元素已经被加载完成,另外放在 slot 元素中可能发生变更,所以我在吸顶状态变更之前获取其高度。

      import { getFirstScrollElement } from 'util.js'export default { props: { top: { type: Number, default: 0 }, zIndex: { type: Number, default: 0 }, parent: { type: String, default: '' } }, data() { return { isMounted: false, fixedClass: '', offsetHeight: 0, scrollElement: null, showPlaceholder: false } }, mounted() { this.isMounted = true this.initScrollElement() }, watch: { parent: { immediate: true, handler: 'getScrollElement' }, fixedClass { if (v && !this.offsetHeight) { this.offsetHeight = this.$el.offsetHeight } this.showPlaceholder = !!v } }, destroyed() { this.removeScrollEvent() }, methods: { handleScroll { const scrollOffsetTop = this.$el.offsetTop - this.top if (this.scrollElement.scrollTop >= scrollOffsetTop) { this.fixedClass = 'top-fixed' } else { this.fixedClass = '' } }, initScrollElement() { if  return const parent = this.parent let element = null if  { element = document.querySelector if (element === this.scrollElement) return } else if  { element = getFirstScrollElement } if  { this.removeScrollEvent() this.scrollElement = element this.scrollElement.addEventListener('scroll', this.handleScroll) } }, removeScrollEvent() { if  { this.scrollElement.removeEventListener('scroll', this.handleScroll) } } }}.cpt-sticky { .top-fixed { position: fixed; width: 100%; background: #fff; }}

锚点定位

网页中经常会有用到锚点定位的场景,例如百度知道的目录,我目前知道有三种方式可以实现这种功能。

本文由www.129028.com金沙发布于Web前端,转载请注明出处:www.129028.com金沙实现一个 Vue 吸顶锚点组件方法_vue.js_脚本之家

关键词:

vue单元测试学习笔记www.129028.com金沙

时间: 2019-09-08阅读: 146标签: 测试 单元测试是什么 维基百科:单元测试是针对 程序的最小单元 来进行正确性检验的测...

详细>>

Omi框架Store体系的前世今生

时间: 2019-09-09阅读: 925标签: store 原文链接-https://github.com/AlloyTeam/omi 在src下新建 store/myStore.js Store 体系 先说说Store系统...

详细>>

vue插槽slot和slot-scope

具名插槽 父组件在向子组件的插槽提供内容的时候,有时需要访问子组件中的数据,那么可以利用v-bind指令在插槽上...

详细>>

SDK 与API之间的关系和联系

时间: 2019-09-09阅读: 201标签: api SDK(software development kit),中文可译为“软件开发工具包”。 SDK(SoftwareDevelopmentKit,软...

详细>>