Problem/Motivation

As a follow-up to #3167034-30: Leverage the 'loading' html attribute to enable lazy-load by default for images in Drupal core and elsewhere, let's add a UI to allow the loading attribute to be changed from the UI.

Steps to reproduce

Proposed resolution

Add a new setting in the Image field formatter that allow users to select the loading priority.

Remaining tasks

User interface changes

By default settings are closed to de-emphasize these options (here they are expanded).

Summary view

API changes

Data model changes

Release notes snippet

A user interface has been added to image formatters to choose whether images should be loaded 'lazy' or 'eager', previously, lazy loading was hard-coded. This allows site owners to override the default for images that are likely to be shown above the fold.

CommentFileSizeAuthor
#141 3173180-91-1287-D9.3-3.patch32.86 KBs1933
#140 3173180-91-1287-D9.3-2.patch32.89 KBs1933
#139 3173180-91-1287-D9.3-1.patch32.91 KBs1933
#112 lazy-load-formatter.png41.58 KBheddn
#101 image (2).png33.57 KBheddn
#100 switch-radios.png64.72 KBheddn
#98 field-display-summary.png12.98 KBheddn
#98 field-display-expanded.png49.14 KBheddn
#96 expanded.png91.62 KBrkoller
#96 collapsed.png51.05 KBrkoller
#91 3173180-91-1287-D9.3.patch33.55 KBkiwimind
#90 3173180-90-1287-D9.2.patch34.84 KBgiorgosk
#89 3173180-89-1287-D9.2.patch34.87 KBgiorgosk
#66 core-3173180-lazy-load-ui-66.patch19.77 KB3li
#51 res-image-no-dimensions-required-if-not-lazy.png105.98 KBedysmp
#51 image-formatter.png110.6 KBedysmp
#51 responsive-image-formatter.png126.58 KBedysmp
#51 3173180-51.patch19.66 KBedysmp
#51 interdiff-44-51.txt16.13 KBedysmp
#46 after--patch.jpg434.35 KBranjith_kumar_k_u
#44 3173180-44.patch13.19 KBedysmp
#44 interdiff-41-44.txt2.61 KBedysmp
#43 Expanded.png112.68 KBedysmp
#43 Default view.png83.57 KBedysmp
#41 3173180-41.patch10.57 KBedysmp
#41 interdiff-39-41.txt737 bytesedysmp
#39 3173180-39.patch9.85 KBedysmp
#39 interdiff-37_39.txt3.9 KBedysmp
#37 interdiff_31_37.txt1.42 KBanmolgoyal74
#37 3173180-37.patch7 KBanmolgoyal74
#31 3173180-31.patch7 KBedysmp
#31 interdiff-29-31.txt3.17 KBedysmp
#29 3173180-29.patch3.84 KBedysmp
#29 interdiff-23-29.txt8.96 KBedysmp
#23 3173180-23.patch9.12 KBandypost
#23 interdiff.txt4.02 KBandypost
#20 interdiff_19-20.txt1.76 KBchrisfree
#20 3173180-20.patch8.96 KBchrisfree
#19 interdiff_17-19.txt1.74 KBchrisfree
#19 3173180-19.patch8.96 KBchrisfree
#17 interdiff_14-17.txt7.32 KBchrisfree
#17 3173180-17.patch8.93 KBchrisfree
#14 3173180-14.patch3.25 KBmarkdorison
#14 interdiff-3173180-11-14.txt1.01 KBmarkdorison
#11 interdiff-8-11.patch910 byteschrisfree
#11 3173180-9.patch3.24 KBchrisfree
#11 Screen Shot 2020-10-09 at 7.56.50 PM.png397.21 KBchrisfree
#6 3173180-6.patch2.73 KBedysmp
#8 3173180-8.patch3.24 KBedysmp
#6 interdiff-4-6.txt2.28 KBedysmp
#8 interdiff-6-8.txt523 bytesedysmp
#4 3173180-4.patch1.85 KBedysmp

Issue fork drupal-3173180

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

heddn created an issue. See original summary.

chrisfree’s picture

Below is a simple mockup of a potential solution, which I proposed over in #3167034: Leverage the 'loading' html attribute to enable lazy-load by default for images in Drupal core:

Proposed Image field formatter configuration UI

Drupal display formatter UI, showing a proposed lazy loading priority field

The options would map to the supported values: auto, lazy, eager, lazy being the default. Rendered markup would respond accordingly, based on this setting. I could also see this additional display field/UI being gated behind a higher level setting if its inclusion by default is considered too heavy handed.

My thinking centers around the fact that there are certain instances where setting the loading attribute to something other than lazy is desirable. For example, if a page has an image which makes up the Largest Contentful Paint, its loadingattribute should be set to loading="eager" to tell the browser to load that resource right away. Doing so should also help with First Contentful Paint, another important performance metric.

There might also be sites that handle this on their own and wish to disable Drupal core's defaults.

I am struggling to think of a better place to contain this functionality. I don't believe it belongs on the image style. Doing so could be far too broad an assumption. Imagine a hero_image_xl image style. It might be used across a site and across content types, in varying layout positions. Customizing its loading attribute to eager would thus have adverse effects. To me, this attribute it purely about display, and thus should be configurable on the image field's display formatter. In this way it is decoupled from the image style and configurable based on how/where the content is presented.

adamzimmermann’s picture

This approach makes sense to me and seems like the right place to address the problem.

I would be interested in helping create a patch if others are in support of the approach.

edysmp’s picture

Issue summary: View changes
Status: Active » Needs review
StatusFileSize
new1.85 KB

Here is a quick patch that add the UI for the Image field formatter. It's functional. Please test it.

We also need to see how to integrate this with CKeditor and put one eye on this issue: https://www.drupal.org/project/drupal/issues/2061377

The UI also should be implemented for the Responsive image module e.g adding the UI into its field formatter.

andypost’s picture

  1. +++ b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php
    @@ -128,6 +129,17 @@ public function settingsForm(array $form, FormStateInterface $form_state) {
    +      'auto' => t('Auto'),
    +      'lazy' => t('Lazy'),
    +      'eager' => t('Eager'),
    ...
    +      '#title' => t('Lazy loading priority'),
    
    @@ -161,6 +173,9 @@ public function settingsSummary() {
    +    $summary[] = t('Lazy loading priority: @priority', ['@priority' => $lazy_loading_priority_setting]);
    

    it should use $this->t() as inside of class

  2. +++ b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php
    @@ -128,6 +129,17 @@ public function settingsForm(array $form, FormStateInterface $form_state) {
    +      '#options' => $lazy_load_options,
    

    Would be great to add description with a link to docs about what each option means

edysmp’s picture

StatusFileSize
new2.28 KB
new2.73 KB

Addressing #5 ;)

The last submitted patch, 4: 3173180-4.patch, failed testing. View results

edysmp’s picture

StatusFileSize
new523 bytes
new3.24 KB

Added new setting schema.

The last submitted patch, 6: 3173180-6.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 8: 3173180-8.patch, failed testing. View results

chrisfree’s picture

StatusFileSize
new397.21 KB
new3.24 KB
new910 bytes

I tested 3173180-8.patch and it works as advertised. I was able to successfully create a new display mode called "Hero" for an existing Media entity type (Image) and from within said display mode, configure it's default Image field to have a lazy loading value of eager, auto, and lazy:

Screenshot of proposed Media manage display mode UI

I was then able to verify that when rendering this "Hero" display mode, the chosen value is rendered as expected.

That said, I think it would be following existing standards to link to Mozilla's documentation instead of Google's. I see that Drupal core already does so 18 times. I see no references to the web.dev website.

As such attached is a patch (and interdiff) that changes the referenced documentation to that of Mozilla.

edysmp’s picture

Thanks for testing!
Does the new link list the loading options?
IMHO in that case we could use https://html.spec.whatwg.org/multipage/urls-and-fetching.html#lazy-loadi... instead. We already have used this website in core too.

chrisfree’s picture

Oh good point. The Mozilla page isn’t nearly as clear on that point. I’ll do some more research and get another patch going if need be.

markdorison’s picture

Status: Needs work » Needs review
StatusFileSize
new1.01 KB
new3.25 KB

Updated link as suggested in #12

Status: Needs review » Needs work

The last submitted patch, 14: 3173180-14.patch, failed testing. View results

chrisfree’s picture

The whatwg.org/ spec is probably the right/better documentation page to link to. That said, it does not list auto as one of the supported attributes. Since auto in Chrome is equivalent to the loading attribute not existing, and since the spec defines eager as the both the missing value and invalid value default, it would probably make sense to remove auto as an option. Thoughts?

Additionally, I still wonder if exposing the "Lazy loading priority" field on the display formatter should be gated by some higher level configuration. I think the best place for this would be under "Media settings". This could also be a great place to better describe Drupal's default's for native lazy loading.

I'm going to work on a patch for this right now.

chrisfree’s picture

StatusFileSize
new8.93 KB
new7.32 KB

Here is draft of a patch that adds a configuration option within the Media module for globally enabling/disabling per image formatter lazy loading settings.

The Image formatter now checks to see if customization is enabled before displaying lazy loading configuration options.

After getting this all working, I realized that the Media module might not be the best place for this configuration because it cannot be assumed that the Media module will be enabled on all sites. Or in other words, the Image module might be in use, but the Media module might not. This draft assumes Media module as a dependency.

Since the main lazy loading bits are all contained in the Image module, I suppose that might be a better place for this configuration UI, but I wonder then about how the responsive_image module will play into this (See #3173231: Enable lazy-load by default for responsive images). In that case, maybe it's ok to allow this level of customization to live on in a separate place (such as the Media module)?

In any case, I took a pass at what the UI might look like, including help text to describe native lazy loading, overriding.

This will also need tests, but I am not versed in creating such things.

andypost’s picture

Issue tags: +Needs tests, +Needs upgrade path
  1. +++ b/core/modules/image/config/schema/image.schema.yml
    @@ -142,6 +142,9 @@ field.formatter.settings.image:
    +    lazy_loading_priority:
    
    +++ b/core/modules/media/config/install/media.settings.yml
    @@ -2,3 +2,4 @@ icon_base_uri: 'public://media-icons/generic'
    +lazy_loading_customization: false
    

    new config needs upgrade path and upgrade tests to be ready for existing sites

  2. +++ b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php
    @@ -129,6 +142,23 @@ public function settingsForm(array $form, FormStateInterface $form_state) {
    +      $description_link = $this->t('Select the lazy-load priority for load images. <a href="@link">Learn more.</a', ['@link' => 'https://html.spec.whatwg.org/multipage/urls-and-fetching.html#lazy-loading-attributes']);
    

    Links must use ":" for URL - <a href=":link">

chrisfree’s picture

StatusFileSize
new8.96 KB
new1.74 KB

Here's an updated patch and interdiff, fixing the link in the help text.

chrisfree’s picture

StatusFileSize
new8.96 KB
new1.76 KB

Whoops, I didn't use the proper syntax for URLs. This patch fixes that.

markdorison’s picture

Status: Needs work » Needs review
andypost’s picture

Status: Needs review » Needs work
+++ b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php
@@ -43,6 +44,13 @@ class ImageFormatter extends ImageFormatterBase {
+   * @var \Drupal\Core\Config\ImmutableConfig
...
+  protected $config;

@@ -64,11 +72,14 @@ class ImageFormatter extends ImageFormatterBase {
+    $this->config = $config_factory->get('media.settings');

Please don't store immutable config in protected property, no reason to read config at plugin creation time and it makes serialization weird

+++ b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php
@@ -129,6 +142,23 @@ public function settingsForm(array $form, FormStateInterface $form_state) {
+      $description_link = $this->t('Select the lazy-load priority for load images. <a href="@link">Learn more.</a', ['@link' => 'https://html.spec.whatwg.org/multipage/urls-and-fetching.html#lazy-loading-attributes']);

Link still rendered with wrong selector, see #18

andypost’s picture

StatusFileSize
new4.02 KB
new9.12 KB

Fix #22 but leaving NW for #18.1

andypost’s picture

+++ b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php
@@ -129,6 +146,26 @@ public function settingsForm(array $form, FormStateInterface $form_state) {
+    $custom_loading_enabled = $this->configFactory->get('media.settings')
+      ->get('lazy_loading_customization');
+    if ($custom_loading_enabled) {
...
+      $lazy_load_options = [
+        'auto' => $this->t('Auto'),
+        'lazy' => $this->t('Lazy'),
+        'eager' => $this->t('Eager'),
+      ];

btw instead of global setting it could use "empty/none" option as default for formatter

chrisfree’s picture

Thanks @andypost. I wasn't fully sure how to handle all of the config bits. I was following the lead of the oembed field formatter. A bit of which was over my head.

I'll recruit some help on my end to work on the upgrade path stuff.

edysmp’s picture

#16 and #24.
I also think auto doesn't make too sense in the UI, maybe just keeping lazy and eager and set the fallback option to auto.
e.g:

    $lazy_load_options = [
      'lazy' => $this->t('Lazy'),
      'eager' => $this->t('Eager'),
    ];
    $element['lazy_loading_priority'] = [
      '#title' => $this->t('Lazy loading priority'),
      '#type' => 'select',
      '#default_value' => $this->getSetting('lazy_loading_priority'),
      '#options' => $lazy_load_options,
      '#description' => $description_link,
      '#empty_option' => '- None -', // by default is - None - . So we won't need it, at least we want another word.
      '#empty_value' => 'auto',
    ];
heddn’s picture

+++ b/core/modules/media/src/Form/MediaSettingsForm.php
@@ -110,6 +110,23 @@ public function buildForm(array $form, FormStateInterface $form_state) {
+    $form['performance']['lazy_loading_customization'] = [

I don't think we should add a flag to turn on/off globally. It is one more thing to confuse site owners about how to configure the widget to display the settings. Instead, if we want to encourage a certain behaviour (keep it lazy by default), add the settings on the field formatter in a closed details form element.

'#type' => 'details'

That way it is a little more hard to shoot yourself in the foot, but still right there for those who want to configure the setting.

chrisfree’s picture

I think @heddn is right. It would significantly reduce the complexity here in this issue, but also seems like a better user experience for site owners. The '#type' => 'details' idea is a ++ from me.

edysmp’s picture

Status: Needs work » Needs review
StatusFileSize
new8.96 KB
new3.84 KB

Moved changes into the details element. Also applied #26.

Status: Needs review » Needs work

The last submitted patch, 29: 3173180-29.patch, failed testing. View results

edysmp’s picture

StatusFileSize
new3.17 KB
new7 KB

Fixing Image.Image tests.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

heddn’s picture

Status: Needs work » Needs review
heddn’s picture

Status: Needs review » Needs work

NW for upgrade path and below nits.

  1. +++ b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php
    @@ -129,6 +130,29 @@ public function settingsForm(array $form, FormStateInterface $form_state) {
    +    $performance_description = $this->t('By default, all Image assets are rendered with native browser lazy loading attributes included (<em>loading="lazy"</em>). This improves performance by allowing <a href=":link">modern browsers</a> to lazily load images without JavaScript. It is sometimes desirable to override this default to force browsers to download an image as soon as possible using the "<em>eager</em>" value instead.', [':link' => 'https://caniuse.com/loading-lazy-attr']);
    

    I think Image should be lowercase as it is not a proper noun.

    Typically we don't put html () tags into translatable markup. Can we add them as variables for the call to t()?

  2. +++ b/core/modules/image/tests/src/Functional/ImageFieldDisplayTest.php
    @@ -116,6 +116,7 @@ public function _testImageFieldFormatters($scheme) {
    +    $image['#attributes']['loading'] = 'lazy';
    

    Can one of these test using eager to confirm that also is functional? We don't have any test coverage for that scenario.

kapilv’s picture

Assigned: Unassigned » kapilv
kapilv’s picture

Assigned: kapilv » Unassigned
anmolgoyal74’s picture

Status: Needs work » Needs review
StatusFileSize
new7 KB
new1.42 KB

Change Image to lowercase.
We have used html in the translatable markup in many other places.

Status: Needs review » Needs work

The last submitted patch, 37: 3173180-37.patch, failed testing. View results

edysmp’s picture

Status: Needs work » Needs review
StatusFileSize
new3.9 KB
new9.85 KB

Fixed some tests and added new ones to address #34.2.

Changing to Needs review for now to see how test are working.

Status: Needs review » Needs work

The last submitted patch, 39: 3173180-39.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

edysmp’s picture

Status: Needs work » Needs review
Issue tags: -Needs tests
StatusFileSize
new737 bytes
new10.57 KB

Fixing tests.

heddn’s picture

Status: Needs review » Needs work
Issue tags: +Needs screenshots

The last tests passed. Can we get screenshots added to the IS and we'll also need to add some upgrade testing.

edysmp’s picture

Issue summary: View changes
Issue tags: -Needs screenshots
StatusFileSize
new83.57 KB
new112.68 KB

Adding screenshots.

edysmp’s picture

Status: Needs work » Needs review
Issue tags: -Needs upgrade path
StatusFileSize
new2.61 KB
new13.19 KB

Upgrade path added.

edysmp’s picture

+++ b/core/modules/image/image.install
@@ -78,3 +78,24 @@ function image_requirements($phase) {
+function image_update_9001(&$sandbox) {

The correct schema version should be 9201 :)

ranjith_kumar_k_u’s picture

StatusFileSize
new434.35 KB

The #44 works fine ,
After patch
after patch
RTBC.

ranjith_kumar_k_u’s picture

Status: Needs review » Reviewed & tested by the community
edysmp’s picture

Wouldn't it make sense to move the new setting to the ImageFormatterBase class? Doing that https://www.drupal.org/project/drupal/issues/3173231 could also use it and we don't have code duplication.
Currently ImageFormatterBase is exteded only by ImageFormatter and ResponsiveImageFormatter classes, so both will share the same setting.

I am also thinking on mixing both issues into this one, because they are very related.

Thoughts?

chrisfree’s picture

+1 to combining these closely related issues.

edysmp’s picture

Assigned: Unassigned » edysmp
Status: Reviewed & tested by the community » Needs work

Working on a patch.

edysmp’s picture

Status: Needs work » Needs review
StatusFileSize
new16.13 KB
new19.66 KB
new126.58 KB
new110.6 KB
new105.98 KB

Added patch implementing #48. Moving settings to base class and mixing with 3173231 issue.

https://www.drupal.org/project/drupal/issues/3173231#comment-13876634 was addressed here.

edysmp’s picture

Assigned: edysmp » Unassigned

Patch added.

Status: Needs review » Needs work

The last submitted patch, 51: 3173180-51.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

heddn’s picture

Some nits, some substantive comments.

  1. +++ b/core/modules/image/config/schema/image.schema.yml
    @@ -142,6 +142,13 @@ field.formatter.settings.image:
    +    lazy_loading_settings:
    +      type: mapping
    +      label: 'Lazy loading settings'
    +      mapping:
    +        lazy_loading_priority:
    
    +++ b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatter.php
    @@ -218,6 +220,9 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
    +      $lazy_loading_settings = $this->getSetting('lazy_loading_settings');
    

    While using these settings, it shows that we don't really need the nested settings. Let's just put the priority directly at the same level as the rest of the config. So we can just call:

    $item_attributes['loading'] = $this->getSetting('image_loading');

    We might not load as lazy (that is one of the options). Naming is always hard. But what about image_loading?

  2. +++ b/core/modules/image/image.install
    @@ -78,3 +78,24 @@ function image_requirements($phase) {
    +function image_update_9101(&$sandbox) {
    

    Per https://www.drupal.org/project/drupal/issues/3087479#comment-13307977, point #3, this seems to indicate this should be a post_update instead of a hook_update_N.

  3. +++ b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageFormatterBase.php
    --- a/core/modules/image/src/Plugin/Field/FieldFormatter/ImageUrlFormatter.php
    +++ b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageUrlFormatter.php
    
    +++ b/core/modules/image/src/Plugin/Field/FieldFormatter/ImageUrlFormatter.php
    @@ -35,6 +35,7 @@ public function settingsForm(array $form, FormStateInterface $form_state) {
         unset($element['image_link']);
    +    unset($element['lazy_loading_settings']);
    

    I think a trait might be cleaner. So we don't have to unset things. Adding a trait would be pretty easy to implement in those places that need it.

  4. +++ b/core/modules/responsive_image/config/schema/responsive_image.schema.yml
    @@ -67,3 +67,16 @@ field.formatter.settings.responsive_image:
    +    lazy_loading_settings:
    +      type: mapping
    +      label: 'Lazy loading settings'
    +      mapping:
    +        lazy_loading_priority:
    +          type: string
    +          label: 'Lazy loading priority'
    +        lazy_loading_image_width:
    +          type: integer
    +          label: 'Image default width'
    +        lazy_loading_image_height:
    +          type: integer
    +          label: 'Image default height'
    

    Now, it makes sense why we have a nested config option. So ignore my earlier comments about making this more flat.

    But naming... let's remove all the lazy prefixes.
    Example:

    image_loading:
      priority: lazy
      image_width: 100
      image_height: 100
    
  5. +++ b/core/modules/responsive_image/src/Plugin/Field/FieldFormatter/ResponsiveImageFormatter.php
    @@ -118,6 +118,11 @@ public static function defaultSettings() {
    +        'lazy_loading_priority' => 'auto',
    +        'lazy_loading_image_width' => '',
    +        'lazy_loading_image_height' => '',
    

    We can't set a string value of '' if our schema says these values are integers. We'd need to default to some size. Maybe 100 x 100 is a good default to set non string values. A default of 0x0 doesn't make sense.

    see:

            lazy_loading_image_height:
              type: integer
              label: 'Image default height'
    
  6. +++ b/core/modules/responsive_image/src/Plugin/Field/FieldFormatter/ResponsiveImageFormatter.php
    @@ -236,6 +269,22 @@ public function viewElements(FieldItemListInterface $items, $langcode) {
    +      if ($lazy_loading_settings['lazy_loading_priority'] == 'lazy') {
    

    ===

edysmp’s picture

Assigned: Unassigned » edysmp

Working on #54

edysmp’s picture

Resolved #54 1, 4, 5, and 6 points plus some responsive images test fixes.

Still needs work for 2 and 3.

edysmp’s picture

Cleaning displayed files.

edysmp’s picture

Assigned: edysmp » Unassigned
Status: Needs work » Needs review

Let tests to run.

heddn’s picture

Status: Needs review » Needs work

NW for latest feedback.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

yivanov’s picture

The patch from #51 is no longer applicable on Core 9.1.10

d.fisher’s picture

The patch from #51 is no longer applying for me either on Core 9.1.10

dww’s picture

@yivanov + @darren.fisher - that's because development on this issue moved to gitlab. See https://git.drupalcode.org/project/drupal/-/merge_requests/43 for the latest code. You can no longer simply apply this fix as a patch on your site.

You can get an auto-generated patch for the latest changes here:

https://git.drupalcode.org/project/drupal/-/merge_requests/43.diff

However, it's a really bad idea to add that to composer for your site, since any random person can push more changes to that merge request and the next time you run composer install, the 43.diff file will be different and contain the new code. So you'll end up always running whatever code has been pushed to this issue fork, which is definitely unwise. There's no way to get a link to a gitlab patch locked to a specific commit hash you want to install. Welcome to the "new improved" contribution workflow using Gitlab! ;)

If you want to safely deal with this, you'll want to download that "43.diff" file, rename it to something unique like $issue_num.$hash.patch (e.g. '3173180.cd2041ff2a7c7be97059a4d77afa71eddd9f1e9f.patch'). Commit *that* to your site's source code somewhere "safe", and tell composer to install the patch from this local file, not from a link.

Good luck,
-Derek

Phil Wolstenholme’s picture

Here's a good read on why this is important:

The performance effects of too much lazy-loading by @rick_viscomi and @felixarntz https://web.dev/lcp-lazy-loading/

The article focuses on WordPress but the same would apply to Drupal. Lazyloading images that appear in the viewport saves on bytes but negatively affects the page's LCP score when things like hero images are lazyloaded instead of eagerly loaded.

3li’s picture

StatusFileSize
new19.77 KB

Patch in #51 and version in merge request was not applying to 9.2.4.
Re-rolled to apply.

chrisfree’s picture

Is the merge request's outstanding discussions the last bits to get this across the finish line? My team might have some time available to finish this up if so.

drupaldope’s picture

As I'm currently working on a high-performance Drupal site, I would like to add my 2 cents here:

- all images need dimensions to avoid CLS
- most images should use the "responsive image" formatter
- a responsive image needs dimensions to avoid CLS
- the module https://www.drupal.org/project/css_aspect_ratio provides dimensions for responsive images. Using this module I achieved a score of 0 CLS, that's why I think this should be integrated into core (this aspect_ratio option is missing from the responsive image formatter in views when rendering fields)
- all images need the lazy attribute as an option, obviously, there are some images for which lazy-loading is not adequate. I think this should be integrated into the image formatters, normal image formatters and responsive image formatters

- an additional need is to have lazy-loading responsive images as backgrounds.

heddn’s picture

OK, I finally got a MR opened against 9.3 that isn't needing a rebase. Sorry for all the noise. Now on to responding to feedback and figuring out what is blocking this issue.

heddn’s picture

Status: Needs work » Needs review

OK, with that last push, the last tests should be passing now. And this is ready for review again. I think it has full test coverage, so we just need a few folks to review the code and the UI of the formatter options. If we can get that all wrapped up, we might still be able to slot this into 9.3.0 before the alpha code freeze the week of October 25th.

nod_’s picture

Status: Needs review » Needs work

Url to caniuse seems brittle to add in a help test, and i'm not convinced at the default being lazy.

heddn’s picture

Status: Needs work » Needs review

I'll adjust the caniuse.com references.

The default of lazy is already the default as of 9.2. This issue makes it configurable so you can change it to eager. Any other feedback?

drupaldope’s picture

the issue of image dimensions for responsive images.
see https://www.drupal.org/project/drupal/issues/3173180#comment-14204256
we could use aspect ratio.

heddn’s picture

re #74:

https://www.smashingmagazine.com/2020/03/setting-height-width-images-imp... has an interesting write-up on what I think you are referring to with aspect ratio.

Given the info I gleaned from that article, I think leaving aspect ratio container work around in contrib is better for now. There still might be a day when we need to use it in core, but the momentum I'm seeing it to natively support all of this via setting dimensions on the img or source html elements. For responsive image, see the latest patches in #3192234: Apply width and height attributes to allow responsive image tag use loading="lazy".

heddn’s picture

Responded to feedback and post some changes.

heddn’s picture

Status: Needs review » Reviewed & tested by the community

Discussed w/ Fabian and he intended to mark this RTBC.

dww’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs reroll

MR isn't mergeable:

Merge blocked: fast-forward merge is not possible. To merge this request, first rebase locally.

So someone needs to rebase before this is RTBC. Tagging for "Needs reroll" although maybe we should introduce a "Needs rebase" instead. 🤓😉

heddn’s picture

Status: Needs work » Reviewed & tested by the community
Issue tags: -Needs reroll

Straightforward rebase. Back to RTBC. If the tests fail, it will automatically move it over to NW.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

mattjones86’s picture

It's a bit disappointing that this MR won't support Responsive Images at all, but I understand that it's easier to get smaller commits merged on a fast moving project.

Hoping this one get's merged soon so we can take a look at Responsive Images support too!

heddn’s picture

quietone’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs reroll

I wasn't able to apply the MR, tagging for reroll

heddn’s picture

Status: Needs work » Needs review

This gets it to no conflicts on 9.3. Next up, we'll see if we can rebase it to support 9.4.x

heddn’s picture

OK, rebased on 9.4 now. I kept track of all the moving bits and I don't think we lost anything.

heddn’s picture

Issue tags: -Needs reroll
fabianx’s picture

Status: Needs review » Reviewed & tested by the community

RTBC - This patch looks great to me now.

anybody’s picture

Great work! Thank you all! Will be nice to have this in 9.4.x! :)

giorgosk’s picture

StatusFileSize
new34.87 KB

For those that want to use this patch for Drupal 9.2 here is a backported one

giorgosk’s picture

StatusFileSize
new34.84 KB

Small update

kiwimind’s picture

StatusFileSize
new33.55 KB

Having tried to pull this apart for 9.3.x, I believe that the attached patch should apply.

heddn’s picture

Status: Reviewed & tested by the community » Needs work

NW to convert the post update to ConfigEntityUpdater. Not super hard to do. See how #3267870: Order image mappings by breakpoint ID and numeric multiplier does it (although the logic here is much easier can be copy/pasted from the existing post_update.

heddn’s picture

Status: Needs work » Needs review

Addressed my own feedback. Back to NR.

fabianx’s picture

Status: Needs review » Reviewed & tested by the community

Back to RTBC - interdiff looks good to me

quietone’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs issue summary update, +Usability, +Needs change record

Thanks everyone for adding this feature!

Doing a review.

I am not familiar with the display for managing images and it is not clear if the images in the Issue Summary are before or after screenshots. I checked on a local test site and now know they are after screenshots. I applied the patch and what I see on my test site does not match the screenshots in the Issue Summary. This needs up-to-date before and after screenshots in the Issue Summary. Adding tag

This is adding help text for the user and I do not see a review of that text from the usability team, adding tag.

Since this is changing the UI, adding a new feature, let's have a change record.

I took a very brief look at the MR and asked a question.

rkoller’s picture

StatusFileSize
new51.05 KB
new91.62 KB

Amazing addition, thank you for working on that! I've applied the patch and also taken a look. Two quick observations:

1. If you are inside the format settings and you expand the Image loading settings then they are not only expanded vertically but also horizontally to an unexpected degree. Since it happened already on a smaller viewport I've expanded the browser window to a unusual degree to test if it is happening there as well and also used that for the screenshot - I would have expected only a vertical expansion. (Tested with Safari 13.1.2 and 98.0.2 with identical results)

collapsed image loading settings in image field formatter
expanded image loading settings in image field formatter

2. The settings summary for the image field formatter in an article content type in a standard Drupal 9.4.x-dev installation states:

Image style: Wide (1090)
Loading attribute: lazy

But the titles inside the field formatter are: Image Style, Link image to and Image loading. Would it make sense to make the titles consistent and use Image loading instead of loading attribute in the summary or the other way around?

p.s. should the issue be added to next weeks ux meeting issue as @quietone asked for a review by the usability team?

andypost’s picture

Wording "image handling" needs discussion as contrib can extend it - for example module focal-point

heddn’s picture

Issue summary: View changes
StatusFileSize
new49.14 KB
new12.98 KB
heddn’s picture

Status: Needs work » Needs review
Issue tags: -Needs issue summary update, -Needs change record

I've asked for inclusion in the next UX meeting. And I've updated the IS and screenshots. And added a CR. Cleaning-up issue tags.

heddn’s picture

Issue summary: View changes
Status: Needs review » Needs work
Issue tags: -Usability
StatusFileSize
new64.72 KB

This issue was a topic of usability meeting on April 1, 2022. The general consensus was that the description was wordy and switching to radios would help understanding.

heddn’s picture

Issue summary: View changes
Status: Needs work » Needs review
StatusFileSize
new33.57 KB

updated the IS with a new screenshot of the new wording.

nod_’s picture

We'll be able to expand that to manage the decoding attribute too.

nod_’s picture

not comfortable RTBC but it's looking good to me.

Got one question, were there talk about not adding the attribute at all in case it's configured as eager? it seems like an unnecessary output change since it doesn't do anything different.

prudloff’s picture

eager does not mean the same thing as not having the attribute.
When the attribute is not there, the browser can use heuristics to determine if it should use lazy loading or not, whereas the attribute forces a specific mode.

nod_’s picture

right, so we're missing an option of not defining the attribute then?

specs says default = eager though The attribute's missing value default and invalid value default are both the Eager state. so it's not supposed to be up to the browser.

prudloff’s picture

My bad, what I said applies to the decoding attribute (decoding="sync" is not the same as not having the attribute).
But for the loading attribute, the spec says:

The attribute's missing value default and invalid value default are both the Eager state.

heddn’s picture

I don't think we considered not adding eager. But based on the dialog above, it doesn't sound like it really matters that much.

antoniya’s picture

Thanks again for all the work on this nice feature!

It also wouldn't matter much to me whether we set loading="eager" or omit the loading attribute when 'eager' is selected. If we choose the latter tho, I'd recommend expanding the description of the 'eager' option by saying (in as few words as possible) that it is the default browser behavior. I only mention this because unexperienced users might be surprised that no loading attribute is set and perceive this as a bug, when in fact it would be the desired behavior.

catch’s picture

Status: Needs review » Needs work
Issue tags: +Needs reroll

Agreed with #108, I almost wonder if we should say 'Default' instead of 'eager' in the UI, so it's just 'default' vs. 'lazy' (but keep it eager in config just in case a third one shows up one day).

The MR needs updating for the new database dumps in 10.x/9.4.x (and removal of the old ones in 10.x). Couple of other minor questions, but in general this looks good to me. We originally thought we didn't need a UI for this, but google pagespeed disagrees.

rkoller’s picture

I am not sure if changing the wording from eager to default wouldnt be too confusing for the user of any experience level. what eager and lazy means is defined in the description for each radio button for less experienced users, in contrast for more experienced users eager and lazy are prevalent terms.
by changing from eager to default experience users would have to think what is meant by default? and there is another problem in the introductory line it is stated image assets are rendered with native browser loading attribute of (loading="lazy") by default in the learn more-link in contrast within the spec eager is defined as the default state. so leaving the terms eager and lazy as is would be the safer and more clear in combination with the description text of the radio buttons we currently have.
and i agree with @antoniya in #108 as well. in case for eager there isn't a need to set an attribute then that should be noted in the description for the radio button.

nod_’s picture

I mean we can also put a checkbox that says "lazy" with the description of what it does and just not mention the default/eager thing. In that case it would make sense to not add the attribute if the checkbox is not checked and that reduce the amount of text to read to understand what it means.

heddn’s picture

Issue summary: View changes
Status: Needs work » Needs review
StatusFileSize
new41.58 KB

This responds to all the recent feedback, except it keeps the radios for greater clarity. The options of eager and lazy are fully explained this way. Updated screenshot added to IS.

fabianx’s picture

Status: Needs review » Reviewed & tested by the community

Back to RTBC - that UI looks super clean!

catch’s picture

Status: Reviewed & tested by the community » Needs work

Changes look great, I think having the radios to keep things explicit is fine.

Went to commit this, but it's failing phpstan:

Running PHPStan on changed files.
 ------ --------------------------------------------------------------------- 
  Line   modules/views/views.post_update.php                                  
 ------ --------------------------------------------------------------------- 
  51     Class ViewsConfigUpdater not found.                                  
         💡 Learn more at https://phpstan.org/user-guide/discovering-symbols  
  52     Class ConfigEntityUpdater not found.                                 
         💡 Learn more at https://phpstan.org/user-guide/discovering-symbols  
  52     Parameter $view of anonymous function has invalid type               
         ViewEntityInterface.                                                 
 ------ --------------------------------------------------------------------- 

Pretty sure this is because views.post_update.php has had some use statements removed since updates were removed from Drupal 10.

We also need a 10.x test run here - will probably need a separate MR/patch with the use statement changes.

heddn’s picture

Status: Needs work » Reviewed & tested by the community

Assuming tests pass, this can go back to RTBC. I added a d10 MR version that includes the imports for the views post updates.

  • catch committed 9f17ffb on 10.0.x
    Issue #3173180 by heddn, edysmp, chrisfree, andypost, markdorison,...
  • catch committed 9de5949 on 9.4.x
    Issue #3173180 by heddn, edysmp, chrisfree, andypost, markdorison,...
catch’s picture

Status: Reviewed & tested by the community » Fixed

Committed/pushed to 10.0.x and cherry-picked to 9.4.x, thanks!

heddn’s picture

We should also add to the release notes?

catch’s picture

Issue summary: View changes

Added a release notes snippet.

  • larowlan committed 96728e5 on 9.4.x
    Revert "Issue #3173180 by heddn, edysmp, chrisfree, andypost,...
larowlan’s picture

Status: Fixed » Needs work

This broke testing on PHP 7.3, so I've rolled it back from 9.4.x (only).

xjm made their first commit to this issue’s fork.

xjm’s picture

Without looking into it too closely, best guess is there is a typehint conflict on PHP 7.3 somewhere in here:

function image_post_update_image_loading_attribute(array &$sandbox = NULL): void {
  $image_config_updater = \Drupal::classResolver(ImageConfigUpdater::class);
  \Drupal::classResolver(ConfigEntityUpdater::class)->update($sandbox, 'entity_view_display', function (EntityViewDisplayInterface $view_display) use ($image_config_updater): bool {
    return $image_config_updater->processImageLazyLoad($view_display);
  });
}

Easiest thing to do would be to either manually test the upgrade path on 7.3, or run the failing upgrade path test on 7.3 and look at the HTML results.

larowlan’s picture

Yeah void is 7.4 + I think

heddn’s picture

Status: Needs work » Needs review

The failures are a re-surfacing of #3145563: Route serialization incompatibilities between PHP 7.4 and 7.3 (9.x only). The 9.3 DB fixture doesn't currently have tests executing against it (yet). @catch will file a follow-up to fix the fixture. For now, switching back to 8.8 fixture to get tests passing green again.

catch’s picture

  • catch committed f689c80 on 9.4.x
    Issue #3173180 by heddn, edysmp, chrisfree, andypost, markdorison,...
catch’s picture

Status: Needs review » Fixed

Since this ended up being just switching to the 8.8 fixture (which doesn't hurt anyway to update more things at once as part of the test), I've gone ahead and re-committed the new patch to 9.4.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

xjm’s picture

Is there a disruption from this issue such that it needs to be in the release notes, or is it meant to be a release highlight as a new feature?

heddn’s picture

It doesn't feel like a disruptive thing for the release notes but rather a highlight.

catch’s picture

Issue tags: -9.4.0 release notes +9.4.0 highlights

Agreed, more of a highlight.

quietone’s picture

Issue tags: -9.4.0 highlights +9.4.0 release highlights

Updating tag

g-brodiei’s picture

This change influences the formatter "URL to image", and causes PHP warning.
Please see #3291622: Formatter 'URL to image' from ImageUrlFormatter shows PHP warning due to the newly introduced loading attribute on image field, thank you.

anybody’s picture

Getting content.thumbnail.settings.image_loading missing schema, in detail:

Drupal\Core\Config\Schema\SchemaIncompleteException: Schema errors for core.entity_view_display.media.document.media_library with the following errors: core.entity_view_display.media.document.media_library:content.thumbnail.settings.image_loading missing schema

in https://www.drupal.org/pift-ci-job/2476536

for tests using the "standard" installation profile (to install core default media entity types).

The content section of
core/profiles/standard/config/optional/core.entity_view_display.media.document.media_library.yml looks like this and should match the schema:

content:
  thumbnail:
    type: image
    label: hidden
    settings:
      image_style: thumbnail
      image_link: ''
      image_loading:
        attribute: lazy
    third_party_settings: {  }
    weight: 0
    region: content

So I'm unsure if this is a follow-up issue with the "standard" installation profile after this change. If anyone has the same issue or can see what's causing this, we should create a separate issue for that. Not yet 100% sure the isn't on our side.

Thought I'd better leave a note here, for anyone experiencing the same.

s1933’s picture

StatusFileSize
new32.91 KB

Patch update for D9.3 compatibility

s1933’s picture

StatusFileSize
new32.89 KB

Update patch 9.3.22

s1933’s picture

StatusFileSize
new32.86 KB

Sorry, some fixes. Update patch 9.3.22