diff --git a/core/modules/toolbar/js/toolbar.anti-flicker.js b/core/modules/toolbar/js/toolbar.anti-flicker.js new file mode 100644 index 0000000000000000000000000000000000000000..43946ba8dc647e470811c7ce98dc2109654734f1 --- /dev/null +++ b/core/modules/toolbar/js/toolbar.anti-flicker.js @@ -0,0 +1,72 @@ +/** + * @file + * Prevents flicker of the toolbar on page load. + */ + +(() => { + const toolbarState = sessionStorage.getItem('Drupal.toolbar.toolbarState') + ? JSON.parse(sessionStorage.getItem('Drupal.toolbar.toolbarState')) + : false; + // These are classes that toolbar typically adds to <body>, but this code + // executes before the first paint, when <body> is not yet present. The + // classes are added to <html> so styling immediately reflects the current + // toolbar state. The classes are removed after the toolbar completes + // initialization. + const classesToAdd = ['toolbar-loading', 'toolbar-anti-flicker']; + if (toolbarState) { + const { + orientation, + hasActiveTab, + isFixed, + activeTray, + activeTabId, + isOriented, + userButtonMinWidth, + } = toolbarState; + + classesToAdd.push( + orientation ? `toolbar-${orientation}` : 'toolbar-horizontal', + ); + if (hasActiveTab !== false) { + classesToAdd.push('toolbar-tray-open'); + } + if (isFixed) { + classesToAdd.push('toolbar-fixed'); + } + if (isOriented) { + classesToAdd.push('toolbar-oriented'); + } + + if (activeTray) { + // These styles are added so the active tab/tray styles are present + // immediately instead of "flickering" on as the toolbar initializes. In + // instances where a tray is lazy loaded, these styles facilitate the + // lazy loaded tray appearing gracefully and without reflow. + const styleContent = ` + .toolbar-loading #${activeTabId} { + background-image: linear-gradient(rgba(255, 255, 255, 0.25) 20%, transparent 200%); + } + .toolbar-loading #${activeTabId}-tray { + display: block; box-shadow: -1px 0 5px 2px rgb(0 0 0 / 33%); + border-right: 1px solid #aaa; background-color: #f5f5f5; + z-index: 0; + } + .toolbar-loading.toolbar-vertical.toolbar-tray-open #${activeTabId}-tray { + width: 15rem; height: 100vh; + } + .toolbar-loading.toolbar-horizontal :not(#${activeTray}) > .toolbar-lining {opacity: 0}`; + + const style = document.createElement('style'); + style.textContent = styleContent; + style.setAttribute('data-toolbar-anti-flicker-loading', true); + document.querySelector('head').appendChild(style); + if (userButtonMinWidth) { + const userButtonStyle = document.createElement('style'); + userButtonStyle.textContent = ` + #toolbar-item-user {min-width: ${userButtonMinWidth}.px;}`; + document.querySelector('head').appendChild(userButtonStyle); + } + } + } + document.querySelector('html').classList.add(...classesToAdd); +})(); diff --git a/core/modules/toolbar/toolbar.libraries.yml b/core/modules/toolbar/toolbar.libraries.yml index 061bd7a98b4d527f4bdcedb2a4cf359b3656d34c..af520b8f89a972d3402259a97499015a18946c35 100644 --- a/core/modules/toolbar/toolbar.libraries.yml +++ b/core/modules/toolbar/toolbar.libraries.yml @@ -28,6 +28,7 @@ toolbar: - core/once - core/drupal.displace - toolbar/toolbar.menu + - toolbar/toolbar.anti-flicker toolbar.menu: version: VERSION @@ -50,3 +51,9 @@ toolbar.escapeAdmin: - core/drupal - core/drupalSettings - core/once +toolbar.anti-flicker: + # Block the page from being loaded until anti-flicker is initialized. + version: VERSION + header: true + js: + js/toolbar.anti-flicker.js: {} diff --git a/core/modules/toolbar/toolbar.module b/core/modules/toolbar/toolbar.module index e5ebb050393b219fc23adf4ca8bb3b3c323d7217..feeb1d4b2aa7df49ce9c390cbaeee2a8987b7bed 100644 --- a/core/modules/toolbar/toolbar.module +++ b/core/modules/toolbar/toolbar.module @@ -7,7 +7,6 @@ use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Render\Element; -use Drupal\Core\Render\Markup; use Drupal\Core\Render\RenderContext; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Template\Attribute; @@ -49,101 +48,6 @@ function toolbar_theme($existing, $type, $theme, $path) { return $items; } -/** - * Implements hook_page_attachments(). - */ -function toolbar_page_attachments(array &$page) { - // This JavaScript code provides temporary styles while the toolbar loads, so - // it better visually resembles the appearance it will have once fully loaded. - // @todo investigate potential alternatives to this approach in - // https://www.drupal.org/i/3355381 - $anti_flicker_js = <<<JS -(function() { - const toolbarState = sessionStorage.getItem('Drupal.toolbar.toolbarState') - ? JSON.parse(sessionStorage.getItem('Drupal.toolbar.toolbarState')) - : false; - // These are classes that toolbar typically adds to <body>, but this code - // executes before the first paint, when <body> is not yet present. The - // classes are added to <html> so styling immediately reflects the current - // toolbar state. The classes are removed after the toolbar completes - // initialization. - const classesToAdd = ['toolbar-loading', 'toolbar-anti-flicker']; - if (toolbarState) { - const { - orientation, - hasActiveTab, - isFixed, - activeTray, - activeTabId, - isOriented, - userButtonMinWidth - } = toolbarState; - - classesToAdd.push( - orientation ? `toolbar-` + orientation + `` : 'toolbar-horizontal', - ); - if (hasActiveTab !== false) { - classesToAdd.push('toolbar-tray-open'); - } - if (isFixed) { - classesToAdd.push('toolbar-fixed'); - } - if (isOriented) { - classesToAdd.push('toolbar-oriented'); - } - - if (activeTray) { - // These styles are added so the active tab/tray styles are present - // immediately instead of "flickering" on as the toolbar initializes. In - // instances where a tray is lazy loaded, these styles facilitate the - // lazy loaded tray appearing gracefully and without reflow. - const styleContent = ` - .toolbar-loading #` + activeTabId + ` { - background-image: linear-gradient(rgba(255, 255, 255, 0.25) 20%, transparent 200%); - } - .toolbar-loading #` + activeTabId + `-tray { - display: block; box-shadow: -1px 0 5px 2px rgb(0 0 0 / 33%); - border-right: 1px solid #aaa; background-color: #f5f5f5; - z-index: 0; - } - .toolbar-loading.toolbar-vertical.toolbar-tray-open #` + activeTabId + `-tray { - width: 15rem; height: 100vh; - } - .toolbar-loading.toolbar-horizontal :not(#` + activeTray + `) > .toolbar-lining {opacity: 0}`; - - const style = document.createElement('style'); - style.textContent = styleContent; - style.setAttribute('data-toolbar-anti-flicker-loading', true); - document.querySelector('head').appendChild(style); - - if (userButtonMinWidth) { - const userButtonStyle = document.createElement('style'); - userButtonStyle.textContent = `#toolbar-item-user {min-width: ` + userButtonMinWidth +`px;}` - document.querySelector('head').appendChild(userButtonStyle); - } - } - } - document.querySelector('html').classList.add(...classesToAdd); -})(); -JS; - - // The anti flicker javascript is added as an inline tag so it is executed - // as early as possible. This enables it to add classes that prevent - // flickering before the first paint. - $page['#attached']['html_head'][] = [ - [ - '#tag' => 'script', - '#attributes' => [ - 'type' => 'text/javascript', - 'data-toolbar-anti-flicker-loading' => TRUE, - ], - // Process through Markup to prevent character escaping. - '#value' => Markup::create($anti_flicker_js), - ], - 'anti_flicker_js', - ]; -} - /** * Implements hook_page_top(). * diff --git a/core/profiles/demo_umami/tests/src/FunctionalJavascript/PerformanceTest.php b/core/profiles/demo_umami/tests/src/FunctionalJavascript/PerformanceTest.php index baecf5b8387e9ca066e95c288ea43f3c7542788a..9af5caf95ad68bc8d690ea5f42aa9d2ebc761c32 100644 --- a/core/profiles/demo_umami/tests/src/FunctionalJavascript/PerformanceTest.php +++ b/core/profiles/demo_umami/tests/src/FunctionalJavascript/PerformanceTest.php @@ -35,7 +35,7 @@ public function testFrontPagePerformance(): void { $this->drupalGet('<front>'); $this->assertSession()->pageTextContains('Umami'); $this->assertSame(2, $this->stylesheetCount); - $this->assertSame(1, $this->scriptCount); + $this->assertSame(2, $this->scriptCount); } }