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.
| Comment | File | Size | Author |
|---|---|---|---|
| #141 | 3173180-91-1287-D9.3-3.patch | 32.86 KB | s1933 |
| #112 | lazy-load-formatter.png | 41.58 KB | heddn |
Issue fork drupal-3173180
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
Comment #2
chrisfree commentedBelow 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
My thinking centers around the fact that there are certain instances where setting the
loadingattribute to something other thanlazyis desirable. For example, if a page has an image which makes up the Largest Contentful Paint, itsloadingattribute should be set toloading="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
loadingattribute toeagerwould 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.Comment #3
adamzimmermann commentedThis 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.
Comment #4
edysmpHere 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.
Comment #5
andypostit should use
$this->t()as inside of classWould be great to add description with a link to docs about what each option means
Comment #6
edysmpAddressing #5 ;)
Comment #8
edysmpAdded new setting schema.
Comment #11
chrisfree commentedI 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, andlazy: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.
Comment #12
edysmpThanks 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.
Comment #13
chrisfree commentedOh 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.
Comment #14
markdorisonUpdated link as suggested in #12
Comment #16
chrisfree commentedThe whatwg.org/ spec is probably the right/better documentation page to link to. That said, it does not list
autoas one of the supported attributes. Sinceautoin Chrome is equivalent to theloadingattribute not existing, and since the spec defineseageras the both the missing value and invalid value default, it would probably make sense to removeautoas 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.
Comment #17
chrisfree commentedHere 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.
Comment #18
andypostnew config needs upgrade path and upgrade tests to be ready for existing sites
Links must use ":" for URL -
<a href=":link">Comment #19
chrisfree commentedHere's an updated patch and interdiff, fixing the link in the help text.
Comment #20
chrisfree commentedWhoops, I didn't use the proper syntax for URLs. This patch fixes that.
Comment #21
markdorisonComment #22
andypostPlease don't store immutable config in protected property, no reason to read config at plugin creation time and it makes serialization weird
Link still rendered with wrong selector, see #18
Comment #23
andypostFix #22 but leaving NW for #18.1
Comment #24
andypostbtw instead of global setting it could use "empty/none" option as default for formatter
Comment #25
chrisfree commentedThanks @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.
Comment #26
edysmp#16 and #24.
I also think
autodoesn't make too sense in the UI, maybe just keepinglazyandeagerand set the fallback option toauto.e.g:
Comment #27
heddnI 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.
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.
Comment #28
chrisfree commentedI 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.Comment #29
edysmpMoved changes into the
detailselement. Also applied #26.Comment #31
edysmpFixing Image.Image tests.
Comment #33
heddnComment #34
heddnNW for upgrade path and below nits.
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()?Can one of these test using eager to confirm that also is functional? We don't have any test coverage for that scenario.
Comment #35
kapilv commentedComment #36
kapilv commentedComment #37
anmolgoyal74 commentedChange Image to lowercase.
We have used html in the translatable markup in many other places.
Comment #39
edysmpFixed some tests and added new ones to address #34.2.
Changing to Needs review for now to see how test are working.
Comment #41
edysmpFixing tests.
Comment #42
heddnThe last tests passed. Can we get screenshots added to the IS and we'll also need to add some upgrade testing.
Comment #43
edysmpAdding screenshots.
Comment #44
edysmpUpgrade path added.
Comment #45
edysmpThe correct schema version should be 9201 :)
Comment #46
ranjith_kumar_k_u commentedThe #44 works fine ,

After patch
RTBC.
Comment #47
ranjith_kumar_k_u commentedComment #48
edysmpWouldn'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?
Comment #49
chrisfree commented+1 to combining these closely related issues.
Comment #50
edysmpWorking on a patch.
Comment #51
edysmpAdded 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.
Comment #52
edysmpPatch added.
Comment #54
heddnSome nits, some substantive comments.
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?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.
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.
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:
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:
===
Comment #55
edysmpWorking on #54
Comment #57
edysmpResolved #54 1, 4, 5, and 6 points plus some responsive images test fixes.
Still needs work for 2 and 3.
Comment #58
edysmpCleaning displayed files.
Comment #59
edysmpLet tests to run.
Comment #60
heddnNW for latest feedback.
Comment #62
yivanov commentedThe patch from #51 is no longer applicable on Core 9.1.10
Comment #63
d.fisher commentedThe patch from #51 is no longer applying for me either on Core 9.1.10
Comment #64
dww@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
Comment #65
Phil Wolstenholme commentedHere's a good read on why this is important:
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.
Comment #66
3liPatch in #51 and version in merge request was not applying to 9.2.4.
Re-rolled to apply.
Comment #67
chrisfree commentedIs 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.
Comment #68
drupaldope commentedAs 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.
Comment #70
heddnOK, 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.
Comment #71
heddnOK, 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.
Comment #72
nod_Url to caniuse seems brittle to add in a help test, and i'm not convinced at the default being lazy.
Comment #73
heddnI'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?
Comment #74
drupaldope commentedthe issue of image dimensions for responsive images.
see https://www.drupal.org/project/drupal/issues/3173180#comment-14204256
we could use aspect ratio.
Comment #75
heddnre #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".
Comment #76
heddnResponded to feedback and post some changes.
Comment #77
heddnDiscussed w/ Fabian and he intended to mark this RTBC.
Comment #78
dwwMR isn't mergeable:
So someone needs to rebase before this is RTBC. Tagging for "Needs reroll" although maybe we should introduce a "Needs rebase" instead. 🤓😉
Comment #79
heddnStraightforward rebase. Back to RTBC. If the tests fail, it will automatically move it over to NW.
Comment #81
mattjones86It'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!
Comment #82
heddnResponsive image is being worked on parallel in #3192234: Apply width and height attributes to allow responsive image tag use loading="lazy".
Comment #83
quietone commentedI wasn't able to apply the MR, tagging for reroll
Comment #84
heddnThis gets it to no conflicts on 9.3. Next up, we'll see if we can rebase it to support 9.4.x
Comment #85
heddnOK, rebased on 9.4 now. I kept track of all the moving bits and I don't think we lost anything.
Comment #86
heddnComment #87
fabianx commentedRTBC - This patch looks great to me now.
Comment #88
anybodyGreat work! Thank you all! Will be nice to have this in 9.4.x! :)
Comment #89
giorgoskFor those that want to use this patch for Drupal 9.2 here is a backported one
Comment #90
giorgoskSmall update
Comment #91
kiwimind commentedHaving tried to pull this apart for 9.3.x, I believe that the attached patch should apply.
Comment #92
heddnNW 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.
Comment #93
heddnAddressed my own feedback. Back to NR.
Comment #94
fabianx commentedBack to RTBC - interdiff looks good to me
Comment #95
quietone commentedThanks 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.
Comment #96
rkollerAmazing 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 loadingsettings 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)2. The settings summary for the image field formatter in an article content type in a standard Drupal 9.4.x-dev installation states:
But the titles inside the field formatter are:
Image Style,Link image toandImage loading. Would it make sense to make the titles consistent and useImage loadinginstead ofloading attributein 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?
Comment #97
andypostWording "image handling" needs discussion as contrib can extend it - for example module focal-point
Comment #98
heddnComment #99
heddnI'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.
Comment #100
heddnThis 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.
Comment #101
heddnupdated the IS with a new screenshot of the new wording.
Comment #102
nod_We'll be able to expand that to manage the decoding attribute too.
Comment #103
nod_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.
Comment #104
prudloff commentedeagerdoes 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.
Comment #105
nod_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.Comment #106
prudloff commentedMy bad, what I said applies to the
decodingattribute (decoding="sync"is not the same as not having the attribute).But for the
loadingattribute, the spec says:Comment #107
heddnI don't think we considered not adding eager. But based on the dialog above, it doesn't sound like it really matters that much.
Comment #108
antoniya commentedThanks 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.
Comment #109
catchAgreed 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.
Comment #110
rkollerI am not sure if changing the wording from
eagertodefaultwouldnt be too confusing for the user of any experience level. whateagerandlazymeans is defined in the description for each radio button for less experienced users, in contrast for more experienced userseagerandlazyare prevalent terms.by changing from
eagertodefaultexperience users would have to think what is meant by default? and there is another problem in the introductory line it is statedimage assets are rendered with native browser loading attribute of (loading="lazy") by defaultin thelearn more-link in contrast within the speceageris defined as the default state. so leaving the termseagerandlazyas 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.
Comment #111
nod_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.
Comment #112
heddnThis 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.
Comment #113
fabianx commentedBack to RTBC - that UI looks super clean!
Comment #114
catchChanges look great, I think having the radios to keep things explicit is fine.
Went to commit this, but it's failing phpstan:
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.
Comment #116
heddnAssuming tests pass, this can go back to RTBC. I added a d10 MR version that includes the imports for the views post updates.
Comment #118
catchCommitted/pushed to 10.0.x and cherry-picked to 9.4.x, thanks!
Comment #119
heddnWe should also add to the release notes?
Comment #120
catchAdded a release notes snippet.
Comment #122
larowlanThis broke testing on PHP 7.3, so I've rolled it back from 9.4.x (only).
Comment #126
xjmWithout looking into it too closely, best guess is there is a typehint conflict on PHP 7.3 somewhere in here:
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.
Comment #127
larowlanYeah void is 7.4 + I think
Comment #128
heddnThe 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.
Comment #129
catchI've opened #3275093: Drupal 9.3.0 dumps were created with PHP 8, needs to be PHP 7.3 to sort the dumps out.
Comment #131
catchSince 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.
Comment #133
xjmIs 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?
Comment #134
heddnIt doesn't feel like a disruptive thing for the release notes but rather a highlight.
Comment #135
catchAgreed, more of a highlight.
Comment #136
quietone commentedUpdating tag
Comment #137
g-brodieiThis 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.
Comment #138
anybodyGetting content.thumbnail.settings.image_loading missing schema, in detail:
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:
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.
Comment #139
s1933 commentedPatch update for D9.3 compatibility
Comment #140
s1933 commentedUpdate patch 9.3.22
Comment #141
s1933 commentedSorry, some fixes. Update patch 9.3.22