Skip to content

Add native loading=lazy support for video elements#2450

Open
adamsilverstein wants to merge 5 commits into
trunkfrom
add-native-video-support
Open

Add native loading=lazy support for video elements#2450
adamsilverstein wants to merge 5 commits into
trunkfrom
add-native-video-support

Conversation

@adamsilverstein
Copy link
Copy Markdown
Member

@adamsilverstein adamsilverstein commented Apr 9, 2026

Summary

Closes #2400.

  • Adds loading="lazy" as a progressive enhancement for <video> elements that are below the fold (intersectionRatio = 0), alongside the existing JavaScript IntersectionObserver-based lazy loading
  • Removes any existing loading attribute from LCP videos and visible videos to prevent unintended lazy loading
  • Keeps the JS fallback (od-lazy-video class, data-original-* attributes, lazy-load-video.js) since browser support for loading="lazy" on video is still rolling out

This leverages the new HTML spec support for loading="lazy" on video elements (whatwg/html#10376). In unsupported browsers, the attribute is simply ignored (zero cost). Audio tag support is deferred to a separate PR.

Test plan

  • Verify existing snapshot tests pass with updated expected outputs
  • New test: hidden video gets loading="lazy" alongside JS lazy loading
  • New test: visible video with loading="lazy" has the attribute removed
  • New test: LCP video with loading="lazy" has the attribute removed
  • PHPStan passes at level 10

AI Usage

I used Claude code to write the code and tests for this PR which I reviewed. It should probably be manually tested as well, I have not done that yet. The PR is mostly test cases, the code change itself is minor.

Add loading="lazy" as a progressive enhancement for video elements that
are below the fold, alongside the existing JavaScript-based lazy loading.
For LCP and visible videos, remove any existing loading attribute to
prevent unintended lazy loading.

This leverages the new HTML spec support for loading="lazy" on video
elements (whatwg/html#10376) while keeping the JS IntersectionObserver
fallback for browsers that don't yet support it.
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 9, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 69.34%. Comparing base (097e644) to head (d8e6794).
⚠️ Report is 21 commits behind head on trunk.

Additional details and impacted files
@@            Coverage Diff             @@
##            trunk    #2450      +/-   ##
==========================================
+ Coverage   69.17%   69.34%   +0.17%     
==========================================
  Files          90       90              
  Lines        8243     7752     -491     
==========================================
- Hits         5702     5376     -326     
+ Misses       2541     2376     -165     
Flag Coverage Δ
multisite 69.34% <100.00%> (+0.17%) ⬆️
single 35.71% <0.00%> (+0.05%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@adamsilverstein adamsilverstein added the [Type] Feature A new feature within an existing module label Apr 10, 2026
@adamsilverstein adamsilverstein marked this pull request as ready for review April 10, 2026 21:10
@github-actions
Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: adamsilverstein <[email protected]>
Co-authored-by: westonruter <[email protected]>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@adamsilverstein adamsilverstein self-assigned this Apr 10, 2026
@adamsilverstein adamsilverstein added [Plugin] Enhanced Responsive Images Issues for the Enhanced Responsive Images plugin (formerly Auto Sizes) [Plugin] Image Prioritizer Issues for the Image Prioritizer plugin (dependent on Optimization Detective) and removed [Plugin] Enhanced Responsive Images Issues for the Enhanced Responsive Images plugin (formerly Auto Sizes) labels Apr 10, 2026
@adamsilverstein
Copy link
Copy Markdown
Member Author

I verified this worked as expected on a test page in my local. Non LCP video tags get the lazy loading attribute:

<figure class="wp-block-video"><video class="od-lazy-video" data-od-added-class data-od-added-data-original-preload data-od-added-loading data-od-added-preload data-od-xpath="/HTML/BODY/DIV[@id=&apos;page&apos;]/*[3][self::DIV]/*[1][self::DIV]/*[1][self::MAIN]/*[1][self::ARTICLE]/*[1][self::DIV]/*[12][self::FIGURE]/*[1][self::VIDEO]" data-original-preload="default" loading="lazy" preload="none" height="904" style="aspect-ratio: 1392 / 904;" width="1392" controls src="https://wpdev.localhost/wp-content/uploads/2026/04/inner-button-turns-to-save.mp4"></video></figure>

Comment thread plugins/image-prioritizer/class-image-prioritizer-video-tag-visitor.php Outdated
Comment thread plugins/image-prioritizer/class-image-prioritizer-video-tag-visitor.php Outdated
Comment thread plugins/image-prioritizer/class-image-prioritizer-video-tag-visitor.php Outdated
@westonruter
Copy link
Copy Markdown
Member

  • Keeps the JS fallback (od-lazy-video class, data-original-* attributes, lazy-load-video.js) since browser support for loading="lazy" on video is still rolling out

Should this check for native support for video lazy-loading and short-circuit when this is the case? The logic in the JS can short-circuit if 'loading' in document.createElement('video').

@westonruter
Copy link
Copy Markdown
Member

Should this check for native support for video lazy-loading and short-circuit when this is the case? The logic in the JS can short-circuit if 'loading' in document.createElement('video').

Actually, this is probably the better check:

'loading' in HTMLMediaElement.prototype

I just updated to Chrome 148 and confirm that this is true.

adamsilverstein and others added 2 commits May 11, 2026 08:50
…g is supported

When the browser supports `loading="lazy"` on video elements (detected via
`'loading' in HTMLMediaElement.prototype`), restore the original preload,
autoplay, and poster attributes immediately. The browser then defers the
video load itself via the `loading="lazy"` attribute, avoiding the need
for an IntersectionObserver-based fallback.

Addresses review feedback on PR #2450.
@adamsilverstein
Copy link
Copy Markdown
Member Author

Updated in e871d03 to short-circuit the IntersectionObserver when 'loading' in HTMLMediaElement.prototype. When native video lazy-loading is supported, the original preload, autoplay, and poster attributes are restored immediately and the browser handles the deferral via loading="lazy". Otherwise, the IntersectionObserver fallback runs as before.

The three inline suggestions from your review are already applied in 771be4f.

The test helper normalizes inline scripts by replacing them with a
`/* const <varname> ... */` placeholder taken from the first declared
identifier. After refactoring lazy-load-video.js to begin with
`const restoreVideo`, the expected snapshots need to match.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Plugin] Image Prioritizer Issues for the Image Prioritizer plugin (dependent on Optimization Detective) [Type] Feature A new feature within an existing module

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add native lazy loading support for HTML video and audio tags

2 participants