When a path is initially navigated to that heavily utilizes Drupal states (think the new development page with Twig debuggin). The page is initially viewable with non-JS styles. Then the JavaScript kicks in and modifies the DOM so that the page layout shifts.

This issue is intended to mitigate or remove this jankiness (as much as possible).

Plan

The plan is to use the new at-media scripting feature to reduce the jank as much as possible in browsers that support this. For older browsers, the current behavior will still persist.

CommentFileSizeAuthor
state.mp4418.23 KBmherchel

Issue fork drupal-3404217

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

mherchel created an issue. See original summary.

kostyashupenko’s picture

Issue summary: View changes

Regarding this issue here is a list of states which can really damage layout shifting:

  • visible
  • invisible
  • expanded
  • collapsed

First two states can be used for any thing, while the last two states are for the details html element (playing around open html attribute).

Now about visible/invisible states. We can have something like

   $form['something'] = [
      '#type' => 'fieldset',
      '#title' => $this->t('Some title'),
      '#states' => [
        'visible' => $something,
      ],
    ];

With the code above we adding state `visible` directly to the container fieldset.
While with this code:

  $form['something'] = [
      '#type' => 'checkbox',
      '#title' => $this->t('Something'),
      '#states' => [
        'visible' => $something,
      ],
    ];

we adding state directly to checkbox. But rendering system in Drupal provides form_element hook theme which is actually a wrapper of form elements. And according to states.js

  $document.on('state:visible', (e) => {
    if (e.trigger) {
      let $element = $(e.target).closest(
        '.js-form-item, .js-form-submit, .js-form-wrapper',
      );
      // For links, update the state of itself instead of the wrapper.
      if (e.target.tagName === 'A') {
        $element = $(e.target);
      }
      $element.toggle(e.value);
    }
  });

we are not actually hiding/showing checkbox itself. We hiding/showing container (form-element container) element.

=====

What i want to say is that we can't solve this issue just by using CSS media query "scripting". Instead, here should be more complex logic which can give frontender some attributes or classnames of initial state.

Here is example:
1. Visit /admin/config/development/settings page
2. Initially all checkboxes are unchecked.
3. If you will toggle "Twig development mode" checkbox and then click on save btn
4. On the next visits of this page initial state will be different. Because checkbox is initially checked, so container element which is sensitive to the state of this checkbox should be initially visible. <-- here we are missing some helpful attributes or classnames, just to detect initial state to understand which CSS rules we should write.

If initial state should be "visible" -> we don't need to hide anything.
If initial state should be "hidden" -> we should hide element using CSS.

====
Summarizing:
We have to have some extra html attribute or classname above the element with state, which explains initial behavior. For instance data-initial-state="hidden". These attributes/classnames we could use to prevent layout shifting on initial page load. For instance:

  @media (scripting: enabled) {
    [data-initial-state="hidden"]:not(data-once*="states") {
      display: none;
    }
  }

With html element "details" we could add just from PHP "open" html attribute in details html tag in case if initial state is "expanded"

kostyashupenko’s picture

Status: Active » Needs review

Added proof of concept in merge request. You can test on '/admin/config/development/settings' page

andypost’s picture

smustgrave’s picture

Status: Needs review » Needs work
Issue tags: +Needs issue summary update

The issue summary mentions using at-media scripting feature, but proof of concept seemed to go a different route. Could it be explained some/documented in issue summary why a different approach is needed?

emil stoianov’s picture

The proposed solution works but one needs to manually set the initial_state for each container
Some additional work is needed on automatically setting the initial_state.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.