Unfortunately there's no 100% accurate solution
onvisibilitychange
correctly triggers on tab changes, but does not trigger on window changes (ALT+TAB) visibilitychange event is not triggered when switching program/window with ALT+TAB or clicking in taskbar
window.onfocus
triggers when the document becomes focused. This works as expected if the tab's focus is already inside the web page, then it correctly triggers when window or tab becomes focused.
But if you have the focus on the URL bar, or in the console, you are already "out of focus", and when you get out of the window or tab and return, you will remain "out of focus", so this event won't trigger until you click inside the page, or navigate into it through TAB key
You can test below how each event triggers (click inside the white iframe to test onfocus/onblur events)
window.onfocus = () => console.log("focus");
window.onblur = () => console.log("out of focus");
document.onvisibilitychange = () => console.log("visibilityState: ", document.visibilityState);