Class is being removed with lazy loading JavaScript - javascript

I am trying to Lazy load some images onto a website. The problem that I am encountering is that I have to remove the class for the lazy loading to work but I still want the CSS properties to be used by the image. Can anyone help?
// JavaScript Document
document.addEventListener("DOMContentLoaded", function() {
var lazyImages = [].slice.call(document.querySelectorAll("img.Images"));
if ("IntersectionObserver" in window) {
let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
let lazyImage = entry.target;
lazyImage.src = lazyImage.dataset.src;
lazyImage.srcset = lazyImage.dataset.srcset;
lazyImage.classList.remove("Images");
lazyImageObserver.unobserve(lazyImage);
}
});
});
lazyImages.forEach(function(lazyImage) {
lazyImageObserver.observe(lazyImage);
});
} else {
// Possibly fall back to a more compatible method here
}
});
.Images {
width: 190px;
height: 190px;
padding: 10px;
object-fit: cover;
}
<img class="Images" src="https://i.imgur.com/mf5YDAy.jpg" data-src="https://i.imgur.com/mf5YDAy.jpg" data-srcset="https://i.imgur.com/mf5YDAy.jpg, https://i.imgur.com/mf5YDAy.jpg" />

Related

Trigger multiple classes within js funtion

so i found this js somewhere, which is working perfectly fine. But how can i add this funtion to a diffrent class without overwriting it? like i want to use the same funtion but for a diffrent object in my html, so that i can use the same effect again just at a diffrent viewpoint and with a diffrent object on my page.
$(document).scroll(function() {
myID = document.getElementById("advertisement");
var myScrollFunc = function () {
var y = window.scrollY;
if (y >= 550) {
myID.className = "advertisement show"
} else {
myID.className = "advertisement hide"
}
};
window.addEventListener("scroll", myScrollFunc);
});
i tried to just copy paste it and create a new variable but im a js beginner so had no luck with that
You don't need the jQuery scroll event. Only the inner JavaScript one.
Also, use classList.toggle("className", force) instead:
const elAdvertisement = document.querySelector("#advertisement");
const toggleAdvertisement = () => {
elAdvertisement.classList.toggle("hide", scrollY < 550);
};
addEventListener("scroll", toggleAdvertisement); // On scroll
toggleAdvertisement(); // On init
body {
min-height: 1000vh; /* just to force scrollbars */
}
#advertisement {
position: fixed;
top: 0;
}
.hide {
display: none;
}
Scroll down
<div id="advertisement" class="hide">ADVERTISEMENT HERE</div>
If you are using jQuery, here's a code sample with that library:
const $advertisement = $("#advertisement");
const toggleAdvertisement = () => {
$advertisement.toggleClass("hide", scrollY < 550);
};
$(document).on("scroll", toggleAdvertisement); // On scroll
toggleAdvertisement(); // On init
body {
min-height: 1000vh; /* just to force scrollbars */
}
#advertisement {
position: fixed;
top: 0;
}
.hide {
display: none;
}
Scroll down
<div id="advertisement" class="hide">ADVERTISEMENT HERE</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>

JS/CSS Loading spinner finishes before JS executes

Kinda JS/dev newbie here. Having a play around with a loading spinner - this is the example I'm working from. I've currently got a bunch of JS calculations that are performed on a Flask/SQLite backend API. I'll ignore the CSS as it's likely irrelevant.
document.onreadystatechange = function() {
if (document.readyState !== "complete") {
document.querySelector("main").style.visibility = "hidden";
document.querySelector("#loader").style.visibility = "visible";
} else {
document.querySelector("#loader").style.display = "none";
document.querySelector("main").style.visibility = "visible";
}
};
This in the html:
<main role="main" class="container">
<div id="loader" class="spinner-1"></div>
...content here...
</main>
<script type="text/javascript" src="{{ url_for ('static', filename='scripts/main.js') }}"></script>
The issue is that the JS is still running on DOM load. So the spinner disappears and items are still being added to the DOM via JS. They appear after the spinner disappears, which negates the point of having a spinner!
I've tried several of these methods, but they all work the same.
I thought about having the conditional tied to one of the loading items, but that seems a bit clunky and I'd not be able to repeat the code on other pages on the site. Is there a baked in JS method for doing this properly?
EDIT - some of the JS I'm using
async function recipeGet () {
let response = await fetch('/recipeget/' + recipeId)
let data = await response.json();
return data
};
recipeGet();
async function efficiency () {
let mEfficiency = (await recipeGet()).efficiency;
mEfficiency = mEfficiency / 100
return mEfficiency
}
async function insertEff () {
let eff = await efficiency();
let effElement = document.querySelector('#eff');
effElement.innerText = (((Math.round(abv * 100) / 100 )) + "%");
};
insertEff();
I appreciate this may not be the right way to do things, but I'm still relatively new to developing, and it works currently.
ANSWER:
With the suggestion of the answer below, I managed to implement this:
JS
async function insertEff () {
let eff = await efficiency();
let effElement = document.querySelector('#eff');
effElement.innerText = (((Math.round(abv * 100) / 100 )) + "%");
document.querySelector("#loader").style.display = "none";
document.querySelector("#spins").style.visibility = "visible";
};
HTML
<div id="loader" class="spinner-1"></div>
<div class="tab-content" id="spins">
CSS
#spins {
visibility: hidden;
}
Where spins is the ID of the division I want to hide. It is initially hidden, then unhides when the function is executed. I still have to toy around with the HTML as the spinner jigs around a bit on page load, but that's a relatively trivial problem.
I would maybe change how you're receiving the data on your front end. It seems like you're trying to directly add the data from the back end to the front end using some kind of template.
Instead of doing this, try doing a fetch request from the front end after the page is loaded. That way you get the page much faster, and your spinner will be coordinated with the data you receive, meaning it will only disappear once you have all the data.
Here's an example of an ajax/fetch request:
const API_URL = 'https://opentdb.com/api.php?amount=3'
const dataWrapper = document.querySelector('.data-wrapper')
const spinner = document.querySelector('.spinner')
const data = document.querySelector('.data')
const fetchData = async (URL) => {
try {
const res = await fetch(URL)
const json = await res.json()
// populate data with json
data.innerText = JSON.stringify(json, undefined, 2)
spinner.remove() // or just make display: none; - whatever you need
} catch(err) {
console.log(err.message)
}
}
fetchData(API_URL)
.data-wrapper {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.spinner {
position: absolute;
width: 100px;
height: 100px;
font-size: 100px;
color: blue;
animation: spin 1s linear infinite;
display: flex;
justify-content: center;
align-items: center;
}
#keyframes spin {
to {
transform: rotate(360deg);
}
}
.data {
background: lightgray;
color: #235789;
font-family: 'Cascadia Code', monospace;
word-break: break-all;
white-space: pre-wrap;
width: 100%;
height: 100%;
overflow-y: scroll;
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css" integrity="sha512-iBBXm8fW90+nuLcSKlbmrPcLa0OT92xO1BIsZ+ywDWZCvqsWgccV3gFoRBv0z+8dLJgyAHIhR35VZc2oM/gI1w==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<div class="data-wrapper">
<div class="spinner">
<i class="fas fa-spinner"></i>
</div>
<pre class="data"></pre>
</div>
The point is, by doing an ajax call in this way, the line where the spinner gets removed will only be reached and executed once the data is fully received.
window.onload = function() {
setTimeout(() => {
document.querySelector('#loader').style.opacity = '0';
setTimeout(() => {
document.querySelector('#loader').style.display = 'none';
document.querySelector('main').style.visibility = 'visible';
}, 200);
}, 1000);
}
I use this code to make my preloader smooth and hide after 1 second of window onload. Maybe this code will help you.

Replaying a gif from start with createElement in React

I'm trying to create a function in my React project that will create a new element that plays one loop of a gif, then deletes the element. So far I have been semi-successful using this:
function playGif() {
var gif = document.createElement('img')
gif.className = "gif-play"
document.body.appendChild(gif)
setTimeout(() => { gif.parentNode.removeChild(gif); }, 600)
}
With the following CSS:
.gif-play {
width: 256px;
height: 256px;
position: absolute;
background-image: url(./assets/images/animations/atk1.gif);
background-size: cover;
}
However, no matter what I set the timeout to delete the element (even the precise duration of the gif in milliseconds) the gif will still start clipping and playing from the middle after several button clicks.
I have also tried setting the image src directly like this:
function playGif() {
var gif = document.createElement('img')
gif.src = require('./assets/images/animations/atk1.gif')
document.body.appendChild(gif)
setTimeout(() => { gif.parentNode.removeChild(gif); }, 600)
}
But with this the gif never shows up at all, just a broken image. I thought using require() in React was supposed to fix that, but it still doesn't work for me.
So any tips on how to get my gif to play from the beginning when I call the function would be great.
Thanks!
not sure exactly what you want but I have prepared one animation for you. You can check it. Hope it helps you.
PlayGifExample Component
import React, {useEffect} from "react";
import PlayGif from './PlayGif.css'
function PlayGifExample() {
function animate(i) {
if(i > 1000)
return;
let gif = document.createElement('img');
gif.className = "gif-play";
gif.style.cssText = `left: ${i}px`;
console.log(i);
document.body.appendChild(gif);
setTimeout(() => {
gif.parentNode.removeChild(gif);
animate(i+1);
}, 10)
}
function playGif() {
let gif = document.createElement('img');
gif.className = "gif-play";
gif.style.cssText = `left: 1px`;
document.body.appendChild(gif);
setTimeout(() => {
gif.parentNode.removeChild(gif);
animate(2);
}, 10)
}
return (
<>
<button onClick={playGif}>Click me</button>
</>
);
}
export default PlayGifExample;
PlayGif.css
.gif-play {
width: 256px;
height: 256px;
position: absolute;
background-image: url(./images/4.png);
background-size: cover;
}

Preloading background images

I'm building a page that loops through 3 different backgrounds, changing every 750ms. To do this, I'm adding a class to the body with the relevent background image, changing with JS. For the first loop through, they flash because the image has to load, so it's not there instantly. Therefore is there any methods I can use to preload the images?
CSS:
&.backgrounds{
background-position: center bottom;
background-repeat: no-repeat;
background-size: 130%;
&.freddy{
background-image: url(/img/illustrations/snapchat/snapchat_holding_page_freddy.jpg);
}
&.irene{
background-image: url(/img/illustrations/snapchat/snapchat_holding_page_irene.jpg);
}
&.joe{
background-image: url(/img/illustrations/snapchat/snapchat_holding_page_joe.jpg);
}
}
JS:
setInterval(function() {
if ( $('.backgrounds').hasClass('freddy') ){
$('.backgrounds').removeClass('freddy').addClass('irene');
} else if ( $('.backgrounds').hasClass('irene') ){
$('.backgrounds').removeClass('irene').addClass('joe');
} else if ( $('.backgrounds').hasClass('joe') ){
$('.backgrounds').removeClass('joe').addClass('freddy');
}
}, 750);
I would do something like this. loadImages returns a Promise that will resolve once all of the images are loaded. The .then attached to it calls cycleImages, which starts up the interval. Since you will need the URLs in the JS anyway to do the pre-loading, instead of class switching I'm directly manipulating the background-image, that way you can remove the image URLs from the CSS and save a few redundant bytes. This also makes it easier to expand the list of images in the future too, you only need to add an item to the array of images instead of maintaining a complicated if statement.
function loadImages (images) {
// each image will be loaded by this function.
// it returns a Promise that will resolve once
// the image has finished loading
let loader = function (src) {
return new Promise(function (resolve, reject) {
let img = new Image();
img.onload = function () {
// resolve the promise with our url so it is
// returned in the result of Promise.all
resolve(src);
};
img.onerror = function (err) {
reject(err);
};
img.src = src;
});
};
// create an image loader for each url
let loaders = [];
images.forEach(function (image) {
loaders.push(loader(image));
});
// Promise.all will return a promise that will resolve once all of of our
// image loader promises resolve
return Promise.all(loaders);
}
// the images we are going to display
let myImages = [
'http://www.gifpng.com/400x200',
'http://www.gifpng.com/400x200/ffffff/000000',
'http://www.gifpng.com/400x200/000000/ffffff'
];
// $(document).ready(fn) is deprecated,
// use the $(fn) form instead
$(function() {
// after the images are loaded this will be called with an array of the loaded images
function cycleImages (images) {
let index = 0;
setInterval(function() {
// since we need an array of the image names to preload them anyway,
// just load them via JS instead of class switching so you can cut them
// out of the CSS and save some space by not being redundant
$('#backgrounds').css('backgroundImage', 'url("' + images[index] + '")');
// increment, roll over to 0 if at length after increment
index = (index + 1) % images.length;
}, 750);
}
// load the images and start cycling through them after they are loaded
loadImages(myImages).then(cycleImages).catch(function (err) {
console.error(err);
});
});
#backgrounds {
height: 200px;
width: 400px;
border: 1px solid #000;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="backgrounds"></div>
Edit:
Taking Just a student's comments into account, here is a version that will only switch the image once it is loaded, but do it right away if it is loaded. It also skips images that failed to load. Since cycleImages is no longer called via .then, I also changed it so it accepts a target element (as a jQuery object) along with an array of images promises. That way you can easily use this on multiple places on a page with different images sets if you wanted to.
function loadImages (images) {
// each image will be loaded by this function.
// it returns a Promise that will resolve once
// the image has finished loading
let loader = function (src) {
return new Promise(function (resolve, reject) {
let img = new Image();
img.onload = function () {
// resolve the promise with our url
resolve(src);
};
img.onerror = function (err) {
reject(err);
};
img.src = src;
});
};
// return an array of image-loading promises
return images.map(function (image) {
return loader(image);
});
}
// the images we are going to display
let myImages = [
'http://www.gifpng.com/400x200',
'http://www.invalid-domain-name.foo/this-url-certainly-does-not-exist.jpg',
'http://www.gifpng.com/400x200/ffffff/000000',
'http://www.gifpng.com/400x200/000000/ffffff'
];
// $(document).ready(fn) is deprecated,
// use the $(fn) form instead
$(function() {
// this receives an array of the promises for each image
function cycleImages ($target, images) {
let index = 0,
interval = 750; // how many ms to wait before attempting to switch images
function nextImage () {
// p is the promise for the current image
let p = images[index],
next = function (wait) {
// increment our counter and wait to display the next one
index = (index + 1) % images.length;
setTimeout(nextImage, wait);
};
// wait for this image to load or fail to load
p.then(function (src) {
// it loaded, display it
$target.css('backgroundImage', 'url("' + src + '")');
next(interval);
}).catch(function (err) {
// this one failed to load, skip it
next(0);
});
}
// start cycling
nextImage();
}
// load the images and start cycling through them as they are loaded
cycleImages($('#backgrounds'), loadImages(myImages));
});
#backgrounds {
height: 200px;
width: 400px;
border: 1px solid #000;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="backgrounds"></div>
Instead of hard-coding the interval between images changes you could also pass that in as a parameter. At that point though, I would refactor it to use a config object to pass everything but the image promise array: cycleImages(myImages, {target: $('#backgrounds'), interval: 1000});
You can load them in javascript. Like:
(new Image()).src = url1;
(new Image()).src = url2;
(new Image()).src = url3;
Change the "url1", "url2", "url3" with you own image urls. Browser will load the image but they wont be visible anywhere.
Interesting idea.
Could you potentially load them in the background so like:
<div class="hidden-images">
<img src="img1.jpg" />
<img src="img2.jpg" />
<img src="img3.jpg" />
</div>
Then in CSS
.hidden-images {
position: relative;
z-index: 1;
}
Then on whatever you sort of main container div is, slap the following on it?
.main-container {
position: relative;
z-index: 2; // Or higher, whatever is needed
}
You can do also do this with different html and css structure like this.
html
<div class="backgrounds">
<div class="background freddy"></div>
</div>
css
.backgrounds{
background-color: transparent;
background-position: center bottom;
background-repeat: no-repeat;
background-size: 130%;
height: 250px;
width; 100%;
}
.backgrounds .freddy{
height: 100%;
width: 100%;
background-image: url('http://adrianweb.net/includes/images/hero.jpg');
}
.backgrounds .irene{
height: 100%;
width: 100%;
background-image: url('http://adrianweb.net/includes/images/projects-blur.png');
}
.backgrounds .joe{
height: 100%;
width: 100%;
background-image: url('http://adrianweb.net/includes/images/contacts-blur.png');
}
JS
$(document).ready(function(){
setInterval(function() {
if ( $('.background').hasClass('freddy') ){
$('.background').removeClass('freddy').addClass('irene');
} else if ( $('.background').hasClass('irene') ){
$('.background').removeClass('irene').addClass('joe');
} else if ( $('.background').hasClass('joe') ){
$('.background').removeClass('joe').addClass('freddy');
}
}, 750);
});
Maybe you can add transition/animation to make it look fade in and out.
Sample codepen below:
http://codepen.io/adrianrios/pen/yMEpEv

Using Lightbox2 to display video

Site: http://blieque.comli.com/motion
Before I start, I know there are many alternatives to Lightbox2 (Lokesh Dhakar's) to display videos but I want to avoid using three different JavaScript things, already using MooTools and JQuery as I'd like to keep HTTP requests to a minimum, as well as disk usage.
As it comes, Lightbox2 does not support videos, full stop. However, I noticed that the JavaScript was essentially taking the contents of an a's href attribute and placing it in an img's src attribute when the light-box was open. As far as I can see, changing this img to an iframe (done) and setting the anchor tag's href to youtube.com/embed/*video-id* should generate an iframe containing that YouTube video (replacing watch?id= with embed/ presents a full screen version of the video.
I then also added JavaScript for width, height and frameborder attributes on top of the default class="lb-image". Now when the page is loaded and the Lightbox called it creates an empty window. If you inspect the code you can see all the attributes are there but the page in the frame frame isn't loaded, just creating an empty head and body tag.
I was just wondering if it was a server problem or a code problem and if so, how to fix it. If there any way to make it work?
Thanks
Note: I'm NOT using Drupal so the lightvideo option is not available.
Maybe someone still is looking for solution.
I had same problem in my project. Not with youtube iframe but it’s not difficult to implenet it. Lightbox2 cannot be extended so i wrote simple class whose adding listener and observer. For proper displaing require is that video have a poster with the same sizes. That’s the fastest way to keep corret sizes of popup.
In href required is to add dataset href with image url
<a href="POSTER_URL" data-href="VIDEO_URL" data-lightbox="Videos">
Open Lightbox
</a>
SCSS to cover image in popup and set fade effect while loading
.lightbox {
.lb {
&-outerContainer {
video {
position: absolute;
top: 0;
left: 0;
right: 0;
z-index: 9999;
width: 100%;
height: auto;
opacity: 1;
transition: opacity 300ms ease-in-out;
border: none;
outline: none;
&:hover, &:focus {
border: none;
outline: none;
}
}
&.animating {
video {
opacity: 0;
}
}
}
&-container {
position: relative;
.lb-image {
border: none;
}
}
}
}
And JS class which one create and set video into popup. Maybe little messy but i don't care. It's only quick solution.
class LightBoxVideo {
constructor() {
this.videos = {};
this.lightBoxVideo();
}
lightBoxVideo = () => {
this.setEvents();
this.setMutationObserver();
}
setMutationObserver = () => {
const observer = new MutationObserver(mutation => {
const imageMutations = mutation.filter((m) => {
return m.attributeName === "src" && m.target.className === 'lb-image'
});
const overlayDisplay = window.getComputedStyle(document.querySelector('.lightboxOverlay'), null).display;
if("none" === overlayDisplay) {
this.removeVideoElement();
}
if(imageMutations.length > 0) {
if(this.videos[imageMutations[0].target.src]) {
this.removeVideoElement();
this.setVideoElement(this.videos[imageMutations[0].target.src]);
}
}
});
observer.observe(document.body, {
childList: false,
attributes: true,
subtree: true,
characterData: false
});
}
setEvents = () => {
const videoLinks = this.findVideoLinks();
videoLinks.forEach((link) => {
this.videos[link.href] = link;
link.addEventListener('click', (e) => {
this.removeVideoElement();
this.setVideoElement(e.target);
});
});
}
setVideoElement = (element) => {
const lightbox = document.querySelector('.lightbox')
const container = lightbox.querySelector('.lb-container');
const videoElement = this.createVideoElement(element);
container.prepend(videoElement);
}
removeVideoElement = () => {
const lightbox = document.querySelector('.lightbox')
const container = lightbox.querySelector('.lb-container');
const video = container.querySelector('video');
if(video) {
container.removeChild(video);
}
}
createVideoElement = (element) => {
const video = document.createElement('video');
video.setAttribute('poster', element.href);
video.setAttribute('controls', 'true');
const source = document.createElement('source');
source.setAttribute('src', element.dataset.href);
source.setAttribute('type', 'video/mp4');
video.append(source);
return video;
}
findVideoLinks = () => {
const hrefs = document.querySelectorAll('a[data-lightbox]');
const regex = /\.(mp4|mov|flv|wmv)$/;
if(0 === hrefs.length) {
return [];
}
return Array.from(hrefs).filter((href) => {
return !! href.dataset.href.match(regex);
});
}
}
To preview how it's work - codepen here: https://codepen.io/PatrykN/pen/RwKpwMe
Enabling video support
By default, support for videos is disabled. However, this can be easily enabled by checking the enable video support option on admin/settings/lightbox2.
Basic Example
This Video
Reference

Categories

Resources