refreshless-8.x-1.x-dev/modules/refreshless_turbo/js/browser_fixes/scroll_reset.js
modules/refreshless_turbo/js/browser_fixes/scroll_reset.js
/**
* @file
* Fixes scroll sometimes not resetting to the top on forward navigation.
*
* This works around a Firefox issue where the browser will sometimes not
* scroll to the top as expected on an advance visit (i.e. clicking a link,
* not a back or forward history navigation) if scroll-behavior: smooth; is
* set on the <html> element. The solution is to temporarily force
* scroll-behavior: auto; (instant scrolling) right before the render and remove
* it once the render has finished.
*
* Also note that Turbo at the time of writing seems to add aria-busy to
* <html> on clicking an in-page anchor (such as a table of contents) and not
* remove it until navigated to a different page. Because of this, we can't
* use a CSS-only solution.
*
* @see https://github.com/hotwired/turbo/issues/190#issuecomment-1525135845
* CSS-only solution for future reference.
*
* @see https://github.com/hotwired/turbo/issues/426
*
* @see https://github.com/hotwired/turbo/issues/698
*/
(function(html, $, once) {
'use strict';
/**
* Our event namespace.
*
* @type {String}
*
* @see https://learn.jquery.com/events/event-basics/#namespacing-events
*/
const eventNamespace = 'refreshless-turbo-disable-smooth-scroll';
/**
* The once() identifier for our behaviour.
*
* @type {String}
*/
const onceName = eventNamespace;
/**
* Class added to the <html> element during a load to prevent smooth scroll.
*
* @type {String}
*/
const smoothScrollDisableClass = 'refreshless-disable-smooth-scroll';
$(once(onceName, html))
.on(`refreshless:before-render.${eventNamespace}`, async (event) => {
// Don't do anything if this a refresh copy being rendered replacing a
// preview as the page is already interactive.
if (event.detail.isFreshReplacingPreview === true) {
return;
}
await event.detail.delay(async (resolve, reject) => {
$(event.target).addClass(smoothScrollDisableClass);
// Wait for a frame to be rendered before resolving so that instant
// scrolling is applied by the browser before page content is swapped.
await new Promise(requestAnimationFrame);
await new Promise(requestAnimationFrame);
resolve();
});
}).on(`refreshless:render.${eventNamespace}`, async (event) => {
// Don't do anything if this a refresh copy being rendered replacing a
// preview as the page is already interactive.
if (event.detail.isFreshReplacingPreview === true) {
return;
}
// Wait for at least a frame to be rendered to reduce the chance we might
// remove the class too early and the browser ends up smooth scrolling.
await new Promise(requestAnimationFrame);
await new Promise(requestAnimationFrame);
$(event.target).removeClass(smoothScrollDisableClass);
});
})(document.documentElement, jQuery, once);
