Javascript Image Onload Fails On Firefox And Chrome
I am trying to load my images with a fade in effect that will be triggered once the image is loaded. I noticed that onload
works properly in Chrome, but in Firefox or Safari it is called some hundred miliseconds before the image is rendered, so any animation below that duration will be skipped.
I' have been reading other questions, as:
image.onload event and browser cache: not useful, as it is not a cache issue, as the image is always a new one;
onload event in <img> tags firing prematurely in Firefox, not other browsers: in this question it is pointed out that Firefox had an issue with onload, that was being triggered on load start instead of on load end; but in our case
onload
is fired correctly on image load end; it is the image what is rendered some ms after load finished.
Here I created a snippet demonstrating the problem using Picsum heavy images —3000 x 3000—. When opening this question on Chrome and running this snippet, on load the image is faded; but when opening this same question on Firefox or Safari the image is rendered without fade in.
const imgElement = new Image();
const rootElement = document.getElementById("root");
rootElement.append(imgElement);
imgElement.className = "image";
imgElement.src = "https://picsum.photos/3000/3000";
imgElement.onload = function () {
imgElement.classList.add("loaded");
};
.image {
border: 2px solid gray;
max-width: 100%;
opacity: 0;
transition: opacity 0.3s ease;
}
.image.loaded {
opacity: 1;
}
<div id="root"></div>
Any idea will be welcome!
Answer
Yes, the load event does correspond to the time when the network resource has been fetched and when the browser has ensured it will be able to read it without issue.
That's when Firefox and Safari do fire the event, as per the specs.
Chrome currently does more work before firing this event, as they will wait until the full decoding is done.
So as you correctly guessed, Safari and Firefox will first render the <img>
element empty, apply the transition on that empty image and then only render the final image.
To overcome this issue you can use a relatively recent addition to the HTML specs: HTMLImageElement#decode(). This method will return a Promise, resolving when the decoding part has been completely done.
const imgElement = new Image();
const rootElement = document.getElementById("root");
rootElement.append(imgElement);
imgElement.className = "image";
imgElement.src = "https://picsum.photos/3000/3000";
imgElement.decode().then(()=>{
imgElement.classList.add("loaded");
});
.image {
border: 2px solid gray;
max-width: 100%;
opacity: 0;
transition: opacity 0.3s ease;
}
.image.loaded {
opacity: 1;
}
<div id="root"></div>
Ps: for the ones needing to support older browsers that didn't support decode()
yet, there is a way to force the decoding synchronously.
Related Questions
- → How to update data attribute on Ajax complete
- → October CMS - Radio Button Ajax Click Twice in a Row Causes Content to disappear
- → Octobercms Component Unique id (Twig & Javascript)
- → Passing a JS var from AJAX response to Twig
- → Laravel {!! Form::open() !!} doesn't work within AngularJS
- → DropzoneJS & Laravel - Output form validation errors
- → Import statement and Babel
- → Uncaught TypeError: Cannot read property '__SECRET_DOM_DO_NOT_USE_OR_YOU_WILL_BE_FIRED' of undefined
- → React-router: Passing props to children
- → ListView.DataSource looping data for React Native
- → Can't test submit handler in React component
- → React + Flux - How to avoid global variable
- → Webpack, React & Babel, not rendering DOM