Lazy-load place photos
Only preload photos in view as well as the next one(s), not all of them
This commit is contained in:
@@ -123,11 +123,11 @@ export default class PlacePhotosCarousel extends Component {
|
||||
{{#if photo.thumbUrl}}
|
||||
<source
|
||||
media="(max-width: 768px)"
|
||||
srcset={{photo.thumbUrl}}
|
||||
data-srcset={{photo.thumbUrl}}
|
||||
/>
|
||||
{{/if}}
|
||||
<img
|
||||
src={{photo.url}}
|
||||
data-src={{photo.url}}
|
||||
class="place-header-photo landscape"
|
||||
alt={{@name}}
|
||||
{{fadeInImage photo.url}}
|
||||
@@ -136,7 +136,7 @@ export default class PlacePhotosCarousel extends Component {
|
||||
{{else}}
|
||||
{{! Portrait uses thumb everywhere if available }}
|
||||
<img
|
||||
src={{if photo.thumbUrl photo.thumbUrl photo.url}}
|
||||
data-src={{if photo.thumbUrl photo.thumbUrl photo.url}}
|
||||
class="place-header-photo portrait"
|
||||
alt={{@name}}
|
||||
{{fadeInImage (if photo.thumbUrl photo.thumbUrl photo.url)}}
|
||||
|
||||
@@ -7,24 +7,69 @@ export default modifier((element, [url]) => {
|
||||
element.classList.remove('loaded');
|
||||
element.classList.remove('loaded-instant');
|
||||
|
||||
// Create an off-DOM image to reliably check cache status
|
||||
// without waiting for the actual DOM element to load it
|
||||
const img = new Image();
|
||||
img.src = url;
|
||||
|
||||
if (img.complete) {
|
||||
// Already in browser cache, skip the animation
|
||||
element.classList.add('loaded-instant');
|
||||
return;
|
||||
}
|
||||
let observer;
|
||||
|
||||
const handleLoad = () => {
|
||||
element.classList.add('loaded');
|
||||
// Only apply the fade-in animation if it wasn't already loaded instantly
|
||||
if (!element.classList.contains('loaded-instant')) {
|
||||
element.classList.add('loaded');
|
||||
}
|
||||
};
|
||||
|
||||
element.addEventListener('load', handleLoad);
|
||||
|
||||
const loadWhenVisible = (entries, obs) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
// Stop observing once we start loading
|
||||
obs.unobserve(element);
|
||||
|
||||
// Check if the image is already in the browser cache
|
||||
// Create an off-DOM image to reliably check cache status
|
||||
// without waiting for the actual DOM element to load it
|
||||
const img = new Image();
|
||||
img.src = url;
|
||||
|
||||
if (img.complete) {
|
||||
// Already in browser cache, skip the animation
|
||||
element.classList.add('loaded-instant');
|
||||
}
|
||||
|
||||
// If this image is inside a <picture> tag, we also need to swap <source> tags
|
||||
const parent = element.parentElement;
|
||||
if (parent && parent.tagName === 'PICTURE') {
|
||||
const sources = parent.querySelectorAll('source');
|
||||
sources.forEach((source) => {
|
||||
if (source.dataset.srcset) {
|
||||
source.srcset = source.dataset.srcset;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Swap data-src to src to trigger the actual network fetch (or render from cache)
|
||||
if (element.dataset.src) {
|
||||
element.src = element.dataset.src;
|
||||
} else {
|
||||
// Fallback if data-src wasn't used but the modifier was called
|
||||
element.src = url;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Setup Intersection Observer to only load when the image enters the viewport
|
||||
observer = new IntersectionObserver(loadWhenVisible, {
|
||||
root: null, // Use the viewport as the root
|
||||
rootMargin: '100px 100%', // Load one full viewport width ahead/behind
|
||||
threshold: 0, // Trigger immediately when any part enters the expanded margin
|
||||
});
|
||||
|
||||
observer.observe(element);
|
||||
|
||||
return () => {
|
||||
element.removeEventListener('load', handleLoad);
|
||||
if (observer) {
|
||||
observer.disconnect();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -43,7 +43,7 @@ module('Integration | Component | place-photos-carousel', function (hooks) {
|
||||
assert.dom('.carousel-slide').exists({ count: 1 }, 'it renders one slide');
|
||||
assert
|
||||
.dom('img.place-header-photo')
|
||||
.hasAttribute('src', 'photo1.jpg', 'it renders the photo');
|
||||
.hasAttribute('data-src', 'photo1.jpg', 'it sets the data-src correctly');
|
||||
|
||||
// There should be no chevrons when there's only 1 photo
|
||||
assert
|
||||
|
||||
Reference in New Issue
Block a user