When we use onSubmit
, the default behaviour is to instant scroll and focus invalid element. Sometimes, if you have fixed headers, the input becomes hidden after scroll.
The following function will:
- Select the first errored input
- Smoothly scroll to selected input
- Apply offset from top
- Set focus, when input becomes visible
// Declare a window variable to get only first invocation of 'onInvalid' from 'form'
declare global {
interface Window {
scrolled?: boolean;
}
}
export function scrollToElement(
element: EventTarget & HTMLFormElement,
offset: number = 250
) {
// It uses offset to check if its visible below header
function isFullVisible(element: EventTarget & HTMLFormElement, offset: number = 0) {
const elementRect = element.getBoundingClientRect();
const windowHeight =
(window.innerHeight || document.documentElement.clientHeight);
const isTopVisible = elementRect.top >= offset && elementRect.top < windowHeight;
const isBottomVisible =
elementRect.bottom >= offset && elementRect.bottom <= windowHeight;
const fullVisible = isTopVisible && isBottomVisible;
return fullVisible
}
function run(element: EventTarget & HTMLFormElement, offset: number = 250) {
const elementPosition =
element.getBoundingClientRect().top + window.scrollY;
const alreadyRunt = window.scrolled;
if (alreadyRunt) {
return;
} else {
window.scrolled = true;
// Time to guarantee that all invocations of 'onInvalid' was ended
setTimeout(() => delete window["scrolled"], 300);
}
if (isFullVisible(element, offset)) {
element?.focus();
return;
}
window.scroll({
// This scroll is not always accurate
// -1 is to force a scroll if by chance it is not visible for less than 1px
top: elementPosition - offset - 1,
behavior: "smooth",
});
window.addEventListener("scroll", function scrollHandler() {
// 0 offset to immediate focus on user view, and not when scroll ended
const fullVisible = isFullVisible(element, 0);
if (fullVisible) {
window.removeEventListener("scroll", scrollHandler);
element?.focus();
}
});
}
return run(element, offset);
}
Then just use it
<form
onSubmit={(e) => {
// ...
}}
onInvalid={(e) => {
e.preventDefault()
const element = e.target as EventTarget & HTMLFormElement
scrollToElement(element, 250) // 250 is header height + gap
}}
>
...
This post was created with the aim of helping people through the search, and registering this snippet for my future uses.
I hope you found this, and it helped you. :D
Top comments (0)