From b964a4ce1df41539426b1f7b1912cb06d38c56d8 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Mon, 13 May 2024 23:10:18 +0100 Subject: [PATCH] fix: improve ff handling of lazy images (#11593) * fix: improve ff handling of lazy images * tune * tune * tune * tune * tune --- .changeset/plenty-zoos-fix.md | 5 ++++ .../3-transform/client/visitors/template.js | 26 +++++++++---------- .../client/dom/elements/attributes.js | 20 ++++++++++++++ .../src/internal/client/dom/hydration.js | 2 +- packages/svelte/src/internal/client/index.js | 3 ++- 5 files changed, 41 insertions(+), 15 deletions(-) create mode 100644 .changeset/plenty-zoos-fix.md diff --git a/.changeset/plenty-zoos-fix.md b/.changeset/plenty-zoos-fix.md new file mode 100644 index 0000000000..538ef2d36e --- /dev/null +++ b/.changeset/plenty-zoos-fix.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: improve handling of lazy image elements diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js index 082a6c4dee..8db98930a8 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js @@ -1888,30 +1888,22 @@ export const template_visitors = { let needs_special_value_handling = node.name === 'option' || node.name === 'select'; let is_content_editable = false; let has_content_editable_binding = false; + let img_might_be_lazy = false; - if ( + if (is_custom_element) { // cloneNode is faster, but it does not instantiate the underlying class of the // custom element until the template is connected to the dom, which would // cause problems when setting properties on the custom element. // Therefore we need to use importNode instead, which doesn't have this caveat. - is_custom_element || - // If we have an occurance, we need to use importNode for FF - // otherwise, the image won't be lazy. If we detect an attribute for "loading" then - // just fallback to using importNode. Also if we have a spread attribute on the img, - // then it might contain this property, so we also need to fallback there too. - (node.name === 'img' && - node.attributes.some( - (attribute) => - attribute.type === 'SpreadAttribute' || - (attribute.type === 'Attribute' && attribute.name === 'loading') - )) - ) { metadata.context.template_needs_import_node = true; } for (const attribute of node.attributes) { if (attribute.type === 'Attribute') { attributes.push(attribute); + if (node.name === 'img' && attribute.name === 'loading') { + img_might_be_lazy = true; + } if ( (attribute.name === 'value' || attribute.name === 'checked') && !is_text_attribute(attribute) @@ -1988,6 +1980,9 @@ export const template_visitors = { // Then do attributes let is_attributes_reactive = false; if (node.metadata.has_spread) { + if (node.name === 'img') { + img_might_be_lazy = true; + } serialize_element_spread_attributes( attributes, context, @@ -2039,6 +2034,11 @@ export const template_visitors = { } } + // Apply the src and loading attributes for elements after the element is appended to the document + if (img_might_be_lazy) { + context.state.after_update.push(b.stmt(b.call('$.handle_lazy_img', node_id))); + } + // class/style directives must be applied last since they could override class/style attributes serialize_class_directives(class_directives, node_id, context, is_attributes_reactive); serialize_style_directives(style_directives, node_id, context, is_attributes_reactive); diff --git a/packages/svelte/src/internal/client/dom/elements/attributes.js b/packages/svelte/src/internal/client/dom/elements/attributes.js index d7ad037eb9..abd1f1640a 100644 --- a/packages/svelte/src/internal/client/dom/elements/attributes.js +++ b/packages/svelte/src/internal/client/dom/elements/attributes.js @@ -333,3 +333,23 @@ function srcset_url_equal(element, srcset) { ) ); } + +/** + * @param {HTMLImageElement} element + * @returns {void} + */ +export function handle_lazy_img(element) { + // If we're using an image that has a lazy loading attribute, we need to apply + // the loading and src after the img element has been appended to the document. + // Otherwise the lazy behaviour will not work due to our cloneNode heuristic for + // templates. + if (!hydrating && element.loading === 'lazy') { + var src = element.src; + element.removeAttribute('loading'); + element.removeAttribute('src'); + requestAnimationFrame(() => { + element.loading = 'lazy'; + element.src = src; + }); + } +} diff --git a/packages/svelte/src/internal/client/dom/hydration.js b/packages/svelte/src/internal/client/dom/hydration.js index 5831baba0e..1afbbcf874 100644 --- a/packages/svelte/src/internal/client/dom/hydration.js +++ b/packages/svelte/src/internal/client/dom/hydration.js @@ -40,7 +40,7 @@ export function hydrate_anchor(node) { var current = /** @type {Node | null} */ (node); // TODO this could have false positives, if a user comment consisted of `[`. need to tighten that up - if (/** @type {Comment} */ (current)?.data !== HYDRATION_START) { + if (/** @type {Comment} */ (current).data !== HYDRATION_START) { return node; } diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index b42de9b68c..33eb0c712b 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -26,7 +26,8 @@ export { set_attributes, set_custom_element_data, set_dynamic_element_attributes, - set_xlink_attribute + set_xlink_attribute, + handle_lazy_img } from './dom/elements/attributes.js'; export { set_class, set_svg_class, set_mathml_class, toggle_class } from './dom/elements/class.js'; export { event, delegate } from './dom/elements/events.js';