Reputation: 1379
I want to call focus()
on an input after the widow scrolled. I'm using the smooth behavior for the scrollTo()
method. The problem is the focus
method cut the smooth behavior. The solution is to call the focus
function just after the scroll end.
But I can't find any doc or threads speaking about how to detect the end of scrollTo
method.
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()
Any ideas?
Upvotes: 56
Views: 34728
Reputation: 137006
There is now a scrollend
event, supported everywhere but in Safari, but for what you want you shouldn't even need it, since we're supposed to be able to set the scrolling behavior through CSS, with the scroll-behavior
rule, this way even .focus()
should trigger a smooth scroll. Unfortunately, and weirdly enough, only Chrome seems to support this...
document.querySelector("button").onclick = (evt) => document.querySelector("input").focus();
.obstacle {
background: repeating-linear-gradient(black, black 20px, white 20px, white 40px);
height: 800vh;
}
html { /* The scrolling element */
scroll-behavior: smooth
}
<button>focus target</button>
<div class=obstacle></div>
<input>
Upvotes: 1
Reputation: 1170
I just wanted to make an async version of the @Fabian von Ellerts function, replaced the depreciated pageYOffset
, and added semicolons :)
/**
* Async scrollTo
* @param offset - offset to scroll to
*/
async function scrollTo(offset) {
return new Promise((resolve, reject) => {
const fixedOffset = offset.toFixed();
const onScroll = function () {
if (window.scrollY.toFixed() === fixedOffset) {
window.removeEventListener('scroll', onScroll);
resolve();
}
}
window.addEventListener('scroll', onScroll);
onScroll();
window.scrollTo({
top: offset,
behavior: 'smooth'
});
});
}
Upvotes: 0
Reputation: 5211
I wrote a generic function based on the solution of George Abitbol, without overwriting 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'
})
}
Upvotes: 56
Reputation: 7812
Other answers didn't fully work for me, therefore based on @Fabian von Ellerts answer, I wrote my own solution.
My problems were that :
The element I was scrolling (and all its parents along the hierarchy) always had a offsetTop
of 0, so it was not working.
I needed to scroll a nested element.
Using getBoundingClientRect
and a container element as reference works :
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',
})
}
Upvotes: 1
Reputation: 1379
I found a way to achieve what I want but I think it's a bit hacky, isn't it?
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
}
}
Upvotes: 4