This project offers a simple solution to enhance the performance of web pages by optimizing their loading time. The proposed script automates the image loading process. Only when the user reaches the corresponding section on the page, the image is loaded.
- Place the script in your project's scripts folder.
- Add divs with the class "img" and an ID identical to the image name in your project's folder.
// For example, if you have an image named "sports_car".
// Then you should create a div wherever you want this image to appear, like this:
<div class="img" id="sports_car"></div>
This way, the script will automatically load the corresponding image when the user scrolls to the section where the image is located.
Below, I'll share a bit of the code's evolution:
function isSectionVisible(element) {
const { top, bottom, left, right } = element.getBoundingClientRect();
const windowHeight = window.innerHeight || document.documentElement.clientHeight;
const windowWidth = window.innerWidth || document.documentElement.clientWidth;
const vertInView = (top <= windowHeight) && (bottom >= 0);
const horInView = (left <= windowWidth) && (right >= 0);
return vertInView && horInView;
}
const bodyElement = document.getElementsByTagName('body')[0];
const documentSections = bodyElement.querySelectorAll('section');
let sectionsList = []
let imgsList = []
let sectionImgs = []
documentSections.forEach(section => {
sectionsList.push(section)
});
sectionsList.forEach(section => {
const sectionInfo = {
sectionTag: section,
sectionId: section.id,
images: []
};
imgNodes = section.getElementsByClassName('img');
for (var i = 0; i < imgNodes.length; i++) {
var img = imgNodes[i];
imgsList.push(img)
sectionInfo.images.push(img)
}
sectionImgs.push(sectionInfo);
})
function loadImages() {
sectionImgs.forEach(section => {
if (isSectionVisible(section.sectionTag)) {
section.images.forEach(img => {
const imgId = img.attributes.id.nodeValue;
const imgContainer = document.getElementById(imgId);
if (imgContainer) {
imgContainer.innerHTML =
`
<picture>
<source media="(min-width: 1200px)" srcset="./${imgId}.webp">
<source media="(min-width: 768px)" srcset="./${imgId}.webp">
<source media="(min-width: 320px)" srcset="./${imgId}.webp">
<img loading="lazy" src="./${imgId}.webp">
</picture>
`;
}
})
}
window.removeEventListener('scroll', loadImages);
})
}
window.addEventListener('scroll', loadImages);
function isSectionVisible(element) {
let { top, bottom, left, right } = element.getBoundingClientRect();
let windowHeight = window.innerHeight || document.documentElement.clientHeight;
let windowWidth = window.innerWidth || document.documentElement.clientWidth;
let vertInView = (top <= windowHeight) && (bottom >= 0);
let horInView = (left <= windowWidth) && (right >= 0);
return vertInView && horInView;
}
function getDocumentElements() {
let bodyElement = document.getElementsByTagName('body')[0];
let documentSections = bodyElement.querySelectorAll('section');
let sectionsList = [];
const sectionImgs = [];
documentSections.forEach(section => {
sectionsList.push(section);
});
sectionsList.forEach(section => {
const sectionInfoObject = {
sectionTag: section,
sectionId: section.id,
images: []
};
imgDiv = section.getElementsByClassName('img');
for (var i = 0; i < imgDiv.length; i++) {
let divImgId = imgDiv[i];
sectionInfoObject.images.push(divImgId);
};
sectionImgs.push(sectionInfoObject);
});
return sectionImgs;
}
function loadImages(sectionImgs) {
sectionImgs.forEach(section => {
if (isSectionVisible(section.sectionTag) && !section.sectionTag.classList.contains('processed')) {
section.images.forEach(img => {
let imgId = img.attributes.id.nodeValue;
let imgDivElement = document.getElementById(imgId);
if (imgDivElement) {
imgDivElement.innerHTML =
`
<picture>
<source media="(min-width: 1200px)" srcset="./${imgId}.webp">
<source media="(min-width: 768px)" srcset="./${imgId}.webp">
<source media="(min-width: 320px)" srcset="./${imgId}.webp">
<img loading="lazy" src="./${imgId}.webp">
</picture>
`;
}
});
section.sectionTag.classList.add('processed');
}
});
}
window.addEventListener('scroll', () => {
let imgsElement = getDocumentElements();
loadImages(imgsElement);
});
Now we can define the path of the images, const imagePath = './.../'
As well as whether it has more than one size, such as for mobile, desktop and tablet, or if it is just for desktop.
isImageResponsive = true or false
Furthermore, now the script checks automatically and without the need for the developer to define the image extension webp, png, jpg...
If you have images with different extensions, the script will find the correct one.
// Dev must change this vars.
const isImageResponsive = true;
const imagePath = './images/';
const imageExtension = ['webp', 'png', 'jpg', 'jpeg', 'svg'];
function generateImage(imgNameId, isImageResponsive, callback) {
let imgMatch = false;
for(var i = 0; i < imageExtension.length; i++) {
let ext = imageExtension[i];
const imgSource = `${imagePath}${imgNameId}.${ext}`;
fileExists(imgSource, function (exists) {
if (exists && !imgMatch) {
imgMatch = true;
let imgResult;
if (isImageResponsive) {
imgResult = `
<picture>
<source media="(min-width: 1200px)" srcset="${imgSource}">
<source media="(min-width: 768px)" srcset="${imgSource}">
<source media="(min-width: 320px)" srcset="${imgSource}">
<img loading="lazy" src="${imgSource}">
</picture>
`;
} else {
imgResult = `
<img loading="lazy" src="${imgSource}">
`;
}
callback(imgResult);
}
});
}
}
function fileExists(filePath, callback) {
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE) {
callback(xhr.status === 200);
}
};
xhr.open('HEAD', filePath, true);
xhr.send();
}
function isSectionVisible(element) {
let { top, bottom, left, right } = element.getBoundingClientRect();
let windowHeight = window.innerHeight || document.documentElement.clientHeight;
let windowWidth = window.innerWidth || document.documentElement.clientWidth;
let vertInView = (top <= windowHeight) && (bottom >= 0);
let horInView = (left <= windowWidth) && (right >= 0);
return vertInView && horInView;
}
function getDocumentElements() {
let bodyElement = document.getElementsByTagName('body')[0];
let documentSections = bodyElement.querySelectorAll('section');
let sectionsList = [];
const sectionImgs = [];
documentSections.forEach(section => {
sectionsList.push(section);
});
sectionsList.forEach(section => {
const sectionInfoObject = {
sectionTag: section,
sectionId: section.id,
images: []
};
imgDiv = section.getElementsByClassName('img');
for (var i = 0; i < imgDiv.length; i++) {
let divImgNameId = imgDiv[i];
sectionInfoObject.images.push(divImgNameId);
};
sectionImgs.push(sectionInfoObject);
});
return sectionImgs;
}
function loadImages(sectionImgs) {
sectionImgs.forEach(section => {
if (isSectionVisible(section.sectionTag) && !section.sectionTag.classList.contains('processed')) {
section.images.forEach(img => {
let imgNameId = img.attributes.id.nodeValue;
let imgDivElement = document.getElementById(imgNameId);
if (imgDivElement) {
generateImage(imgNameId, isImageResponsive, function (html) {
imgDivElement.innerHTML = html;
});
};
});
section.sectionTag.classList.add('processed');
}
});
}
window.addEventListener('scroll', () => {
let imgsElement = getDocumentElements();
loadImages(imgsElement);
});
We welcome contributions to improve and expand this project. Feel free to provide ideas, suggest code improvements, or report issues on the project's page.
The script can be executed on any system.
If you encounter any problems, please report them on the project's page. We will do our best to resolve them promptly.
This project is licensed under the AGPL-3.0 License.
For more information or clarification of doubts, visit my GitHub profile.
StackOverflow Selecting the body tag
Developer Mozilla Building arrays
StackOverflow Building objects
StackOverflow Iterating through child elements
OpenAI How ChatGPT Helped in Image Extension Discovery
For the code new implementation of today, there was a need to automatic discover the correct extension of image from a predefined list.
To achieve this, I was assisted by ChatGPT in creating a JavaScript function that uses asynchronous checks to determine the existence of the image file in the project folder.
The collaboration involved discussions on refining the code logic, introducing a callback mechanism, and addressing challenges in asynchronous file existence checks.
The resulting script enables developers to seamlessly integrate image loading with different extensions into their web projects, enhancing user experience.
For more details and examples, refer to the "Version 0.3" section in the README.md.