宝哥软件园

实现Vue天花板锚固组件的方法

编辑:宝哥软件园 来源:互联网 时间:2021-08-21

前言

最近小哥对我提出了一个新的要求,在一个页面的滚动区增加一组锚点定位按钮,点击按钮会在页面的可视区显示相应的元素。当按钮组超过页面的可见区域时,它被固定在滚动区域的头部。当滚动区域滚动时,与最靠近滚动区域顶部的元素匹配的定位按钮会突出显示。

分离功能点

既然我们已经定义了需求,让我们总结一下这个需求的功能点:

按钮组应该有顶吸效果。点击按钮,具有定位功能。要滚动内容区域,您需要找到相应的按钮并突出显示顶部抽吸组件

制作吸顶效果最简单的方法就是将css的position属性设置为粘性,从而实现粘性布局。粘性容器{ position:粘性;top: 0px}上面的例子只用了两行css代码就实现了粘性布局,但是由于IE浏览器根本不支持粘性布局,而且我的项目需要支持部分IE浏览器,所以需要手动实现这样的功能。

MDN对粘性布局的官方解释如下:粘性布局元素默认是相对定位的。当粘性元素超过父元素的指定值时(如“顶部”、“左侧”等)。),例如,在上面的示例中,元素的粘性元素被更改为固定定位。父元素MDN的描述不太准确。这里,父元素指的是父滚动元素。如果没有父滚动元素,body元素将作为父元素。

由于你需要自己实现天花板效果,考虑到其他页面也可能使用的天花板功能,你决定将其分离为一个通用组件。首先,我们知道粘性布局是定位父滚动元素,所以我们需要先找到父滚动元素。这个功能可以通过两种方式实现,一种是查找,另一种是通过道具传递一个唯一标识的css选择器。

我觉得其他项目可能也会遇到这个功能,所以我定义的组件尽量接近开源,所以我同时支持两种方案。首先,我们需要实现一个查找父滚动元素的功能。如何判断一个元素是否是滚动元素?很容易判断其溢出是自动的还是滚动的。

//util.js文件//判断一个元素是否是滚动元素const scrolllist=['auto ',' scroll']导出函数hascrollelelement(El,direction=' vertical') {if(!El)return const style=window . getcomputed style(El)if(direction==' vertical '){ return scroll list . includes(style . overflow y)} Elseif(direction==' horizontal '){ return scroll list。包括(样式。溢出x)} }//获取第一个滚动元素,导出函数getfirst scrolllelement(El,direction=' vertical') {if(!El)return if(hasscrollelement(El,direction)){ return El } else { return getfirst scrollement(El El)。胃肠外营养,方向)}}以下是实现天花板效果所需的一些基本知识:

固定定位是相对于浏览器的可视区域,也就是说即使页面滚动,也还是会固定在同一个位置。offsetTop是一个只读属性,它返回当前元素与其最近的父定位元素顶部之间的距离。ScrollTop属性可以获取或设置元素内容垂直滚动的像素值,` scrollTop '表示从该元素到父滚动元素顶部的距离。template div class=' CPT-sticky ' : class=' fixed class ' : style=' { top : top ' px ',zIndex } ' slot/slot/div/templatescript export默认{ props: { top: Number,parent: String,zIndex: Number },data() { return { fixedClass: ' ',scrollElement: null } },mounded(){ this . initscrollement().$ El . offsettop-this . top if(this . scrollelement . scroll top=scrollOffsetTop){ this . fixed class=' top-fixed ' } else { this . fixed class=' ' },initScrollElement(){ const element=document . queryselector(this . parent)if(element){ this . removeScrollEvent()this . scrollelelement=element this . scrollelelement . addevent listener(' scroll ',this.handleScroll) } },removeScrollEvent() { if (this)。cpt-sticky {。顶部固定{位置:固定;宽度: 100%;背景# fff}}/style就像上面的示例代码一样,一个吊顶组件只用几十行就实现了,但是实现了吊顶功能,但是还是有一些缺陷。

当页面缓慢滚动时,天花板组件在固定或不固定时会卡住。我的一些要求需要做锚定位,但是直接锚定位会改变路线,所以我改成了滚动定位(后面会详细介绍)。但是,由于安装在天花板上的组件在固定后将从文档流中分离出来,所以一部分定位的元素(安装在天花板上的组件的高度)将被卡在安装在天花板上的组件下面。就像下图的效果一样,右侧锚点定位2区域的标题是隐藏的。

这些问题可以很容易地解决,使用一个与天花板安装组件大小相同的占位符元素。吊顶组件离开文档流后,占位符元素被插入到吊顶组件的原始DOM位置,然后顺便做一些小的优化。由于占位符元素需要与组件高度一致,因此必须确保“插槽”插槽中的DOM元素已经加载。此外,槽元素中的位置可能会改变,因此我会在天花板状态改变之前获得它的高度。

template div class=' CPT-sticky ' div class=' sticky-container ' : class=' fixed class ' : style=' { top : top ' px,zIndex } ' slot/slot/div div v-if=' show placeholder ' class=' sticky-placeholder ' : style=' { height : offset theight ' px ' } '/div/templatescript import { getfirst scrollement } from ' util . js ' export defaultthis . offset theight){ this . offset theight=this。$ El . offset theight } this . show placeholder=!v } },销毁(){ this.removeScrollEvent() },methods : { handleScroll(e){ const scrollOffsetTop=this。$ El . offsettop-this . top if(this . scrollelement . scroll top=scrollOffsetTop){ this . fixed class=' top-fixed ' } else { this . fixed class=' ' },initScrollElement() { if(!this.isMounted)返回const parent=this . parent let element=null if(parent){ element=document . queryselector(parent)if(element===this . scrollelelement)返回} else if (this。$ El){ element=getfirst scrollelement(this。$el) } if(元素){ this . removeScrollEvent(). this . scrollelelement=element this . scrollelement . addeventlistener(' scroll ',this.handleScroll) },removeScrollEvent(){ if(this . scrollelelement){ this . scrollelelelement . removeeventlistener(' scroll ',this . handlescroll)} } }/script style lang=' SCS '。cpt-sticky {。顶部固定{位置:固定;宽度: 100%;背景# fff}}/style锚点定位

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

用标签定位,用js定位,用标签定位

先说A标签定位,这是最常用的定位方法之一。它可以通过两种方式实现,一种是由herf属性链接的指定元素的id。另一种方法是添加一个A标记,然后将href属性链接到A标记的名称属性。

A href='视图1 '按钮1/a href='视图2 '按钮1/a.div id=' view 1 ' view 1 2/a/div diva name=' view 2 ' view 2/a/div。这种定位方法简单,支持任意标签定位。但是也有一些问题,比如滚动区域有固定或者绝对定位,就会出现蒙版的问题,瞬间滚动到顶部交互不太好。当然,这些都可以通过css来解决。但主要问题是A标签位置会改变路由的哈希,如果有对应的路由,就会跳转路由。

通过js模拟锚定位

定位效果可以通过使用js来操作元素的属性(如scrollTop)并使其滚动到父滚动元素指定的位置来实现。这里简单提到了方法“scrollIntoView()”。根据MDN的描述,` Element.scrollIntoView()'方法使当前元素滚动到浏览器窗口的可见区域。` scrollIntoView()`还支持动画选项,可以通过' behavior '进行设置。不幸的是,当它遇到固定定位时,它也会掩盖,所以它最终选择手动删除代码。但是,` scrollIntoView()'非常适合返回顶部的功能。

首先我们需要建立按钮和滚动区内容元素的对应关系,把对应内容区元素的css选择器放在按钮值中,根据点击按钮的值找到对应的元素。因此,计算规则是这个元素从滚动区的高度加上这个元素上边缘的高度(我在内容区加了外边缘,希望显示出来),从可视区减去滚动区的高度(我的页面没有定位,所以offsetTop对应可视区),再减去按钮组件的高度就得到要滚动的位置。

template div class=' CPT-anchor ' El-radio-group v-model=' selector ' size=' mini ' @ change=' handlemenuchchange ' El-radio-button v-for=' menu ' in menu ' : key=' menu。value' : label='菜单。值“{菜单。label } }/El-radio-button/El-radio-group/div/template script//添加慢速函数import {tween}从' shifty'///它与lodash.get类似,但处理null类型的导入{get as _ get}从' noshjs ' import { getfirst scrollement }从' util . js ' export default { prop s : } {//滚动区距可视区顶部的高度为top: { type: number,default 3: },menus:data(){ return { selector : ' ' } },watch : { menus : { immediate : true,handler(list){ this . selector=_ get(list,[0,' value'],' ')} } },methods : { handlemenuchchange(selector){ const scrollement=document . queryselector(select)const rootsscrollement=getfirst scrollement(scrollement)if(scrollement rootsscrollement){ const$el,['parentElement ',' offsetHeight'],0)const top=offset top-this . top-offset Height//做一个缓慢处理的推文({从: { x : rootlollement . scroll top },到: {x: top},Duration : 500,ease : ' easeoutquint ',step :({ x })={ rootlollement。scrolltop=x}})。然后(({x})={rootrollelement。scrolltop=x})}}/。

接下来,让我们看看最后一个函数。当用户滚动内容区域时,与最接近按钮组件的元素相对应的按钮被高亮显示。我可以把这个功能想象成目录导航,在我们查看不同内容的时候突出显示对应的目录。

如何实现这个功能,我们来分析一下,当查看不同的内容时,屏幕会滚动,所以我们要把‘滚动’事件绑定到按钮的父滚动元素上。为了判断当前滚动区域中最接近按钮的元素,我们需要在这个元素上添加一个与按钮中的值相对应的css选择器。当内容区域滚动时,根据按钮获取内容区域中的所有元素,然后从滚动区域元素的scroll top’中减去按钮元素的高度,即获取按钮下方的滚动高度,然后遍历这些元素的头部和尾部是否包含该滚动高度,然后找到该元素对应的按钮。

以上结论可以完成,但也存在一些问题。首先,第一个问题导致按钮导航失败,导航只在下一个按钮边缘结束。这个问题可能不是每个人都能遇到的。之所以会遇到这个问题,是因为我用了Radio的‘Element’组件,想突出v型的时候就改变了v型的值。点击按钮将触发滚动,这将与链接高亮事件冲突。因此,使用“is scroll”变量来标记锚点当前是否处于定位状态,并且定位状态不触发滚动操作。

模板div class=' CPT-anchor ' El-radio-group v-model=' selector ' size=' mini ' @ change=' handlemenuchchange ' El-radio-button v-for='菜单中的菜单:键='菜单。value ' : label='菜单。值“{ 0 }”菜单。标签} }/El-单选按钮/El-单选按钮组/div/模板脚本导入{ tween }来自“shifty”导入{ get as _ get }来自“noshjs”导入{ getfirst scrolle rootsscrollelements : ' ' },mounted(){ this。这是真的吗?getscrollelelement()},watch : { parent : { immediate : true,handler : ' getscrollelelelement ' },menus: { immediate: true,handler(list){ this。menu=_ get(list,[0,' prop'],' ')},滚动顶部(v){ if(this。anchorchange){ .切换按钮会滚动视图,$nextTick之后按钮值改变了,但滚动可能还没有结束,所以需要打个标记。this.isScroll=true } },方法: { handlemenuchchange(select){ this。isscroll=false this。anchorchange=false//滚动高度等于元素距离可视区头部高度减去元素自身高度与元素上边框高度以及滚动区距离可视区头部的高度const scrolle元素=文档。query selector(select)if(scrolle)元素。rootsscrolle元素){ const offset top=scrolle元素。偏移顶部滚动条元素。clienttop常量偏移量heat=_ get(this .$el,['parentElement ',' offsetHeight'],0)const top=offset Top-this。顶部偏移高度/做一个缓动处理tween({ from : { x : this。根收集。滚动顶部},到: { x:顶部},持续时间: 500,easing: ' easeOutQuint ',步骤:({ x })={ this。根收集。滚动顶部=x } }).然后(({ x })={ this。rootsscrollelement。滚动顶部=x })这个$ nextTick(()={ this。anchorchange=true })},getscrollelelement(){ if(!this.isMounted) return //如果没有传入父母默认取第一个父级滚动元素const parent=this.parent let元素=如果(父){ element=document,则为null。queryselector(父)//mount之后rootScrollElement可能已经存在了,如果和上次一样就不做任何操作if(element===this。rootsscrollelement)返回}否则如果(这.$ El){ element=get first scroll element(this .$ El。parent element)} if(element){ this。removeScrollEvent()。这个。rootsscrollelelement=元素this。rootsscrollelelement。addevent listener(' scroll ',this.handleScroll) } },removeScrollEvent(){ if(this。rootsscrollelelelement。removeevent侦听器(' scroll ',this.handleScroll) },handleScroll(event){ const scroll top=this。rootscrollelelelelelelelelelelelelelelelelelelelelelelelelelelelelelelelelelelelelelelelelelelelelelelelelelelelelelelelelelescroltop。scroltop=scroltop if(!this.isScroll)返回常量{ data,top }=这个常量偏移量heat=_ get(这个.$el,['parentElement ',' offset seart '],0)const scrollist=[]data。foreach(item={ const element=document。queryselector(项目。prop)if(元素){ const top=元素。offset top const rect={ top : top元素。客户端顶部-顶部-底部艺术,底部:顶部元素。画外音。push(rect)})//遍历按钮元素的顶端和底部,查看当前滚动在那个元素的区间内。

scrollList.some((it,index)={ if(index scroll top=it。顶上它。bottom){ const menu=_ get(data、[index、' prop']、' ')if(menu)this。menu=menu return true } else {//当小于最小高度时,就等于最小高度如果(滚动顶部=0,滚动顶部。bottom){ const menu=_ get(data )、[ index、“prop”、“”if(menu)this。menu=menu return true } } })} } }/script style lang=' SCS ' .CPT-锚{衬垫-顶部: 4px。CPT-选项卡-菜单{ margin : 0;埃尔-单选按钮{左边距: 10px。埃尔-单选按钮_ _内部{ border:非边框-半径: 5px 5px 0 0边框-底部: 2px实心# e4e 7 ed背景-颜色: # f6f 6 F8;font-size : 16px:悬停{边框-底部: 2px实心# 409 eff} } .is-active { .埃尔-单选按钮_ _内部{ color: # fffborder:无;边框半径: 5px 5px 0 0背景色: # 409 eff边框-底部: 2px实心# 409擦黑板-阴影:无;} } } }}/style吸顶锚点组件

最后,将以上两个组件结合在一起才是我们需要的。

template div class=' CPT-Sticky-anchor ' Sticky : top=' top ' : z-index=' zIndex ' Sticky-menu : top=' top ' : data=' menu ' : parent=' parent '/Sticky-menu/Sticky//滚动区域内容存储位置slot/slot/div/templatescript从粘性中导入'。/sticky.vue“从导入粘性菜单”。/menu . vue ' export default {//在这里是缩写,因为上面已经存在。props: {顶部,菜单,父级,zindex,offsetheight },组件: {粘性,粘性菜单}}的用法示例/脚本

模板div class='page-demo '.其他内容粘性锚定菜单='菜单'父级='。page-demo ' ul Li class=' button-1 ' view 1/Li Li class=' button-2 ' view 2/Li/ul/sticky-anchor/div/templatescription StickyAnchor from ' components/sticky-anchor . vue ' export default { data(){ return { menus :[{ label : ' button one ',value : }。button-1'},{label:' button two ',value: '。button-2'}]}},components: {粘性锚点}/。

整个功能已经在这里实现了。我们来总结一下。

天花板效应有两种解决方案。如果浏览支持粘性布局,css更方便。使用A标签作为锚点位置比较简单,但遇到位置布局时需要特殊处理,会改变路由哈希。链接锚点和滚动事件时要注意按钮点击事件和滚动事件的冲突。

以上就是本文的全部内容。希望对大家的学习有帮助,支持我们。

更多资讯
游戏推荐
更多+