需求:
vue项目PC端详情页内容过多,在右侧或左侧加一个导航栏,通过点击某一项,页面平滑滚动到具体的内容上。并把这个功能封装成组件。
思路:
- 封装成组件复用的话,首先快捷键的每一项数据需要父组件传入;在父组件定义一个数组作为右侧导航栏的数据;由于左侧区域的某一块内容没有数据时,其对应的导航项不显示;
- 要给每一块的内容最外层绑定ref,是为了获取该dom元素,通过点击导航栏某一项时,让页面可以滚动到对应的元素内容;
- 导航在抽屉里做的,给最外层绑定ref=“drawer”,可以获取到当前页面最外层的dom元素,根据最外层容器的dom元素来获取滚动高度;
- 组件要接收导航栏数据、接收当前是否是抽屉(默认为抽屉,传false为普通正常页面)、接收当前页面所有的dom元素、接收样式(显示在页面的左侧还是右侧)
- 统一封装滚动方法:将滚动方法放在定时器中,在获取到最新DOM元素时,添加该定时器;在组件销毁时,清除定时器;定时器中主要功能:当滚动上去的高度+可见区域的高度==全文高度,导航栏选中最后一项;当第一项的距离可视窗口顶部距离>0,导航栏选中第一项。
- 点击方法:点击导航栏某一项,内容区域滚动到可视区域的顶部,导航栏选中该项。
代码:
具体页面
组件:
<template><div><div class&#61;"leftKey" :style&#61;"{ left: left || &#39;auto&#39;, right: right || &#39;auto&#39;, opacity: opacity }"><span v-for&#61;"item in titleKey" :key&#61;"item.id"><a v-if&#61;"item.flag" &#64;click&#61;"goAnchor(item.id)" :class&#61;"{checkKey: item.checked}">{{item.name}}</a></span></div></div>
</template><script>
export default {data () {return {timer: null,scrollTop: 0,anchorFlag: false,ids: &#39;&#39;}},created () {this.titleKey.forEach(item &#61;> {item.checked &#61; false})this.titleKey[0].checked &#61; true},mounted () {this.fun &#61; () &#61;> {clearTimeout(this.timer)this.timer &#61; setTimeout(() &#61;> {var scrollTop &#61; this.drawerFLag ? this.parentRef.drawer.$refs.drawer.scrollTop : (document.documentElement.scrollTop || document.body.scrollTop)var clientHeight &#61; document.documentElement.clientHeight || document.body.clientHeightvar scrollHeight &#61; this.drawerFLag ? this.parentRef.drawer.$refs.drawer.scrollHeight : (document.documentElement.scrollHeight || document.body.scrollHeight)this.scrollTop &#61; scrollTopif (scrollTop &#43; clientHeight &#61;&#61;&#61; scrollHeight) {let lastFlag &#61; truefor (let i &#61; this.titleKey.length - 1; i >&#61; 0; i--) {if (this.titleKey[i].flag && lastFlag) {this.titleKey[i].checked &#61; truelastFlag &#61; false} else {this.titleKey[i].checked &#61; false}}return}if (this.parentRef[this.titleKey[0].id].getBoundingClientRect().top > 0) {this.titleKey.forEach(item &#61;> {item.checked &#61; false})this.titleKey[0].checked &#61; true}}, 50)}this.$once(&#39;hook:beforeDestroy&#39;, () &#61;> {window.removeEventListener(&#39;scroll&#39;, this.fun, true)})this.$nextTick(() &#61;> {window.addEventListener(&#39;scroll&#39;,this.fun,true)})},methods: {goAnchor (id) {if (this.parentRef[id]) {this.parentRef[id].scrollIntoView({behavior: &#39;smooth&#39;,block: &#39;start&#39;})this.titleKey.forEach(item &#61;> {if (item.id &#61;&#61;&#61; id) {item.checked &#61; true} else {item.checked &#61; false}})}},},props: {titleKey: {type: Array,default: () &#61;> []},drawerFLag: {type: Boolean,default: true},parentRef: {type: Object,default: () &#61;> {}},left: {type: String,default: &#39;&#39;},right: {type: String,default: &#39;&#39;},opacity: {type: Number,default: 0}}
}
</script><style lang&#61;"scss" scoped>
&#64;import "~&#64;/styles/variables.scss";
&#64;import "~&#64;/styles/index.scss";.clearfix:after{content: "";display: block;height: 0;clear:both;visibility: hidden;}.clearfix{*zoom: 1;}.checkKey {color: #E8380D;border-bottom: 1px solid #E8380D;background-color: #E7370D;color: #fff;}.leftKey {position: fixed;width: 127px;background: #fff;box-shadow:0px 3px 8px 1px rgba(12,12,12,0.06);font-size: 14px;color: #333;background-color: rgba(255,252,252,1);top: 115px;z-index: 999;a {display: block;padding: 16px 0px 16px 21px;border-top: 1px solid rgba(255,228,222,1);border-left: 1px solid rgba(255,228,222,1);border-right: 1px solid rgba(255,228,222,1);}a:last-child {border-bottom: 1px solid rgba(255,228,222,1);}img {padding-right: 2px;}}</style>
main.js
import Vue from &#39;vue&#39;
import ElementUI from &#39;element-ui&#39;
import shortcutKey from &#39;&#64;/components/ ShortcutKey&#39;Vue.component(&#39;ymShortcutKey&#39;, shortcutKey)Vue.config.productionTip &#61; false
Vue.use(ElementUI)new Vue({el: &#39;#app&#39;,router,store,render: h &#61;> h(App)
})
参考文章&#xff1a;Vue中实现锚点
涉及知识&#xff1a;
-
vue使用$once
清除定时器
mounted () {const that &#61; this// 设置定时器const testSetinterval &#61; setInterval(() &#61;> {setTimeout(() &#61;> {console.log(&#39;test clearInterval&#39;)}, 0)}, 2000)// 通过$once来监听生命周期beforeDestroy钩子&#xff0c;在组件销毁前清除定时器。this.$once(&#39;hook:beforeDestroy&#39;, () &#61;> {clearInterval(testSetinterval)})},
参考&#xff1a;
vue使用$once清除定时器的问题
vue中如何清除定时器
-
scrollTop
网页被卷去的高
clientHeight
网页可见区域高
scrollHeight
网页正文全文高
参考&#xff1a;
搞清clientHeight、offsetHeight、scrollHeight、offsetTop、scrollTop
DOM元素位置&#xff0c;滚动位置和鼠标事件位置相关属性函数总结
获取页面滚动高度
document.documentElement.scrollTop || document.body.scrollTop;
-
this.$nextTick()
this.$nextTick()这个方法作用是当数据被修改后使用这个方法会回调获取更新后的dom再render出来(调用render()函数&#xff0c;重新编译。将vue模版里的逻辑如v-if、v-for等这些内容是浏览器不能识别的&#xff0c;必须通过js去转换为html&#xff0c;才能够显示页面。)
参考&#xff1a;
对vue实现数据实时更新&#xff0c;render() 函数的一些理解
个人理解this.$nextTick()的使用场景
this.$nextTick
-
ref
与 $refs
使用ref绑定DOM元素&#xff0c;通过this.$refs获取绑定的该元素。这样可以减少获取dom节点的消耗
<input type&#61;"text" ref&#61;"input1"/>
this.$refs.input1.value &#61;"22";
参考&#xff1a;$refs基本用法
Vue基础4&#xff1a; ref 和 $refs
-
setInterval()
与setTimeout()
setInterval的特点&#xff1a;一直循环调用函数&#xff0c;不会自己停止&#xff1b;需要用window.clearInterval(setInt);这个函数去停止循环
setTimeout的特点&#xff1a;只调用一次
setTimeout(“showTime()”,5000); //延迟5秒刷新页面
业务场景&#xff1a;
setTimeout用于延迟执行某方法或功能
setInterval则一般用于刷新表单&#xff0c;对于一些表单的假实时指定时间刷新同步
参考&#xff1a;
setInterval()与setTimeout()计时器
setTimeout和setInterval的区别
setInterval和setTimeout的区别