When considering various scenarios, it is important to:
- be mindful of users trying to interact with a web browser while scrolling
- take into account situations where the web browser has a set zoom level and floating-point numbers are returned by the DOM API.
Source:
function SmoothScroller(element, interval = 100, chances = 3, tolerance = 1.0) {
let id = null;
this.scroll = (x, y, callback) => {
if (id) {
clearTimeout(id);
id = null;
}
const options = {
left: x,
top: y,
behavior: 'smooth'
};
element.scrollTo(options);
if (callback) {
let state = +Infinity;
const action = () => {
const elementX = element.scrollLeft;
const elementY = element.scrollTop;
const dx = x - elementX;
const dy = y - elementY;
const square = dx * dx + dy * dy;
if (square < tolerance) {
callback('done');
} else {
const dx = x - elementX;
const dy = y - elementY;
const space = dx * dx + dy * dy;
if (square === state) {
if (chances > 0) {
state = space;
chances -= 1;
id = setTimeout(action, interval);
} else {
callback('canceled');
}
} else {
state = space;
id = setTimeout(action, interval);
}
}
};
id = setTimeout(action, interval);
}
};
this.stop = () => {
if (id) {
clearTimeout(id);
id = null;
}
};
}
// Usage example:
const element = ... // e.g. document.querySelector('#element');
const scroller = new SmoothScroller(element);
scroller.scroll(50, 100, (status) => console.log(`status: ${status}`)); // scrolls into (x, y) = (50, 100) position