前言
回到顶部,相信大家都不陌生吧,这个功能可谓是随处可见,也是作为一名前端开发工程师手到擒来的一个功能点。但前几天刚刚好有一个类似于回到顶部(锚点)的功能点把我卡住了,就是点击锚点按钮页面滚到对应位置。如果说直接显示到对应位置那可真是太简单了,但过程需要点过渡的动画就难到我了,硬生生做了两个小时(说到底还是自己技术不过关)。
需求
点击锚点按钮页面带过渡动画显示到对应位置
HTML
因后期需实现页面滚动到对应位置时锚点高亮,故使用 View Design 框架的步骤条实现锚点按钮样式,为后期功能实现打好基础。
<Steps class="Steps" direction="vertical">
<Step status="wait" class="stepItem" :key="index" @click.native="changAnchor(item)" v-for="(item, index) in teacherData.groupList" :content="item.groupName"></Step>
</Steps>
失败思路
首次实现时的思路是,如果目前的滚动条高度大于锚点盒子顶部距离页面顶部的高度,便以每10毫秒的速度往上滚(减20),同理若小于则往下滚(加20)。等两则高度相等时则结束此方法(清除计时器),注意
: 一定要清除计时器。(此思路存在问题,好奇的同学可以先思考一下错在哪再往下看)。
失败方法
/**
* changAnchor 点击切换锚点
* (this.$refs['scroll'] as any).wrap.scrollTop为本项目自己编写的一个页面滚动条,故此代码不能直接copy复用。
* 此处代码有些庸余,本应该在计时器前声明一下(this.$refs['scroll'] as any).wrap.scrollTop,但声明之后报了一下莫名其妙的错误,就没有继续往下深究。
*/
changAnchor(item: any){
if(this.clickShow === true){
this.clickShow = false // 用于判断上一次滚动是否执行完毕,如若不执行完毕不执行下一次滚动,否则多次点击动画会出现错乱
const targetbox: any = document.getElementById(item.firstStepId); // 获取对应锚点盒子
const target = targetbox.offsetTop - 110;
this.timeTop = setInterval(() => {
if((this.$refs['scroll'] as any).wrap.scrollTop > target){
(this.$refs['scroll'] as any).wrap.scrollTop -= 20
}
if((this.$refs['scroll'] as any).wrap.scrollTop < target) {
(this.$refs['scroll'] as any).wrap.scrollTop += 20
}
if((this.$refs['scroll'] as any).wrap.scrollTop === target){
clearInterval(this.timeTop);
this.clickShow = true
}
}, 1);
}
}
失败思路解析
细心的同学可能就会发现这个方法的漏洞,相加相减实现它的动画效果没毛病,但以每次20为间距来相加相减,若此时相加后或者相减后的最后一次距离小于20,那再执行一次相加相减岂不是刚好跳过了它们相等时的值?此时函数会继续往下执行,页面便会一直向上滚或者往下滚,并且因为没有清除计时器边永久执行此方法。
可行思路
继续延用上诉的思路,但在此基础上进行优化。当相加或相减的距离小于20时,将滚动条高度直接赋值成锚点盒子的高度。
可行方法
/**
* changAnchor 点击切换锚点
*/
changAnchor(item: any){
if(this.clickShow === true){
this.clickShow = false // 用于判断上一次滚动是否执行完毕,如若不执行完毕不执行下一次滚动,否则多次点击动画会出现错乱
const targetbox: any = document.getElementById(item.firstStepId); // 获取对应锚点盒子
const target = targetbox.offsetTop - 110;
this.timeTop = setInterval(() => {
if((this.$refs['scroll'] as any).wrap.scrollTop > target + 20){
(this.$refs['scroll'] as any).wrap.scrollTop -= 20
} else if((this.$refs['scroll'] as any).wrap.scrollTop <= target +20 && (this.$refs['scroll'] as any).wrap.scrollTop >= target){
(this.$refs['scroll'] as any).wrap.scrollTop = target
}
if((this.$refs['scroll'] as any).wrap.scrollTop < target - 20) {
(this.$refs['scroll'] as any).wrap.scrollTop += 20
} else if((this.$refs['scroll'] as any).wrap.scrollTop >= target - 20 && (this.$refs['scroll'] as any).wrap.scrollTop <= target){
(this.$refs['scroll'] as any).wrap.scrollTop = target
}
if((this.$refs['scroll'] as any).wrap.scrollTop === target){
clearInterval(this.timeTop);
this.clickShow = true
}
}, 1);
}
}
效果图:
总结
本题的思路其实跟循环差不多,只是单纯的把循环事件变成计时器来使用,所以其他同学若是感兴趣也可以试着使用循环的方式来实现。