vue / js scrollIntoView的使用和替代方法(无jquery的滚动动画效果)

vue

scrollIntoView: https://developer.mozilla.org/zh-CN/docs/Web/API/Element/scrollIntoView


背景

笔者想要实现一个页面,该页面包括如下功能:

  1. 顶部Tab

    -需要置顶;

    -超出则左右可滑动;

    -点击时将选中Tab高亮,且自动居中

  2. 内容滚动区域

    -需和顶部Tab联动,即点击Tab,内容滚动至该Tab对应的锚点处;相应的,滚动内容,如到达该锚点时,对应的Tab也需要切换为高亮选中状态


模块、功能拆分:

  1. 页面容器内容,分为两个模块:

    tab等置顶模块;

    滚动区域模块

  2. tab模块实现切换高亮和选中时滚动居中功能
  3. 内容滚动区域,实现与顶部tab的联动


功能实现设计:

  1. 顶部Tab置顶,内容滚动区域选择

    方案一: 严格区分置顶区域和滚动区域,使用overflow:hidden 固定页面容器,将滚动区域的overflow设置为可滚动(注意此时的滚动容器)

    方案二: 直接使用position: fixed固定顶部Tab,滚动区域不做安排,直接使用window容器滚动

  2. 点击Tab时滚动内容导到指定锚点处:

    方案一: 使用scrollIntoView

    方案二: 使用基础的scrollTop赋值的方式

  3. 滚动内容时,如到达该埋点时,自动切换Tab

    方案: 监听scroll(根据功能1的方案确定滚动容器)

注意: 其实滚动容器的选择就决定了功能2、功能3的方案选择


方案比较:

  1. 滚动容器选择方案1,即部分区域可滚动

    缺点1: 需要配置好、区分好可滚动区域,且外部需要设置 overflow: hidden 阻止滚动(注意外部不可有滚动区域)

    缺点2: 后续的scroll监听需要依赖于选择的滚动容器节点

    缺点3: scrollIntoView的使用兼容性问题(smooth平滑动画无效果)

  2. 滚动容器选用方案2,即使用window容器进行滚动

    缺点1: scrollIntoView的效果,是将节点滚动到可滚动容器的顶端,而我们是有置顶内容的

所以:

最终选择: 1. 使用window作为滚动区域,不使用scrollIntoView,而是直接通过scrollTop赋值来实现滚动效果


方案一CSS代码: 可实现 顶部Tab 和 滚动区域的严格区分

.page-container {

position: relative;

overflow: hidden;

height: 100%;

display: flex;

flex-direction: column;

header {

// 顶部固定模块

width: 100%;

height: 1.8rem;

}

.scroll-container {

// 滚动容器

flex: 1;

overflow: auto;

.scroll-content {

// 滚动内容

.first {}

.second {}

.third {}

.fourth {}

.fifth {}

}

}

}

方案一JS代码: 可实现 点击Tab 滚动到指定锚点

methods: {

setActiveIndex(index) {

// 滚动tab到可视区域中间

// 使用 smooth 或者 center 有问题?

this.$refs[`topTab${index}`][0]?.scrollIntoView({ inline: 'center' });

if (this.activeIndex === index) {

return;

}

this.activeIndex = index;

// 滚动到指定位置

this.$refs[`scrollView${index}`]?.scrollIntoView();

},

}

scrollIntoView 平滑滚动
element.scrollIntoView({behavior: "smooth"})


最终实现方案:

切换Tab实现内容滚动到指定锚点:

methods: {

// 滚动到指定位置

scrollToElePosition(index) {

const { first, second, third, fourth, fifth } = this.$refs;

// 记录当前滚动状态

this.isSmoothScrolling = true;

// 动画时间

const SCROLL_DURATION = 500;

if (index === TAB_INDEX_ZERO) {

scrollToY(first.offsetTop, SCROLL_DURATION);

} else if (index === TAB_INDEX_ONE) {

scrollToY(second.offsetTop, SCROLL_DURATION);

} else if (index === TAB_INDEX_TWO) {

scrollToY(third.offsetTop, SCROLL_DURATION);

} else if (index === TAB_INDEX_THREE) {

scrollToY(fourth.offsetTop, SCROLL_DURATION);

} else if (index === TAB_INDEX_FOUR) {

scrollToY(fifth.offsetTop, SCROLL_DURATION);

}

},

}

使用scrollTop赋值实现滚动动画:

/*

* y: the y coordinate to scroll, 0 = top

* duration: scroll duration in milliseconds; default is 0 (no transition)

* element: the html element that should be scrolled ; default is the main scrolling element

*/

const scrollToY = function (y, duration = 0, element = document.scrollingElement) {

// cancel if already on target position

if (element.scrollTop === y) {

return;

}

const NUMBER_TWO = 2;

const cosParameter = (element.scrollTop - y) / NUMBER_TWO;

let scrollCount = 0;

let oldTimestamp = null;

const step = function (newTimestamp) {

if (oldTimestamp !== null) {

// if duration is 0 scrollCount will be Infinity

scrollCount += Math.PI * (newTimestamp - oldTimestamp) / duration;

if (scrollCount >= Math.PI) {

element.scrollTop = y;

return;

}

element.scrollTop = cosParameter + y + cosParameter * Math.cos(scrollCount);

}

oldTimestamp = newTimestamp;

window.requestAnimationFrame(step);

};

window.requestAnimationFrame(step);

};


监听内容滚动,切换Tab

实现方式: 主要是通过scroll监听来实现,通过获取当前的scrollTop 来和 每个锚点模块的offsetTop 进行比较

注意点:

  1. 注意throttle节流的使用
  2. 需要使用 isSmoothScrolling 来避免 和 切换Tab时滚动到指定锚点的功能相互影响

mounted() {

// 基准offset - first模块

const { offsetTop: firstOffsetTop } = first;

// 节流时间间隔

const THROTTLE_INTERVAL = 200;

window.addEventListener(

'scroll',

!this.isSmoothScrolling &&

throttle(() => {

// 正在平滑滚动,无需监听

if (this.isSmoothScrolling) return;

// 当前的滚动距离

const scrollTop =

window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;

// 各个临界值,可能会变

const secondOffsetTop = second.offsetTop;

const thirdOffsetTop = third.offsetTop;

const fourthOffsetTop = fourth.offsetTop;

const fifthOffsetTop = fifth.offsetTop;

// scroll计算当前的activeTab

this.scrollCalcCurrentActiveTab({

scrollTop,

firstOffsetTop,

secondOffsetTop,

thirdOffsetTop,

fourthOffsetTop,

fifthOffsetTop,

});

}, THROTTLE_INTERVAL)

);

},

methods: {

// scroll计算当前的activeTab

scrollCalcCurrentActiveTab({

scrollTop,

firstOffsetTop,

secondOffsetTop,

thirdOffsetTop,

fourthOffsetTop,

fifthOffsetTop,

}) {

// 此处向上取整,因为offsetTop获取的是整数,这会导致滚动时tab位置错误

const currentOffsetTop = firstOffsetTop + Math.ceil(scrollTop);

if (currentOffsetTop < secondOffsetTop) {

this.activeIndex = 0;

} else if (currentOffsetTop >= secondOffsetTop && currentOffsetTop < thirdOffsetTop) {

this.activeIndex = 1;

} else if (currentOffsetTop >= thirdOffsetTop && currentOffsetTop < fourthOffsetTop) {

this.activeIndex = 2;

} else if (currentOffsetTop >= fourthOffsetTop && currentOffsetTop < fifthOffsetTop) {

this.activeIndex = 3;

} else if (currentOffsetTop >= fifthOffsetTop) {

this.activeIndex = 4;

}

},

}


最后:

  1. 需要注意 动画滚动和 window.addEventListener('scroll') 监听相互干扰的情况,需要使用变量 isSmoothScrolling 来规避

    实现:

    在切换tab,内容滚动至锚点时 this.isSmoothScrolling = true;

    监听touchmove事件,将 this.isSmoothScrolling = false;

    scroll监听是通过判断isSmoothScrolling,来确定是否执行scroll监听的回调方法

mounted() {

// 此处通过

window.addEventListener(

'touchmove',

throttle(() => {

if (this.isSmoothScrolling) {

this.isSmoothScrolling = false;

}

})

);

}

  1. 注意使用节流来节省性能


最后的最后:

无jquery 的 滚动动画效果:
https://stackoverflow.com/questions/21474678/scrolltop-animation-without-jquery
https://github.com/Robbendebiene/Sliding-Scroll/blob/master/sliding-scroll.js

以上是 vue / js scrollIntoView的使用和替代方法(无jquery的滚动动画效果) 的全部内容, 来源链接: utcz.com/z/375842.html

回到顶部