Skip to content

fix: improve ff handling of lazy images #11593

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/plenty-zoos-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"svelte": patch
---

fix: improve handling of lazy image elements
Original file line number Diff line number Diff line change
Expand Up @@ -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 <img loading="lazy"> 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)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -2039,6 +2034,11 @@ export const template_visitors = {
}
}

// Apply the src and loading attributes for <img> 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);
Expand Down
20 changes: 20 additions & 0 deletions packages/svelte/src/internal/client/dom/elements/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
});
}
}
2 changes: 1 addition & 1 deletion packages/svelte/src/internal/client/dom/hydration.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
3 changes: 2 additions & 1 deletion packages/svelte/src/internal/client/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
Loading