React中使用scrollIntoView时如何调慢平滑滚动速度
原生element.scrollIntoView({behavior: 'smooth'})的滚动速度由浏览器硬编码,没有开放配置接口,之前流传的window.scrollTo逐帧滚动方案在overflow:hidden容器下失效,核心原因是找错了实际的滚动容器:
overflow: hidden只会屏蔽用户手动触发滚动的交互(鼠标滚轮、拖拽滚动条等),完全不会限制JS通过scrollTop/scrollLeft属性控制元素滚动,之前的方案失效全是因为默认把window当成了滚动对象,没有定位到真正产生滚动条的父级容器。
下面是经过验证的可行实现,既可以自由控制滚动速度,也不需要修改容器的overflow属性,全程保留溢出隐藏效果:
实现代码
/** * 自定义速度的元素滚动方法 * @param {HTMLElement} targetEl 要滚动到的目标元素 * @param {Object} options 配置项 * @param {number} options.speed 滚动速度,单位px/秒,默认300(约为原生平滑滚动速度的1/2) * @param {number} options.offset 目标位置偏移量,正数为超出目标位置的距离,负数为预留空隙 * @param {string} options.block 垂直对齐规则,和原生scrollIntoView参数一致:start/center/end/nearest */ function slowScrollIntoView(targetEl, options = {}) { const config = Object.assign({ speed: 300, offset: 0, block: 'start' }, options) // 向上遍历查找真实的滚动容器 let scrollContainer = targetEl.parentElement while (scrollContainer) { const { overflowY } = window.getComputedStyle(scrollContainer) if (overflowY !== 'visible' && overflowY !== '') break scrollContainer = scrollContainer.parentElement } scrollContainer = scrollContainer || window // 计算目标滚动位置 const targetRect = targetEl.getBoundingClientRect() let currentScrollTop, containerViewportTop, containerHeight if (scrollContainer === window) { currentScrollTop = window.scrollY containerViewportTop = 0 containerHeight = window.innerHeight } else { currentScrollTop = scrollContainer.scrollTop const containerRect = scrollContainer.getBoundingClientRect() containerViewportTop = containerRect.top containerHeight = scrollContainer.clientHeight } const targetOffsetInContainer = targetRect.top - containerViewportTop let targetScrollTop switch(config.block) { case 'start': targetScrollTop = currentScrollTop + targetOffsetInContainer + config.offset break case 'center': targetScrollTop = currentScrollTop + targetOffsetInContainer - (containerHeight/2 - targetRect.height/2) + config.offset break case 'end': targetScrollTop = currentScrollTop + targetOffsetInContainer - (containerHeight - targetRect.height) + config.offset break case 'nearest': const startPos = currentScrollTop + targetOffsetInContainer + config.offset const endPos = currentScrollTop + targetOffsetInContainer - (containerHeight - targetRect.height) + config.offset targetScrollTop = Math.abs(startPos - currentScrollTop) < Math.abs(endPos - currentScrollTop) ? startPos : endPos break } // 边界修正,避免滚动超出容器范围 const maxScroll = scrollContainer === window ? document.documentElement.scrollHeight - window.innerHeight : scrollContainer.scrollHeight - scrollContainer.clientHeight targetScrollTop = Math.max(0, Math.min(targetScrollTop, maxScroll)) // requestAnimationFrame逐帧滚动,速度可控 let lastFrameTime = performance.now() const scrollFrame = (now) => { const delta = (now - lastFrameTime) / 1000 lastFrameTime = now const step = config.speed * delta const currentPos = scrollContainer === window ? window.scrollY : scrollContainer.scrollTop const nextPos = currentPos < targetScrollTop ? Math.min(currentPos + step, targetScrollTop) : Math.max(currentPos - step, targetScrollTop) scrollContainer === window ? window.scrollTo(0, nextPos) : (scrollContainer.scrollTop = nextPos) if (nextPos !== targetScrollTop) { scrollContainer._activeScrollRaf = requestAnimationFrame(scrollFrame) } } // 清掉同容器上正在运行的滚动动画,避免冲突 if (scrollContainer._activeScrollRaf) cancelAnimationFrame(scrollContainer._activeScrollRaf) scrollContainer._activeScrollRaf = requestAnimationFrame(scrollFrame) }
使用方式
// 示例:滚动到id为target的元素,速度200px/秒,顶部对齐 const targetElement = document.querySelector('#target') slowScrollIntoView(targetElement, { speed: 200, // 数值越小滚动越慢,原生平滑滚动速度约为700px/秒 block: 'start' })
方案优势
- 不修改容器原有样式:全程保留
overflow:hidden的溢出隐藏效果,不会出现滚动条闪烁、内容溢出的问题 - 速度完全自定义:通过speed参数可以自由调整滚动快慢,不受浏览器默认配置限制
- 兼容性好:所有支持
requestAnimationFrame的浏览器都可正常运行,和原生scrollIntoView的对齐逻辑保持一致 - 性能稳定:动画和浏览器刷新率同步,不会出现卡顿、跳帧问题,自动处理滚动边界,不会出现超出滚动范围的异常
内容的提问来源于stack exchange,提问作者Federico Leuze
相关产品推荐
相关产品推荐

