window.scrollTo有回调函数吗?

49

我希望在窗口平滑滚动时对一个输入框调用 focus()。但是问题是 focus 方法会中断平滑滚动的效果。解决方案是在滚动结束后立即调用 focus 函数。

但是我找不到任何文档或线程来讲述如何检测 scrollTo 方法的结束。

let el = document.getElementById('input')
let elScrollOffset = el.getBoundingClientRect().top
let scrollOffset = window.pageYOffset || document.documentElement.scrollTop
let padding = 12
window.scrollTo({
  top: elScrollOffset + scrollOffset - padding,
  behavior: 'smooth'
})
// wait for the end of scrolling and then
el.focus()

有任何想法吗?


我不确定scrollTo()是否存在回调函数,但是如果你知道滚动的持续时间,可以尝试使用setTimeout() - sinawic
是的,我也在考虑这个问题,但我找不到“scrollTo”方法的滚动持续时间 :/ - Existe Deja
在使用你的 hacky 方法之前,你或许想尝试这个 - Akaino
1
可能是如何在Javascript中知道滚动到元素已完成?的重复问题。 - StudioTime
@Akaino 谢谢你的提示,在普通版中,我只需要在onscroll监听器内工作并观察页面偏移量。这与我正在做的不远。 - Existe Deja
@Darren Sweeney,你说得对,我并没有寻找scrollIntoView,因为它的支持还不够完善。我也会回答这个问题,谢谢! - Existe Deja
3个回答

51

我基于George Abitbol的解决方案编写了一个通用函数,而不是覆盖window.onscroll:

/**
 * Native scrollTo with callback
 * @param offset - offset to scroll to
 * @param callback - callback function
 */
function scrollTo(offset, callback) {
    const fixedOffset = offset.toFixed();
    const onScroll = function () {
            if (window.pageYOffset.toFixed() === fixedOffset) {
                window.removeEventListener('scroll', onScroll)
                callback()
            }
        }

    window.addEventListener('scroll', onScroll)
    onScroll()
    window.scrollTo({
        top: offset,
        behavior: 'smooth'
    })
}

5
谢谢!我在尝试过后添加了这个内容,因为如果滚动位置已经正确,没有这个调用就不会触发回调。 - Fabian von Ellerts
1
偏移量需要是整数才能使该函数正常工作。 - btjakes
2
@btjakes 为了澄清:offset 需要完全匹配 window.pageYOffset。在 MS Edge 中,window.pageYOffset 是一个浮点数,而其他浏览器不是。由于跨浏览器的精度不匹配,除非你在四舍五入和可能制作完成缓冲区时小心处理,否则这种方法将在其中一种浏览器中失败:if (roundedPageYOffset + 2 >= roundedOffset && roundedOffset >= roundedPageYOffset - 2) { ... },而不是仅仅使用 if (window.pageYOffset === offset) { ... } - btjakes
1
@btjakes 我已经修复了数字,所以浮点数不再是一个问题。四舍五入是个好主意,但根据用例可能不够精确。 - Fabian von Ellerts
1
@Ryan 谢谢你提供了替代问题的链接,我在那里找到了我确切的用例! - Felipe Maia
显示剩余5条评论

4

我发现了一种实现我想要的方法,但我认为它有点hacky,不是吗?

let el = document.getElementById('input')
let elScrollOffset = el.getBoundingClientRect().top
let scrollOffset = window.pageYOffset || document.documentElement.scrollTop
let padding = 12
let target = elScrollOffset + scrollOffset - padding
window.scrollTo({
  top: target,
  behavior: 'smooth'
})
window.onscroll = e => {
  let currentScrollOffset = window.pageYOffset || document.documentElement.scrollTop
  // Scroll reach the target
  if (currentScrollOffset === target) {
    el.focus()
    window.onscroll = null // remove listener
  }
}

1

其他答案对我来说并没有完全奏效,因此基于@Fabian von Ellert的答案,我编写了自己的解决方案。

我的问题是:

  • 我滚动的元素(以及在层次结构中所有其父级)总是具有0的offsetTop,因此它无法工作。

  • 我需要滚动嵌套元素。

使用getBoundingClientRect和容器元素作为参考可以解决问题:

    const smoothScrollTo = (
        scrollContainer,
        scrolledContent,
        offset,
        callback
    ) => {
        const fixedOffset = (
            scrollContainer.getBoundingClientRect().top + offset
        ).toFixed()
        const onScroll = () => {
            if (
                scrolledContent.getBoundingClientRect().top.toFixed() ===
                fixedOffset
            ) {
                scrollContainer.removeEventListener('scroll', onScroll)
                callback()
            }
        }
        scrollContainer.addEventListener('scroll', onScroll)
        onScroll()
        scrollContainer.scrollTo({
            top: offset,
            behavior: 'smooth',
        })
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接