Image.onload callback is firing inconsistently - javascript

I'm creating a lazy load function and I want to append the image to the DOM after it has successfully loaded to create a smooth transition.
I have a function which loops through a list of elements with a specific class, which gradually gets smaller every time you scroll.
Once an image in that list is considered "visible" I add a class and it no longer get's evaluated by the function.
I get the SRC by a data attribute, and create a new Image();
I do some css prop manipulation and add the src after I add the onload function.
This works around 1/3 of the time, most images never fire the onload callback and aren't added to the DOM.
My script is as follows:
var lazyClass = 'js-lazyload';
function lazyLoader() {
//Sets an elements var that checks how many non-visible elements still exist
// This variable is reset every time lazyLoader() is called
var elements = document.querySelectorAll('.' + lazyClass + ':not(.js-lazyload--visible)');
//If there are no hidden elements left, end the function we don't need to proceed.
if (elements.length === 0) return;
//Loop through the elements array
//Only untoggled elements can be in this array because of the previous check
for(var i = elements.length; i--;) {
var lazyLoadElement = elements[i];
if (
lazyLoadElement.getBoundingClientRect().bottom <= window.innerHeight &&
lazyLoadElement.getBoundingClientRect().bottom > 0 ||
lazyLoadElement.getBoundingClientRect().top <= window.innerHeight &&
lazyLoadElement.getBoundingClientRect().top > 0)
{
//The element was considered visible, let's go!
lazyLoadElement.classList.add('js-lazyload--visible');
var imgData = lazyLoadElement.getAttribute('data-image'),
image = new Image(),
lazyStyle = window.getComputedStyle(lazyLoadElement);
if(lazyStyle.position !== 'relative'){
if(lazyStyle.position !== 'absolute'){
lazyLoadElement.style.position = 'relative';
}
}
image.onload = () => {
lazyLoadElement.classList.add('js-lazyload--loaded');
lazyLoadElement.insertBefore(image, lazyLoadElement.firstChild);
}
image.classList.add('js-lazyload__image')
image.style.position = 'absolute';
image.style.top = 0;
image.style.left = 0;
image.style.width = '100%';
image.style.height = '100%';
image.style.zIndex = '-1';
image.style.objectFit = 'cover';
image.src = imgData;
}
}
}
Here is a fiddle which shows the issue:
Elements red - not visible
Elements blue - visible, but no image
Elements green - visible with loaded image
https://jsfiddle.net/dalecarslaw/yumv6rft/
No jQuery Please.

The onload callback that you added is in fact getting fired properly.
The issue in this particular case is the fact that the variables declared using var are function scoped. This means that as your for loop finishes it's iteration, the lazyLoadElement variable is pointing to the same element for all the handlers that get fired with an onload handler.
Changing the declarations for lazyloadElement, imgData, image and lazyStyle from var to let will make the code work as intended.

Related

variable inside addEventListener is not working

Inside an upload script I would like to check the dimensions of the image. if these are to small, it shouldn't show the preview-image. Unfortunately the vars are not working.
Can anyone help me so that the variable var_error have the value 1 if pic_width is lower than 720.
Thanks
var fileInput = document.querySelector('#file');
var preview = document.getElementById('preview');
fileInput.addEventListener('change', function(e) {
var var_error = 0;
preview.onload = function() {
var pic_width = this.naturalWidth;
var pic_height = this.naturalHeight;
if(pic_width < 720) {
$('.contest_upload_error').fadeIn();
$('.contest_upload_error').replaceWith('Falsche Bildgrösse (Mindestgrösse 1080px x 720px)');
document.var_error = 1;
}
window.URL.revokeObjectURL(this.src);
};
if(var_error == 0) {
var url = URL.createObjectURL(e.target.files[0]);
preview.setAttribute('src', url);
}
}, false);
I'm thinking the following may work better for you.
var fileInput = document.querySelector('#file');
var preview = document.getElementById('preview');
fileInput.addEventListener('change', function(event) {
var e = event;
preview.onload = function() {
var pic_width = this.naturalWidth,
pic_height = this.naturalHeight;
if (pic_width < 720) {
$('.contest_upload_error')
.fadeIn();
.replaceWith('Falsche Bildgrösse (Mindestgrösse 1080px x 720px)');
this.setAttribute('src', URL.createObjectURL(e.target.files[0]));
}
window.URL.revokeObjectURL(this.src);
};
});
Notes:
I saved the change event handler's parameter, event, as var e, so we can use it in the next function.
I moved the if(var_error == 0) part to inside the load event handler. Where it was situated originally was wrong, as preview.onload() is asynchronous and will be called after if(var_error==0), so var_error, at that point in the script, remains 0.
I removed the variable var_error as we now do the error checking inside preview.onload()'s if(pic_width < 720) conditional statement instead.
Additional notes:
You have a strange mix of jQuery and Javascript selectors in there. At the top you select elements using Javascript and inside the change event handler you select elements with jQuery.

Make a new image appear by clicking on the previous image

I want to write a code in which when you click on an image another image appears. After that when you click on the new image, another one appears, and so on.
I wrote this code which works for the first image. I can't figure out how to define the appeared images as inputs.
var i = 1
function addimage() {
var img = document.createElement("img");
img.src = "images/d" + i + ".jpg";
document.body.appendChild(img);
}
function counter() {
i = i + 1
}
<input type="image" src="images/d1.jpg" onclick="addimage(); counter();">
Attach an onclick function to the new image, with the same code as in your input tag:
var i = 1
function imageClick() {
if (! this.alreadyClicked)
{
addimage();
counter();
this.alreadyClicked = true;
}
}
function addimage() {
var img = document.createElement("img");
img.src = "http://placehold.it/" + (200 + i);
img.onclick = imageClick;
document.body.appendChild(img);
}
function counter() {
i = i + 1
}
<input type="image" src="http://placehold.it/200" onclick="imageClick();">
To add an event handler to an element, there are three methods; only use one of them:
=> With an HTML attribute. I wouldn't recommend this method because it mixes JS with HTML and isn't practical in the long run.
<img id="firstImage" src="something.png" onclick="myListener(event);" />
=> With the element's attribute in JS. This only works if you have a single event to bind to that element, so I avoid using it.
var firstImage = document.getElementById('firstImage');
firstImage.onclick = myListener;
=> By binding it with JavaScript. This method has been standardized and works in all browsers since IE9, so there's no reason not to use it anymore.
var firstImage = document.getElementById('firstImage');
firstImage.addEventListener("click", myListener);
Off course, myListener needs to be a function, and it will receive the event as its first argument.
In your case, you probably don't want to add another image when you click on any image that isn't currently the last. So when a user clicks on the last image, you want to add a new image and stop listening for clicks on the current one.
var i = 1;
function addNextImage(e) {
// remove the listener from the current image
e.target.removeEventListener("click", addNextImage);
// create a new image and bind the listener to it
var img = document.createElement("img");
img.src = "http://placehold.it/" + (200 + i);
img.addEventListener("click", addNextImage);
document.body.appendChild(img);
// increment the counter variable
i = i + 1;
}
var firstImage = document.getElementById("firstImage");
firstImage.addEventListener("click", addNextImage);
Try on JSFiddle
On a side note: while JavaScript does support omitting some semi-columns it's considered a better practice to put them, and it will avoid small mistakes.

How to change image src in a div with javascript?

I am using videojs to build a website to play videos and insert some images to the page at certain time point of the video being played. I used the code below to get the current time and insert an image in "imageContext" div.
<script type="text/javascript">
var myPlayer = document.getElementById("example_video_1");
var added = false;
var ShowAds=function(){
var time = myPlayer.currentTime;
var img = document.createElement('IMG');
div = document.getElementById("imageContext");
div.style.width = '100px';
div.style.height = '100px';
div.style.display = 'inline-block';
if(time > 30 && time <= 40 && !added){
//img.onload = function(){
div.appendChild(img);
added = true;
img.setAttribute("src",'/Applications/MAMP/htdocs/shqc.jpg');
img.style.width = '100%';
img.style.height = 'auto';
}else if(time > 70){
//change to another image in the same position
}
}
myPlayer.addEventListener('timeupdate',ShowAds,false);
</script>
What if I want to show another image on the page when the time changes to, say, the 90th second? Or is there any way to delete the image on the page using javascript? Thanks!
To be clear, I have tried to put
img.setAttribute("src",'/Applications/MAMP/htdocs/yy.jpeg');
under else, it doesn't work
You can set an id to the img element when you create it, (In case if you have multiple img tags inside the document)
img.setAttribute('id', 'myImg');
Then, check whether time equals 90 the same way you check whether it is between 30 and 40 and change the src of it like below.
if(time == 90 ){
document.getElementById("myImg").src="/Applications/MAMP/htdocs/your_image.jpg";
}
In case if you want to delete the img element, since it has a parent
(imageContext), you can do this,
var el = document.getElementById('myImg');
el.parentNode.removeChild(el);
You can use this code:
document.getElementById("image").src="/PATH/To/Image/file.jpg";
I think all you need is this:
document.getElementById("myImg").src = //whatever
You could try creating and array of images locations.
var imageArray = new Array();
imageArray[0]= {
image01: new Image(),
image01.src: "my-image-01.png",
imageCaption: "An image caption"
};
Then at the next interval just set the image attribute to the src of a picture in the array or just append the whole image.
if(time > 90 && time <= 100 && !added){
div.appendChild(img);
added = true;
img.setAttribute("src",imageArray[0].src);
img.style.width = '100%';
img.style.height = 'auto';
Hope that helps.
I used a little help from this other stack overflow question and answer:
Creating an array of image objects

Initializing a plugin AFTER a foreach loop

So I'm trying to implement stellar.js but it must be initialized after an each loop is finished. The loop must add data attributes to the images that are going to be made parallax by the plugin.
The images are in a list:
<ul>
<li class="item">
<div class="item-image" data-stellar-background-ratio="0.7" data-image="http://picjumbo.picjumbocom.netdna-cdn.com/wp-content/uploads/IMG_7706-1300x866.jpg"></div>
</li>
...
</ul>
I must add data-stellar-vertical-offset attribute to each of them that will offset the image by half of its height, so it can be vertically centered initially.
Here is the JS:
/* Inserting the background image */
$('.item-image').each(function () {
var $this = $(this);
$this.css('background-image', 'url(' + $this.data('image') + ')');
})
/* Creating loop that will run as many times as items are in there */
var items = $('.item-image').length;
var currentItem = 0;
$('.item-image').each(function () {
var $this = $(this);
/* Taking the origin height, halving it and putting it as offset so the image can be vertically aligned */
var img = new Image();
img.src = $(this).data('image');
img.onload = function () {
var H2 = this.height;
$this.attr('data-stellar-vertical-offset', -(H2 / 2));
}
currentItem++;
/* Initializing the plugin after every item is looped */
if (currentItem >= items) {
$.stellar();
}
})
However when the plugin is initialized it isn't using the data attribute. If it's put in a timeout like this:
if (currentItem >= items) {
setTimeout(function () {
$.stellar();
}, 10)
}
.. it works but it seems to me like an ugly hack. Is there a better way for this to be done?
Here is a jsfiddle: http://jsfiddle.net/9f2tc/1/
I believe what you want is to initialize stellar once after all the images have been downloaded. The simplest approach is to check each time in the onload handler:
img.onload = function () {
var H2 = this.height;
$this.attr('data-stellar-vertical-offset', -(H2 / 2))
if (++currentItem === items) {
$.stellar();
}
}
jsfiddle: http://jsfiddle.net/X6e9n/2/
However, there are issues with the onload event not firing for images in certain cases. See the caveats section on the jQuery page: http://api.jquery.com/load-event/ The problems listed apply to the load event itself not just jQuery's .load() See Javascript callback for knowing when an image is loaded for solutions. The first answer notes the handler should be attached before the src attribute is set, which you don't do here, but it doesn't seem to be a problem for me in this case.

Why is my image not loading?

At http://blajeny.com I have:
jQuery('body').click(function(event_object)
{
spark(event_object.pageX, event_object.pageY);
});
function spark(x, y)
{
console.log('Spark called.');
var image_object = new Image();
image_object.onLoad = function()
{
image_object.style.position = 'absolute';
image_object.style.zIndex = 1;
image_object.style.top = y;
image_object.style.left = x;
}
image_object.src = '/img/spark.png';
}
The intended effect, at this stage, is to load an image at the X and Y where the user clicked. (I want to do other things as well, like animate it, but right now I'm trying to get the image to show up where the user clicked.)
The javaScript console shows that the handler is being called, however I am not seeing what I expect, a hazy blue circle immediately below and to the right of the point where the mouse was clicked.
What can/should I do differently so it loads a fresh image below and to the right of the clicked coordinates?
Thanks,
As far as I know, the onLoad should be onload
var image_object = document.createElement('img');
image_object.onload = function() { // Note the small onload
// your code
}
// Also, append the image_object to DOM
document.body.appendChild(image_object);
I don't see you appending the image to DOM that's probably why you're not seeing it
$('body').append($(image_object));
I agree, first create an element with the "img" tag, assign the src value to it, and append it to the current div (in this case its the body), like so
var imgTag = document.createElement("img");
imgTag.src = '/img/spark.png';
document.body.appendChild(imgTag);
Hope this helps.
You never append the image to the DOM, that's why you can't see it.
You can do
document.body.appendChild(image_object);
You must also replace onLoad by onload and specify the top and left position with an unit :
image_object.onload = function() {
image_object.style.position = 'absolute';
image_object.style.zIndex = 1;
image_object.style.top = '100px';
image_object.style.left = '100px';
}
Demonstration

Categories