bLazy and Vue.js - DOM not ready fast enough - javascript

I am working on simple gallery with pictures. I wanted to use bLazy plugin to load images, all works fine except the fact that I wanted to load image list via external JSON file and because of that images elements are not created fast enough, so when bLazy script is loaded, it can't see images yes.
If I use setTimeout it works, but it is a nasty way of doing things... Any ideas how to refactor my code?
Please note that it work in progress and I will use routers later...
app.js:
var allPics = Vue.extend({
el: function () {
return "#gallery";
},
data: function () {
return {
pics: {},
folders: {
full: "img/gallery/full_size/",
mid: "img/gallery/mid/",
small: "img/gallery/small/",
zoom: "img/gallery/zoom/"
}
};
},
created: function () {
this.fetchData();
},
ready: function () {
setTimeout(function () {
var bLazy = new Blazy({
});
}, 1000);
},
methods: {
fetchData: function () {
var self = this;
$.getJSON("js/gallery.json", function (json) {
self.pics = json;
})
}
}
});
var router = new VueRouter({
});
router.start(allPics, 'body', function () {
});
HTML:
<div id="gallery" class="gallery">
<div v-for="pic in pics.gallery" class="gallery_item">
<div class="img_div">
<img class="b-lazy"
src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
data-src= "{{* folders.mid + pic.name}}"
alt="{{pic.alt}}" >
</div>
</div>

You might want to check https://github.com/aFarkas/lazysizes, it detects DOM changes automatically, so you don't have to do any setTimeout hacks.
Only add the script and add the class lazyload as also use data-src instead of src and you are done.

I am also working with a small gallery of images and using image-background on divs instead of < img > tags since they offer more control over nested elements positioning and allows to use background-size: cover property.
What i do to preload images is something like this:
var imageUrl = ....
var img = new Image();
img.onload = function() {
this.$els.divId.style.backgroundImage = "url(" + imageUrl + ")";
$(this.$els.divId).fadeIn(1000); // fade in div using jquery
};
img.src = imageUrl;
That way when the image is loaded and cached in the browser i can fade in the image div for a smooth effect.
Note that the divId element is hidden (using display: false) from the start and no background-image property is assigned.
Also onload event should be set before assigning imageUrl to img.src so you don't miss the onload event if the image is already cached.
This functionality can also be added to a mixin or an utils class and keeps things simple. It can also adapted to < img > by setting the onload listener, fadeIn and src on an existing img element.

You can trying to revalidate: "blazy.revalidate()", after fetch function.Or to revalidate in the "updated". I was helped.

Use Vue.nextTick. Reference.
Defer the callback to be executed after the next DOM update cycle
Vue.nextTick(() => {
new Blazy();
});

Related

Intersection Observer Not Working Wordpress 5.8.1

I implemented Intersection Observer on my Wordpress site which is on dev mode atm and just the last image at the bottom of the site is lazyloading.
Not sure the reason why that is happening. See below my lazy code.
/*Lazy load images*/
const allViews = document.querySelectorAll("[data-src]");
function preloadImage(img) {
const src = img.getAttribute("data-src");
if (!src) {
return;
}
img.src = src;
}
const options = {
root: null,
threshold: 0,
rootMargin: "0px",
};
const callback = function (entries) {
//console.log(entries);
};
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) {
return;
} else {
console.log(entry.target);
preloadImage(entry.target);
observer.unobserve(entry.target);
}
});
}, options);
allViews.forEach((image) => {
observer.observe(image);
});
On another file, I have a jquery script adding the data-src attribute to all images and adding the lazyload class:
/*change img src to img data-src for lazy load*/
$("img").each(function () {
$(this).attr("data-src", $(this).attr("src"));
$(this).addClass('lazyload');
//$(this).removeAttr("src");
//console.log($(this)[0].outerHTML);
});
Any help will be great.
I'm still confused why are you actually using this method because wordpress already has native loading="lazy" support
Javascript might execute in the order it loaded, If the Jquery code
is not loaded before the IntersectionObserver code, Your script
become useless.
you have comment out //$(this).removeAttr("src"); which means, src attribute still exist and the image will be loaded normally. You should uncomment it.
Instead of spending time developing a lazyload you could simply leave to wordpress do it natively lazy load, or use Plugins like W3 Total Cache, which has inbuilt support for Lazy Loading.

Execute js after all image loaded not working

(not duplicate, because not find exactly/easy solution)
I'm trying to execute JS after all images completely loaded. My goal is, when all images finish load completely, then removeClass my-loader and addClass visible to main-slider div.
HTML:
<div class='main-slider my-loader'>
<img src="https://unsplash.it/200/300/">
<img src="https://unsplash.it/200/300/">
<img src="https://unsplash.it/200/300/">
</div>
Execute below js when all images completely loaded
$(".main-slider").removeClass("my-loader").addClass("visible");
Tried this js :
But not works properly on my site, problem is when i clear browser cache, then it works/execute! when i reload page then next time it's not works/execute! It only works when i clear browser cache.
var img = $('.main-slider img')
var count = 0
img.each(function(){
$(this).load(function(){
count = count + 1
if(count === img.length) {
$('.main-slider').removeClass('my-loader').addClass('visible')
}
});
});
Any simple solution? Thanks in advance.
jQuery provides a way to register a callback for the window load event which will fire when the entire page, including images and iframes, are loaded.
Reference: https://learn.jquery.com/using-jquery-core/document-ready/
Your code should look something like:
$( window ).load(function () {
var img = $('.main-slider img')
var count = 0
img.each(function(){
$(this).load(function(){
count = count + 1
if(count === img.length) {
$('.main-slider').removeClass('my-loader').addClass('visible')
}
});
});
});
Here's how to do this, using Deferreds and native handlers, and calling the onload handler if the image is cached in older browsers etc.
var img = $('.main-slider img');
var defs = img.map(function(){
var def = new Deferred();
this.onload = def.resolve;
this.onerror = def.reject;
if (this.complete) this.onload();
return def.promise();
});
$.when.apply($, defs).then(function() {
$('.main-slider').removeClass('my-loader').addClass('visible')
});

Animate when the first image is loaded [duplicate]

I want to set a background image on the body tag, then run some code - like this:
$('body').css('background-image','http://picture.de/image.png').load(function() {
alert('Background image done loading');
// This doesn't work
});
How can I make sure the background image is fully loaded?
try this:
$('<img/>').attr('src', 'http://picture.de/image.png').on('load', function() {
$(this).remove(); // prevent memory leaks as #benweet suggested
$('body').css('background-image', 'url(http://picture.de/image.png)');
});
this will create a new image in memory and use load event to detect when the src is loaded.
EDIT: in Vanilla JavaScript it can look like this:
var src = 'http://picture.de/image.png';
var image = new Image();
image.addEventListener('load', function() {
body.style.backgroundImage = 'url(' + src + ')';
});
image.src = src;
it can be abstracted into handy function that return a promise:
function load(src) {
return new Promise((resolve, reject) => {
const image = new Image();
image.addEventListener('load', resolve);
image.addEventListener('error', reject);
image.src = src;
});
}
const image = 'http://placekitten.com/200/300';
load(image).then(() => {
body.style.backgroundImage = `url(${image})`;
});
I have a jQuery plugin called waitForImages that can detect when background images have downloaded.
$('body')
.css('background-image','url(http://picture.de/image.png)')
.waitForImages(function() {
alert('Background image done loading');
// This *does* work
}, $.noop, true);
pure JS solution that will add preloader, set the background-image and then set it up for garbage collection along with it's event listener:
Short version:
const imageUrl = "https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png";
let bgElement = document.querySelector("body");
let preloaderImg = document.createElement("img");
preloaderImg.src = imageUrl;
preloaderImg.addEventListener('load', (event) => {
bgElement.style.backgroundImage = `url(${imageUrl})`;
preloaderImg = null;
});
A bit longer with nice opacity transition:
const imageUrl = "https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png";
let bgElement = document.querySelector(".bg-lazy");
bgElement.classList.add("bg-loading");
let preloaderImg = document.createElement("img");
preloaderImg.src = imageUrl;
preloaderImg.addEventListener('load', (event) => {
bgElement.classList.remove("bg-loading");
bgElement.style.backgroundImage = `url(${imageUrl})`;
preloaderImg = null;
});
.bg-lazy {
height: 100vh;
width: 100vw;
transition: opacity 1s ease-out;
}
.bg-loading {
opacity: 0;
}
<div class="bg-lazy"></div>
There are no JS callbacks for CSS assets.
Something like this:
var $div = $('div'),
bg = $div.css('background-image');
if (bg) {
var src = bg.replace(/(^url\()|(\)$|[\"\'])/g, ''),
$img = $('<img>').attr('src', src).on('load', function() {
// do something, maybe:
$div.fadeIn();
});
}
});
I've located a solution that worked better for me, and which has the advantage of being usable with several images (case not illustrated in this example).
From #adeneo's answer on this question :
If you have an element with a background image, like this
<div id="test" style="background-image: url(link/to/image.png)"><div>
You can wait for the background to load by getting the image URL and
using it for an image object in javascript with an onload handler
var src = $('#test').css('background-image');
var url = src.match(/\((.*?)\)/)[1].replace(/('|")/g,'');
var img = new Image();
img.onload = function() {
alert('image loaded');
}
img.src = url;
if (img.complete) img.onload();
Here is a small plugin I made to allow you to do exactly this, it also works on multiple background images and multiple elements:
Read the article:
http://catmull.uk/code-lab/background-image-loaded/
or go straight to the plugin code:
http://catmull.uk/downloads/bg-loaded/bg-loaded.js
So just include the plugin and then call it on the element:
<script type="text/javascript" src="http://catmull.uk/downloads/bg-loaded/bg-loaded.js"></script>
<script type="text/javascript">
$('body').bgLoaded();
</script>
Obviously download the plugin and store it on your own hosting.
By default it adds an additional "bg-loaded" class to each matched element once the background is loaded but you can easily change that by passing it a different function like this:
<script type="text/javascript" src="http://catmull.uk/downloads/bg-loaded/bg-loaded.js"></script>
<script type="text/javascript">
$('body').bgLoaded({
afterLoaded : function() {
alert('Background image done loading');
}
});
</script>
Here is a codepen demonstrating it working.
http://codepen.io/catmull/pen/Lfcpb
I did a pure javascript hack to make this possible.
<div class="my_background_image" style="background-image: url(broken-image.jpg)">
<img class="image_error" src="broken-image.jpg" onerror="this.parentElement.style.display='none';">
</div>
Or
onerror="this.parentElement.backgroundImage = "url('image_placeHolder.png')";
css:
.image_error {
display: none;
}
https://github.com/alexanderdickson/waitForImages
$('selector').waitForImages({
finished: function() {
// ...
},
each: function() {
// ...
},
waitForAll: true
});
Here is a simple vanilla hack ~
(function(image){
image.onload = function(){
$(body).addClass('loaded-background');
alert('Background image done loading');
// TODO fancy fade-in
};
image.src = "http://picture.de/image.png";
})(new Image());

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.

Preloading Image Bug in IE6-8

Page in question: http://phwsinc.com/our-work/one-rincon-hill.asp
In IE6-8, when you click the left-most thumbnail in the gallery, the image never loads. If you click the thumbnail a second time, then it will load. I'm using jQuery, and here's my code that's powering the gallery:
$(document).ready(function() {
// PROJECT PHOTO GALLERY
var thumbs = $('.thumbs li a');
var photoWrapper = $('div.photoWrapper');
if (thumbs.length) {
thumbs.click( function(){
photoWrapper.addClass('loading');
var img_src = $(this).attr('href');
// The two lines below are what cause the bug in IE. They make the gallery run much faster in other browsers, though.
var new_img = new Image();
new_img.src = img_src;
var photo = $('#photo');
photo.fadeOut('slow', function() {
photo.attr('src', img_src);
photo.load(function() {
photoWrapper.removeClass('loading');
photo.fadeIn('slow');
});
});
return false;
});
}
});
A coworker told me that he's always had problems with the js Image() object, and advised me to just append an <img /> element inside of a div set to display:none;, but that's a little messy for my tastes--I liked using the Image() object, it kept things nice and clean, no unnecessary added HTML markup.
Any help would be appreciated. It still works without the image preloading, so if all else fails I'll just wrap the preloading in an if !($.browser.msie){ } and call it a day.
I see you've fixed this already, but I wanted to see if I could get the pre-loading to work in IE as well.
try changing this
photo.fadeOut('slow', function() {
photo.attr('src', img_src);
photo.load(function() {
photoWrapper.removeClass('loading');
photo.fadeIn('slow');
});
});
to this
photo.fadeOut('slow', function() {
photo.attr('src', img_src);
if (photo[0].complete){
photoWrapper.removeClass('loading');
photo.fadeIn('slow');
} else {
photo.load(function() {
photoWrapper.removeClass('loading');
photo.fadeIn('slow');
});
}
});

Categories