When my site is loading up the image always appears straight away (even when it's still loading) however I want it to appear at the exact same times as the text does to make it look like it loading up at the same time. This is the code I have used to make what I see
Javascript
$(window).on("load",function(){
$(".loader-wrapper").fadeOut("slow");
});
$.fn.srcLazy = function (src, callback) {
let elem = $(this);
let img = new Image();
img.addEventListener('load', function () {
elem.on('load', function () {
if (callback) callback();
});
elem.attr('src', img.src);
});
img.src = src;
}
$('#my-image-in-the-page').srcLazy("./images/ohridimage1.jpg", function () {
// Show text here!
});
function showPage() {
document.getElementById("loader").style.display = "none";
document.getElementById("myDiv").style.display = "flex";
}
CSS (LOADING PAGE)
body {
margin: 0;
padding: 0;
width:100vw;
height: 100vh;
background-color: #eee;
}
.content {
display: flex;
justify-content: center;
align-items: center;
width:100%;
height:100%;
}
.loader-wrapper {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
background-color: #242f3f;
display:flex;
justify-content: center;
align-items: center;
}
.loader {
display: inline-block;
width: 30px;
height: 30px;
position: relative;
border: 4px solid #Fff;
animation: loader 2s infinite ease;
}
.loader-inner {
vertical-align: top;
display: inline-block;
width: 100%;
background-color: #fff;
animation: loader-inner 2s infinite ease-in;
}
#keyframes loader {
0% { transform: rotate(0deg);}
25% { transform: rotate(180deg);}
50% { transform: rotate(180deg);}
75% { transform: rotate(360deg);}
100% { transform: rotate(360deg);}
}
#keyframes loader-inner {
0% { height: 0%;}
25% { height: 0%;}
50% { height: 100%;}
75% { height: 100%;}
100% { height: 0%;}
CSS
#myDiv {
margin: 0 50px 0 50px;
display: none;
text-align: center;
justify-content: space-between;
align-items: center;
}
.image1 {
position: flex;
right: 0px;
top: 0px;
z-index: -1;
filter: grayscale(100%);
style: float
border: 3px solid #73AD21;
width: 300px;
HTML
<!DOCTYPE HTML>
<HTML lang="en">
<head>
<meta charset="UTF-8" />
<link rel="stylesheet" type="text/CSS" href="dropdownmenu.css">
<link rel="stylesheet" type="text/CSS" href="rainbowheading.css">
<link rel="stylesheet" type="text/CSS" href="loadingcss.css">
<link rel="stylesheet" type="text/CSS" href="image.css">
<script src="loading.js"></script>
<title> North Macedonia </title>
<script src="js/jquery.js"></script>
<div class="container">
<h1 class="rainbow"> The pearl of the Balkans: Macedonia </h1>
<div class="navbar">
Home
Macedonian Dispora
Cities
<div class="dropdown">
<button class="dropbtn">History
<i class="fa fa-caret-down"></i>
</button>
<div class="dropdown-content">
Ancient History
Ottoman Period
Yugoslav Period
Modern History
</div>
</div>
</div>
</head>
<script src="loading.js"></script>
<link rel="stylesheet" type="text/css" href="loadingcss.css" />
</head>
<body>
<div class="loader-wrapper">
<span class="loader"><span class="loader-inner"></span></span>
</div>
<div style="display:none;" id="myDiv" class="animate-bottom"></div>
<div>
<h2>Welcome to my website about my country Macedonia (Makedonija)</h2>
<p>Macedonia officially the Republic of North Macedonia, is a country in the Balkan Peninsula in Southeast Europe. It gained its independence in 1991 as one of the successor states of Yugoslavia. A landlocked country, North Macedonia has borders with Kosovo to the northwest, Serbia to the northeast, Bulgaria to the east, Greece to the south, and Albania to the west. It constitutes approximately the northern third of the larger geographical region of Macedonia. The capital and largest city, Skopje, is home to roughly a quarter of the country's 2.06 million inhabitants. <br>
The majority of the residents are ethnic Macedonians, a South Slavic people. Albanians form a significant minority at around 25%, followed by Turks, Romani, Serbs, Bosniaks, and Aromanians.</p>
</div>
<img class="image1" src="./images/ohridimage1.jpg" alt="Smiley face">
</div>
In order to load an image in the background and show it when finished with another elements like text you need to load the image in the Image Object like this:
var img = new Image();
img.addEventListener('load', function () {
// Here you can set the url that was loaded in the background here
// to an actual `<img>` element. Browser uses cache now to load the image quickly.
});
img.src = 'image url here';
As soon as you set the src the image will get loaded but nobody can see it. This will force the browser to load image in the background and cache it. So now when you set that exact src to the img element the browser will use cache to show the image so the image will be loaded quickly with whatever you want to show.
UPDATE --------
You can do it in a better way in jquery:
Let's say you have bunch of images. Then instead of setting their src set their data-src attribute and then use this functionality to do something when all images loaded:
This is the HTML:
<img class="my-images" data-src="<image url 1>" />
<img class="my-images" data-src="<image url 2>" />
<img class="my-images" data-src="<image url 3>" />
<img class="my-images" data-src="<image url 4>" />
This is the JS:
$.fn.whenImagesLoaded = function (callback) {
let found = $(this);
let counter = found.length;
found.each(function (i, item) {
let elem = $(item);
let img = new Image();
img.addEventListener('load', function () {
elem.on('load', function () {
counter--;
if (counter === 0 && callback) callback();
});
elem.attr('src', img.src);
});
img.src = elem.attr('data-src');
});
}
So now you should not set src on the image at first but you have to do this:
$('.my-images').whenImagesLoaded(function () {
// Hide loading text here and show images!
});
JSFiddle
Related
Ok... I'm struggling here. I'm particularly new at JavaScript and am trying to fetch data from the Art Institute of Chicago's API. My goal is to display the information for a particular piece of art in a modal window after clicking on the relevant artwork. I've gotten the CSS and HTML down, I think. Everything displays properly as far as the images and titles and such. However, when I click the image to open the modal, all I can get to display is the Art itself.
I've found a similar question on here and tried some of its code, but their solution didn't quite get me to where I need to be.
Here's my HTML:
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="index.css">
<script src="index.js" defer></script>
<title>Art Institute of Chicago API</title>
</head>
<body>
<h1>From the Art Institute of Chicago:<br>A Sampling of Monet, Van Gogh, and Hokusai</h1>
<div class="container">
<!-- ARTic Banner -->
<div class="gallery-container w-4 h-3">
<div class="gallery-item">
<div class="image banner" onclick="openInNewTab('https:/\/www.artic.edu');">
<img src="/images/bannerArtIC.jpg"
alt="Art Institute of Chicago banner">
</div>
<div class="text">
The Art Institute of Chicago
</div>
</div>
</div>
<!-- Fig 1 - Monet: Water Lilies -->
<div class="gallery-container w-2 h-3">
<div class="gallery-item">
<div class="image" id="16568">
<img src="images/monetWaterLilies_1933.1157.jpg"
alt="Painting of a pond seen up close spotted with thickly
painted pink and white water lilies and a shadow across the
top third of the picture."
onclick="openModal(event); event.stopPropagation(); getArtwork(this.id, event)">
</div>
<div class="text">
Claude Monet:<br>Water Lilies
</div>
</div>
</div>
<!-- Fig 2 - Van Gogh: Self Portrait -->
<div class="gallery-container w-2 h-6">
<div class="gallery-item">
<div class="image" id="80607">
<img src="images/vanGoghSelfPortrait_1954.326.jpg"
alt="Painting of a red-haired, bearded man with light skin,
painted in short brushstrokes and multicolored dots. The
background is likewise a mass of small, closely spaced colored
dots, these in green, blue, and red-orange."
onclick="openModal(event); event.stopPropagation(); getArtwork(this.id, event)">
</div>
<div class="text">
Vincent Van Gogh:<br>Self Portrait
</div>
</div>
</div>
<!-- Fig 3 - Monet: Water Lily Pond -->
<div class="gallery-container w-4 h-3">
<div class="gallery-item">
<div class="image" id="87088">
<img src="images/monetWaterLilyPond_1933.441.jpg"
alt="Painting of a small pond dense with pink water lilies,
their roots visible through the water, a railed footbridge
arching over the pond and lush, dark green folliage surrounding
it."
onclick="openModal(event); event.stopPropagation(); getArtwork(this.id, event)">
</div>
<div class="text">
Claude Monet:<br>Water Lily Pond
</div>
</div>
</div>
<!-- Fig 4 - Van Gogh: The Poet's Garden -->
<div class="gallery-container w-2 h-3">
<div class="gallery-item">
<div class="image" id="14586">
<img src="images/vanGoghThePoetsGarden_1933.433.jpg"
alt="Lush tall grasses with small white flowers foreground a
grove of bushy trees of varying types and heights, their leaves
ranging from deep green to golden. Beneath a dense and heavy
yellow sky, a small blue triangle suggesting a mountain peak
crests above the treeline at far left."
onclick="openModal(event); event.stopPropagation(); getArtwork(this.id, event)">
</div>
<div class="text">
Vincent Van Gogh:<br>The Poet's Garden
</div>
</div>
</div>
<!-- Fig 5 - Hokusai: Under the Wave Off Kanagawa -->
<div class="gallery-container w-4 h-3">
<div class="gallery-item">
<div class="image" id="24645">
<img src="images/hokusaiUnderTheWaveOffKanagawa_1925.3245.jpg"
alt="A crashing wave looms over two small ships, Mount Fuji
in the background."
onclick="openModal(event); event.stopPropagation(); getArtwork(this.id, event)">
</div>
<div class="text">
Katsushika Hokusai:<br>Under the Wave Off Kanagawa
</div>
</div>
</div>
<!-- Fig 6 - Hokusai: Chrysanthemums and Bee -->
<div class="gallery-container w-2 h-6">
<div class="gallery-item">
<div class="image" id="25110">
<img src="images/hokusaiChrysanthemumsAndBee_1925.3373.jpg"
alt="A color woodblock print of pink, orange and yellow
chrysanthemums. In the upper rihgt a tiny bee flies above the
flowers. Signature and seals in the lower left corner."
onclick="openModal(event); event.stopPropagation(); getArtwork(this.id, event)">
</div>
<div class="text">
Katsushika Hokusai:<br>Chrysanthemums and Bee
</div>
</div>
</div>
<!-- div for modal -->
<div id="myModal" class="modal">
<span class="closebtn" onclick="closeModal(event)">×</span>
<img class="modal-content" id="expandedImg">
<div id="caption">
</div>
</div>
</div>
</body>
</html>
And here's my JS (with additions from answers found on stack overflow):
// function to open the AIC homepage in new tab
function openInNewTab(url) {
var win = window.open(url, 'blank');
win.focus();
}
// function to open each image as a modal once it is clicked
function openModal(event) {
var modal = document.getElementById("myModal");
modal.style.display = "block";
document.getElementById("expandedImg").setAttribute('src', event.target.getAttribute("src"));
}
// function to close the image modal
function closeModal(event) {
const modal = document.getElementById("myModal");
modal.style.display = "none";
}
// // // // // // // // // // // // // // // // // // // // // // // // // // //
// //
// functions and methods to pull data for art pieces //
// let art;
let showArtInfo;
/**
* #param art_index
* #param info_index
*
* function to pull the info on the art from AIC
*/
async function clickedEvent(art_index, info_index) { // is the info_index actually necessary here if it's not used again? is that for the API's benefit?
// Get id of artwork
let elem = document.getElementsByTagName('img')[art_index];
let id = elem.attributes[2].value;
let headers = new Headers([
['Content-Type', 'application/json'],
['Accept', 'application/json']
]);
let request = new Request(
`https://api.artic.edu/api/v1/artworks${id}?fields=id,title,artist_display,date_display`, {
method: 'GET',
headers: headers
});
let result = await fetch(request);
let response = await result.json();
console.log(response)
if (showArtInfo) {
stopShow();
} else {
let title = response.data.title;
let artist = response.data['artist_display'];
let date = response.data['date_display'];
let div = document.getElementById('caption');
div.innerHTML = `<br> Title: ${title}<br> Artist: ${artist}<br> Date Display: ${date}`;
elem.parentElement.appendChild(div);
};
}
/**
* #param id // art id
* #param event // 'onClick' event
*
* function to display the pulled info of the artpiece when clicked on
*/
function getArtwork(id, event) {
switch(id) {
case '16568': { // Water Lilies by Claude Monet, id# 16568
event.stopPropagation();
clickedEvent(0, 0)
break;
}
case '80607': { // Self Portrait by Vincent Van Gogh, id# 80607
event.stopPropagation();
clickedEvent(1, 0)
break;
}
case '87088': { // Water Lily Pond by Claude Monet, id# 87088
event.stopPropagation();
clickedEvent(2, 0)
break;
}
case '14586': { // The Poet's Garden by Vincent Van Gogh, id# 14586
event.stopPropagation();
clickedEvent(3, 0)
break;
}
case '24645': { // Under the Wave Off Kanagawa by Katsushika Hokusai, id# 24645
event.stopPropagation();
clickedEvent(4, 0)
break;
}
case '25110': { // Chrysanthemums and Bee by Katsushika Hokusai, id# 25110
event.stopPropagation();
clickedEvent(5, 0)
break;
}
}
}
And lastly, my CSS (in case you want to fully replicate my page to see what I'm going for in real time):
body {
margin: 50px;
padding: 0;
text-align: center;
background-color: #292C33;
}
h1 {
margin-bottom: 50px;
padding: 20px;
outline: 5px solid white;
color: white;
border-radius: 5px;
background-color: #b60235;
}
.container {
display: grid;
grid-template-columns: repeat(6, 1fr);
grid-auto-rows: 100px 100px;
grid-gap: 18px;
grid-auto-flow: dense;
}
.gallery-item {
width: 100%;
height: 100%;
position: relative;
}
.gallery-item .image {
width: 100%;
height: 100%;
overflow: hidden;
border: 2px solid white;
border-radius: 5px;
}
.gallery-item .image img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: 50% 50%;
cursor: pointer;
transition: .66s ease-in-out;
border-radius: 5px;
}
.gallery-item:hover .image img {
transform: scale(1.12);
}
.gallery-item .text {
opacity: 0;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
font-size: 30px;
pointer-events: none;
z-index: 4;
-webkit-backdrop-filter: blur(22px) saturate(1.5);
backdrop-filter: blur(22px) saturate(1.5);
}
.gallery-item:hover .text {
opacity: 1;
animation: move-up .33s linear;
padding: 1em;
width: 77.5%;
outline: 2px solid #b60235;
border-radius: 5px;
}
#keyframes move-up {
0% {top:77.5%}
50% {top:63.75%}
100% {top:50%}
}
.w-1 {
grid-column: span 1;
}
.w-2 {
grid-column: span 2;
}
.w-3 {
grid-column: span 3;
}
.w-4 {
grid-column: span 4;
}
.w-5 {
grid-column: span 5;
}
.w-6 {
grid-column: span 6;
}
.h-1 {
grid-row: span 1;
}
.h-2 {
grid-row: span 2;
}
.h-3 {
grid-row: span 3;
}
.h-4 {
grid-row: span 4;
}
.h-5 {
grid-row: span 5;
}
.h-6 {
grid-row: span 6;
}
#media screen and (max-width: 850px) {
.container {
grid-template-columns: repeat(3,1fr);
}
.w-1, .w-2, .w-3, .w-4, .w-5, .w-6 {
grid-column: span 3;
}
}
/* -------------------------------------------------------------------------- */
.closebtn {
position: absolute;
top: 15px;
right: 35px;
color: #f1f1f1;
font-size: 40px;
font-weight: bold;
transition: 0.3s;
}
.closebtn:hover, .closebtn:focus {
color: #bbb;
text-decoration: none;
cursor: pointer;
}
.modal {
display: none;
position: fixed;
z-index: 10;
padding-top: 5%;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgb(0, 0, 0);
background-color: rgba(0, 0, 0, 0.9);
}
.modal-content {
/* leave it centered and just display info underneath -- maybe?
margin-left: 5%;
display: block; */
width: 80%;
max-width: 750px;
max-height: 650px;
object-fit: contain;
}
#caption {
margin: auto;
display: block;
width: 80%;
max-width: 700px;
text-align: center;
color: #ccc;
padding: 10px 0;
height: 150px;
}
.modal-content {
animation-name: zoom;
animation-duration: 0.5s;
}
#keyframes zoom {
from {
transform: scale(0)
} to {
transform: scale(1)
}
}
So, hopefully you'll see that nearly every thing else I'm programming functions as it should, but I can't quite get the data pulled from the API and displaying inside of the modal when opened. I believe my problem rests fully in the JS code and how I'm attempting to fetch and display the data from the API. Also, I have a div with an id of 'info' placed inside of the 'modal' div in my HTML, but I've got a sneaking suspicion that I'm not using that properly either. Let me know if there's any additional information required on my end.
So previously I asked your help to execute a behaviour for when clicking a container div, said div would change height, clicking it again would revert it back to normal, as would clicking a sibling container div.
I also added code for when clicking said div, the img inside would change data-src.
This img div (not the container) has a sibling that is just text. It would change state through css, when the container div is clicked, a bit like the container div itself.
When adding the code for the data-src change, I lost this ability and cant find out why.
Can you help me?
This is my code:
const allImages = document.querySelectorAll('.containertira .imgclasstosize');
const allContainers = document.querySelectorAll('.containertira');
allContainers.forEach(el => {
el.addEventListener('click', function(event) {
const thisImg = el.querySelector('.imgclasstosize');
const thisTxt = el.querySelector('.centered');
const sibling = thisImg.nextElementSibling; // Get the next sibiling
const bigSrc = thisImg.dataset.srcBig;
const allOtherImages = Array.from(allImages).filter(img => {
return img !== thisImg;
});
const isBig = thisImg.classList.contains('big');
1
if (isBig) {
thisImg.classList.remove('big');
thisTxt.classList.remove('left');
// reset to the small image URL
thisImg.src = thisImg.dataset.smallSrc;
} else {
// save the small image URL first:
if (!thisImg.dataset.smallSrc) {
thisImg.dataset.smallSrc = thisImg.src;
}
// change to the big image URL:
thisImg.src = bigSrc;
thisImg.classList.add('big');
thisTxt.classList.add('left');
sibling.classList.remove('hide');
}
allOtherImages.forEach(img => {
img.classList.remove('big');
// reset to the small image URL
if (img.dataset.smallSrc) {
img.src = img.dataset.smallSrc;
}
img.nextElementSibling.classList[isBig ? 'remove' : 'add']("hide");
});
});
}
);
.imgclasstosize{
width: 100%;
object-fit: cover;
position: relative;
}
img.imgclasstosize {
height: 80px;
border: 1px solid gray;
transition : 1.5s all ease;
}
img.imgclasstosize.big {
height: 100%;
transition: 1.5s all ease;
width: 70vw;
margin-left: auto;
}
.containertira {
position: relative;
text-align: center;
color: white;
display: flex;
justify-content: space-between;
}
.imgclasstosize {
transition: all ease 0.5s;
}
.imgclasstosize.big {
/* transform: scale(1.1); */
}
.centered {
opacity: 1;
transition: all ease 0.5s;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-family: 'blacker_pro_displayregular';
padding-top: 10px;
/* background: linear-gradient(90deg, rgba(33,37,41,1) 0%, rgba(255,255,255,0) 50%); */
color: white;
padding-left: 10px;
padding-bottom: 6.5px;
font-size: 5vh;
display:block;
pointer-events: none;
}
#media screen and (max-width: 960px) {.centered {font-size: 4.6vh;}}
#media screen and (max-width: 500px) {.centered {font-size: 3.5vh;}}
#media screen and (max-width: 375px) {.centered {font-size: 3vh;}}
.centered.left{
top: 0%;
left: 0%;
transform: translate(0%, 0%);
color: black;
padding-left: 10px;
padding-bottom: 6.5px;
transition: all ease 0.5s;
font-size: 7vh;
}
.imgclasstosize.big+.centered.hide {
opacity: 0;
}
<div class="containertira">
<img id="primeiraimagem" class="imgclasstosize" src="//images.impresa.pt/expresso/2021-07-23-1---Minho.png-0c586cc8" data-src-big="//images.impresa.pt/expresso/2021-07-23-960px_minho.png-3938db2f">
<div class="centered">Minho</div>
</div>
<br>
<div class="containertira">
<img id="primeiraimagem" class="imgclasstosize" src="//images.impresa.pt/expresso/2021-07-23-1---Minho.png-0c586cc8" data-src-big="//images.impresa.pt/expresso/2021-07-23-960px_minho.png-3938db2f">
<div class="centered">Minho</div>
</div>
Best regards everyone.
I can't seem to reproduce your error. Tried copying your code and pasting it on a separated HTML file and everything worked.
If you execute this snippet and look at the console, you will see that the classes and properties are changing accordingly to your code.
Maybe your code is executing before some library causing it to not execute something?
const allImages = document.querySelectorAll('.containertira .imgclasstosize');
const allContainers = document.querySelectorAll('.containertira');
allContainers.forEach(el => {
el.addEventListener('click', function(event) {
const thisImg = el.querySelector('.imgclasstosize');
const thisTxt = el.querySelector('.centered');
const sibling = thisImg.nextElementSibling; // Get the next sibiling
const bigSrc = thisImg.dataset.srcBig;
const allOtherImages = Array.from(allImages).filter(img => {
return img !== thisImg;
});
const isBig = thisImg.classList.contains('big');
1
if (isBig) {
thisImg.classList.remove('big');
thisTxt.classList.remove('left');
// reset to the small image URL
thisImg.src = thisImg.dataset.smallSrc;
} else {
// save the small image URL first:
if (!thisImg.dataset.smallSrc) {
thisImg.dataset.smallSrc = thisImg.src;
}
// change to the big image URL:
thisImg.src = bigSrc;
thisImg.classList.add('big');
thisTxt.classList.add('left');
sibling.classList.remove('hide');
}
allOtherImages.forEach(img => {
img.classList.remove('big');
// reset to the small image URL
if (img.dataset.smallSrc) {
img.src = img.dataset.smallSrc;
}
img.nextElementSibling.classList[isBig ? 'remove' : 'add']("hide");
});
});
}
);
.containertira {border:1px black solid;}
<div class="containertira">
<img id="primeiraimagem" class="imgclasstosize" src="img/Tiras/1 - Minho.png" data-src-big="img/INFO joao/joao 2/joao 2/960 px/960px_minho.png">
<div class="centered">Minho</div>
</div>
<div class="containertira">
<img id="primeiraimagem" class="imgclasstosize" src="img/Tiras/1 - Minho.png" data-src-big="img/INFO joao/joao 2/joao 2/960 px/960px_minho.png">
<div class="centered">Minho2</div>
</div>
<div class="containertira">
<img id="primeiraimagem" class="imgclasstosize" src="img/Tiras/1 - Minho.png" data-src-big="img/INFO joao/joao 2/joao 2/960 px/960px_minho.png">
<div class="centered">Minho3</div>
</div>
<div class="containertira">
<img id="primeiraimagem" class="imgclasstosize" src="img/Tiras/1 - Minho.png" data-src-big="img/INFO joao/joao 2/joao 2/960 px/960px_minho.png">
<div class="centered">Minho4</div>
</div>
I am making a vanilla js carousel. I have laid out basic previous and next functionality using js along with html and css.
Now I tried to use css-animations (keyframes) to do left and right slide-in/slide-out animations but the code became messy for me. So here I am asking that what minimal changes would be needed to get the same animation effects in this implementation ?
Will you go for pure JS based or pure CSS based or a mix to do the same ?
My goal is get proper animation with minimal code.
(function () {
let visibleIndex = 0;
let carousalImages = document.querySelectorAll(".carousal__image");
let totalImages = [...carousalImages].length;
function makeNextVisible() {
visibleIndex++;
if (visibleIndex > totalImages - 1) {
visibleIndex = 0;
}
resetVisible();
renderVisible();
}
function makePrevVisible() {
visibleIndex--;
if (visibleIndex < 0) {
visibleIndex = totalImages - 1;
}
resetVisible();
renderVisible();
}
function resetVisible() {
for (let index = 0; index < totalImages; index++) {
carousalImages[index].className = "carousal__image";
}
}
function renderVisible() {
carousalImages[visibleIndex].className = "carousal__image--visible";
}
function renderCarousel({ autoplay = false, autoplayTime = 1000 } = {}) {
if (autoplay) {
[...document.querySelectorAll("button")].forEach(
(btn) => (btn.style.display = "none")
);
setInterval(() => {
makeNextVisible();
}, autoplayTime);
} else renderVisible();
}
renderCarousel();
// Add {autoplay:true} as argument to above to autplay the carousel.
this.makeNextVisible = makeNextVisible;
this.makePrevVisible = makePrevVisible;
})();
.carousal {
display: flex;
align-items: center;
}
.carousal__wrapper {
width: 500px;
height: 400px;
}
.carousal__images {
display: flex;
overflow: hidden;
list-style-type: none;
padding: 0;
}
.carousal__image--visible {
position: relative;
}
.carousal__image {
display: none;
}
<div class='carousal'>
<div class='carousal__left'>
<button onclick='makePrevVisible()'>Left</button>
</div>
<section class='carousal__wrapper'>
<ul class='carousal__images'>
<li class='carousal__image'>
<img src='https://fastly.syfy.com/sites/syfy/files/styles/1200x680/public/2018/03/dragon-ball-super-goku-ultra-instinct-mastered-01.jpg?offset-x=0&offset-y=0' alt='UI Goku' / width='500' height='400'/>
</li>
<li class='carousal__image'>
<img src='https://www.theburnin.com/wp-content/uploads/2019/01/super-broly-3.png' alt='Broly Legendary' width='500' height='400'/>
</li>
<li class='carousal__image'>
<img src='https://lh3.googleusercontent.com/proxy/xjEVDYoZy8-CTtPZGsQCq2PW7I-1YM5_S5GPrAdlYL2i4SBoZC-zgtg2r3MqH85BubDZuR3AAW4Gp6Ue-B-T2Z1FkKW99SPHwAce5Q_unUpwtm4' alt='Vegeta Base' width='500' height='400'/>
</li>
<li class='carousal__image'>
<img src='https://am21.mediaite.com/tms/cnt/uploads/2018/09/GohanSS2.jpg' alt='Gohan SS2' width='500' height='400'/>
</li>
</ul>
</section>
<div class='carousal__right'>
<button onclick='makeNextVisible()'>Right</button>
</div>
</div>
Updated codepen with feedback from the below answers and minor additional functionalities = https://codepen.io/lapstjup/pen/RwoRWVe
I think the trick is pretty simple. ;)
You should not move one or two images at the same time. Instead you should move ALL images at once.
Let's start with the CSS:
.carousal {
position: relative;
display: block;
}
.carousal__wrapper {
width: 500px;
height: 400px;
position: relative;
display: block;
overflow: hidden;
margin: 0;
padding: 0;
}
.carousal__wrapper,
.carousal__images {
transform: translate3d(0, 0, 0);
}
.carousal__images {
position: relative;
top: 0;
left: 0;
display: block;
margin-left: auto;
margin-right: auto;
}
.carousal__image {
float: left;
height: 100%;
min-height: 1px;
}
2nd step would be to calculate the maximum width for .carousal__images. For example in your case 4 * 500px makes 2000px. This value must be added to your carousal__images as part of the style attribute style="width: 2000px".
3rd step would be to calculate the next animation point and using transform: translate3d. We start at 0 and want the next slide which means that we have slide to the left. We also know the width of one slide. So the result would be -500px which also has to be added the style attribute of carousal__images => style="width: 2000px; transform: translate3d(-500px, 0px, 0px);"
That's it.
Link to my CodePen: Codepen for Basic Carousel with Autoplay
Try this. First stack all the images next to each other in a div and only show a single image at a time by setting overflow property to hidden for the div. Next, add event listeners to the buttons. When a bottom is clicked, the div containing the images is translated by -{size of an image} * {image number} on the x axis. For smooth animation, add transition: all 0.5s ease-in-out; to the div.
When someone clicks left arrow on the first image, the slide should display the last image. So for that counter is set to {number of images} - 1 and image is translated to left size * counter px.
For every click on the right arrow, the counter is incremented by 1 and slide is moved left. For every click on the left arrow, the counter is decremented by 1.
Slide.style.transform = "translateX(" + (-size * counter) + "px)"; this is the condition which is deciding how much the slide should be translated.
const PreviousButton = document.querySelector(".Previous-Button");
const NextButton = document.querySelector(".Next-Button");
const Images = document.querySelectorAll("img");
const Slide = document.querySelector(".Images");
const size = Slide.clientWidth;
var counter = 0;
// Arrow Click Events
PreviousButton.addEventListener("click", Previous);
NextButton.addEventListener("click", Next);
function Previous() {
counter--;
if (counter < 0) {
counter = Images.length - 1;
}
Slide.style.transform = "translateX(" + (-size * counter) + "px)";
}
function Next() {
counter++;
if (counter >= Images.length) {
counter = 0;
}
Slide.style.transform = "translateX(" + (-size * counter) + "px)";
}
* {
margin: 0px;
padding: 0px;
box-sizing: border-box;
}
.Container {
width: 60%;
margin: 0px auto;
margin-top: 90px;
overflow: hidden;
position: relative;
}
.Container .Images img {
width: 100%;
}
.Images {
transition: all 0.5s ease-in-out;
}
.Container .Previous-Button {
position: absolute;
background: transparent;
border: 0px;
outline: 0px;
top: 50%;
left: 20px;
transform: translateY(-50%);
filter: invert(80%);
z-index: 1;
}
.Container .Next-Button {
position: absolute;
background: transparent;
border: 0px;
outline: 0px;
top: 50%;
right: 20px;
transform: translateY(-50%);
filter: invert(80%);
z-index: 1;
}
.Container .Images {
display: flex;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link href="https://fonts.googleapis.com/css2?family=Cabin&family=Poppins&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css">
<title>Carousel</title>
</head>
<body>
<div class="Container">
<button class="Previous-Button">
<svg style = "transform: rotate(180deg);" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M8.122 24l-4.122-4 8-8-8-8 4.122-4 11.878 12z"/></svg>
</button>
<button class="Next-Button">
<svg xmlns="http://www.w3.org/2000/svg" width = "24" height = "24" viewBox = "0 0 24 24"><path d="M8.122 24l-4.122-4 8-8-8-8 4.122-4 11.878 12z"/></svg>
</button>
<div class="Images">
<img src="https://source.unsplash.com/1280x720/?nature">
<img src="https://source.unsplash.com/1280x720/?water">
<img src="https://source.unsplash.com/1280x720/?rock">
<img src="https://source.unsplash.com/1280x720/?abstract">
<img src="https://source.unsplash.com/1280x720/?nature">
<img src="https://source.unsplash.com/1280x720/?trees">
<img src="https://source.unsplash.com/1280x720/?human">
<img src="https://source.unsplash.com/1280x720/?tech">
</div>
</div>
<script src="main.js"></script>
</body>
</html>
I'm learning JS by following Wes Bos's class. I'm trying to select buttons and display information every time the user clicks on them. So I add an event listener, however, the fallback function seems to be executed automatically when it is located inside the event listener. I don't understand why 'hello' is displayed automatically while the function youClickTheButton is executed only when the user clicks on a button (see the below code).
Why does this happen?
const myButtons = document.querySelectorAll('.cards button');
const modalOuter = document.querySelector('.modal-outer');
function youClickTheButton(event) {
console.log(event);
modalOuter.classList.add('open');
}
myButtons.forEach(item => item.addEventListener('click', youClickTheButton));
myButtons.forEach(item => item.addEventListener('click', console.log('hello')));
// whenever you click on the button it will open a pop up with a picture with the description
// whenever you click outside of this pop up it should close itself use .closest()
// populate the modal with name and description of the card so you don't have to modify the .html file
// you could also close it by simply pressing escape
const myButtons = document.querySelectorAll('.cards button');
const modalOuter = document.querySelector('.modal-outer');
function youClickTheButton(event) {
console.log(event);
modalOuter.classList.add('open');
}
myButtons.forEach(item => item.addEventListener('click', youClickTheButton));
myButtons.forEach(item => item.addEventListener('click', console.log('hello')));
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title></title>
<link rel="stylesheet" href="../../base.css">
</head>
<body>
<div class="cards">
<div class="card number1" data-description="Wes is cool">
<img src="https://picsum.photos/200?random=1" alt="Wes Bos">
<h2>Wes Bos</h2>
<button>Learn more →</button>
</div>
<div class="card number2" data-description="Scott is neat!">
<img src="https://picsum.photos/200?random=2" alt="Wes Bos">
<h2>Scott Tolinski</h2>
<button>Learn more →</button>
</div>
<div class="card number3" data-description="Kait is beautiful!">
<img src="https://picsum.photos/200?random=3" alt="Wes Bos">
<h2>Kait Bos</h2>
<button>Learn more →</button>
</div>
<div class="card number4" data-description="Snickers is a dog!">
<img src="https://picsum.photos/200?random=4" alt="Wes Bos">
<h2>Snickers the dog</h2>
<button>Learn more →</button>
</div>
</div>
<div class="modal-outer ">
<div class="modal-inner ">
<p>You clicked on the 1st one</p>
</div>
</div>
<style>
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
grid-gap: 20px;
padding: 2rem;
}
.card {
background: white;
padding: 1rem;
border-radius: 2px;
}
.card img {
width: 100%;
}
.card h2 {
color: black;
}
.modal-outer {
display: grid;
background: hsla(50, 100%, 50%, 0.7);
position: fixed;
height: 100vh;
width: 100vw;
top: 0;
left: 0;
justify-content: center;
align-items: center;
/* Hide this modal until we need it */
opacity: 0;
pointer-events: none;
transition: opacity 0.2s;
}
.modal-outer img {
width: 100%;
}
.modal-outer.open {
opacity: 1;
pointer-events: all;
}
.modal-inner {
max-width: 600px;
min-width: 400px;
padding: 2rem;
border-radius: 5px;
min-height: 200px;
background: white;
transform: translateY(-200%);
transition: transform 2s;
}
.modal-outer.open .modal-inner {
transform: translateY(0);
}
</style>
<script src="./click-outside_5.js"></script>
</body>
</html>
Check the difference between your two item.addEventListener()s. In the first one, you pass down a function, while in the second one, you call one (no parentheses vs. parentheses).
If you pass down a callback function (i.e. first case), you don't invoke it immediately, you just say that "Here is this function I created, run it every time someone clicks on any of these buttons."
If you want to log from your second function, you need to pass a function, not the result of the function:
myButtons.forEach(item => item.addEventListener('click', () => console.log('hello')))
// OR
function logHello() {
return console.log('hello')
}
myButtons.forEach(item => item.addEventListener('click', logHello));
You can read more about it on MDN.
Anyway, you should not loop over the buttons twice, but I think you're just testing things out at the moment. console.log('hello') could be in your youClickTheButton() function.
i want that before the actual image gets loaded a spinner should be shown so that my website can load faster
window.addEventListener('load', function(){
var allimages= document.getElementsByTagName('img');
for (var i=0; i<allimages.length; i++) {
if (allimages[i].getAttribute('data-src')) {
allimages[i].setAttribute('src', allimages[i].getAttribute('data-src'));
}
}
}, false)
<img src="https://loremflickr.com/400/600" data-src="images/banguet-hall-location-icon.png" class="secEightImg" />
<img src="http://www.jettools.com/images/animated_spinner.gif" data-src="images/banguet-hall-location-icon.png" class="secEightImg" />
<script>
</script>
the problem is the actual image is shown two times and instead of spinner a broken image (which is shown when no image is found) is shown first
please help
If you want a spinner per image, just add the spinner URL as the initial src attribute for all of them:
<img src="spinner.gif" data-src="actual-image.png" />
Then, once the page loads, change all those src for the real URL (data-src) and listen for the load and error events on each image.
For each of them, add a .loaded class or .error class to the images and style them as you want. For example, you could hide the ones that could not be loaded, show a custom "error" image (using background-image) or style them as you wish, like in this example:
function imageLoaded(e) {
updateImage(e.target, 'loaded');
}
function imageError(e) {
updateImage(e.target, 'error');
}
function updateImage(img, classname) {
// Add the right class:
img.classList.add(classname);
// Remove the data-src attribute:
img.removeAttribute('data-src');
// Remove both listeners:
img.removeEventListener('load', imageLoaded);
img.removeEventListener('error', imageError);
}
window.addEventListener('load', () => {
Array.from(document.getElementsByTagName('img')).forEach(img => {
const src = img.getAttribute('data-src');
if (src) {
// Listen for both events:
img.addEventListener('load', imageLoaded);
img.addEventListener('error', imageError);
// Just to simulate a slow network:
setTimeout(() => {
img.setAttribute('src', src);
}, 2000 + Math.random() * 2000);
}
});
})
html,
body {
margin: 0;
height: 100%;
}
.images {
width: 100%;
height: 100%;
background: #000;
display: flex;
align-items: center;
overflow-x: scroll;
}
.margin-fix {
border-right: 1px solid transparent;
height: 16px;
}
img {
width: 16px;
height: 16px;
margin: 0 64px;
}
img.loaded {
width: auto;
height: 100%;
margin: 0;
}
img.error {
background: red;
border-radius: 100%;
/* You could add a custom "error" image here using background-image */
}
<div class="images">
<img
src="https://i.stack.imgur.com/RvfGz.gif"
data-src="https://d39a3h63xew422.cloudfront.net/wp-content/uploads/2014/07/20145029/driven-by-design-the-incomparable-lancia-stratos-1476934711918-1000x573.jpg" />
<img
src="https://i.stack.imgur.com/RvfGz.gif"
data-src="https://car-images.bauersecure.com/pagefiles/76591/1752x1168/ford_racing_puma_01.jpg?mode=max&quality=90&scale=down" />
<img
src="https://i.stack.imgur.com/RvfGz.gif"
data-src="http://doesntexist.com/image.jpg" />
<span class="margin-fix"></span>
</div>
The problem is that the spinner image you use initially might take some time to load.
One solution would be to use a data URI, so instead of:
<img src="https://i.stack.imgur.com/RvfGz.gif" data-src="actual-image.png" />
You would have:
<img src="data:image/gif;base64,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cXVlcnknPmZ1bmN0aW9uLm15c3FsLXF1ZXJ5PC9hPl06IENhbid0IGNvbm5lY3QgdG8gbG9jYWwgTXlTUUwgc2VydmVyIHRocm91Z2ggc29ja2V0ICcvdmFyL3J1bi9teXNxbGQvbXlzcWxkLnNvY2snICgyKSBpbiA8Yj4vaG9tZS9hamF4bG9hZC93d3cvbGlicmFpcmllcy9jbGFzcy5teXNxbC5waHA8L2I+IG9uIGxpbmUgPGI+Njg8L2I+PGJyIC8+CjxiciAvPgo8Yj5XYXJuaW5nPC9iPjogIG15c3FsX3F1ZXJ5KCkgWzxhIGhyZWY9J2Z1bmN0aW9uLm15c3FsLXF1ZXJ5Jz5mdW5jdGlvbi5teXNxbC1xdWVyeTwvYT5dOiBBIGxpbmsgdG8gdGhlIHNlcnZlciBjb3VsZCBub3QgYmUgZXN0YWJsaXNoZWQgaW4gPGI+L2hvbWUvYWpheGxvYWQvd3d3L2xpYnJhaXJpZXMvY2xhc3MubXlzcWwucGhwPC9iPiBvbiBsaW5lIDxiPjY4PC9iPjxiciAvPgo8YnIgLz4KPGI+V2FybmluZzwvYj46ICBteXNxbF9xdWVyeSgpIFs8YSBocmVmPSdmdW5jdGlvbi5teXNxbC1xdWVyeSc+ZnVuY3Rpb24ubXlzcWwtcXVlcnk8L2E+XTogQ2FuJ3QgY29ubmVjdCB0byBsb2NhbCBNeVNRTCBzZXJ2ZXIgdGhyb3VnaCBzb2NrZXQgJy92YXIvcnVuL215c3FsZC9teXNxbGQuc29jaycgKDIpIGluIDxiPi9ob21lL2FqYXhsb2FkL3d3dy9saWJyYWlyaWVzL2NsYXNzLm15c3FsLnBocDwvYj4gb24gbGluZSA8Yj42ODwvYj48YnIgLz4KPGJyIC8+CjxiPldhcm5pbmc8L2I+OiAgbXlzcWxfcXVlcnkoKSBbPGEgaHJlZj0nZnVuY3Rpb24ubXlzcWwtcXVlcnknPmZ1bmN0aW9uLm15c3FsLXF1ZXJ5PC9hPl06IEEgbGluayB0byB0aGUgc2VydmVyIGNvdWxkIG5vdCBiZSBlc3RhYmxpc2hlZCBpbiA8Yj4vaG9tZS9hamF4bG9hZC93d3cvbGlicmFpcmllcy9jbGFzcy5teXNxbC5waHA8L2I+IG9uIGxpbmUgPGI+Njg8L2I+PGJyIC8+CjxiciAvPgo8Yj5XYXJuaW5nPC9iPjogIG15c3FsX3F1ZXJ5KCkgWzxhIGhyZWY9J2Z1bmN0aW9uLm15c3FsLXF1ZXJ5Jz5mdW5jdGlvbi5teXNxbC1xdWVyeTwvYT5dOiBDYW4ndCBjb25uZWN0IHRvIGxvY2FsIE15U1FMIHNlcnZlciB0aHJvdWdoIHNvY2tldCAnL3Zhci9ydW4vbXlzcWxkL215c3FsZC5zb2NrJyAoMikgaW4gPGI+L2hvbWUvYWpheGxvYWQvd3d3L2xpYnJhaXJpZXMvY2xhc3MubXlzcWwucGhwPC9iPiBvbiBsaW5lIDxiPjY4PC9iPjxiciAvPgo8YnIgLz4KPGI+V2FybmluZzwvYj46ICBteXNxbF9xdWVyeSgpIFs8YSBocmVmPSdmdW5jdGlvbi5teXNxbC1xdWVyeSc+ZnVuY3Rpb24ubXlzcWwtcXVlcnk8L2E+XTogQSBsaW5rIHRvIHRoZSBzZXJ2ZXIgY291bGQgbm90IGJlIGVzdGFibGlzaGVkIGluIDxiPi9ob21lL2FqYXhsb2FkL3d3dy9saWJyYWlyaWVzL2NsYXNzLm15c3FsLnBocDwvYj4gb24gbGluZSA8Yj42ODwvYj48YnIgLz4K" data-src="actual-image.png" />
As you can see, your HTML document will grow fast using this approach, which is a problem.
A better approach might be to use CSS to add the spinner so that you only include the data URI once. To do that, you need to add an empty src attribute to your images initially anyway:
<img src data-src="actual-image.png" />
Or:
<img src="" data-src="actual-image.png" />
If you don't put it, the image will have an annoying gray border you can't get rid of until you add a src attribute.
function imageLoaded(e) {
updateImage(e.target, 'loaded');
}
function imageError(e) {
updateImage(e.target, 'error');
}
function updateImage(img, classname) {
// Add the right class:
img.classList.add(classname);
// Remove the data-src attribute:
img.removeAttribute('data-src');
// Remove both listeners:
img.removeEventListener('load', imageLoaded);
img.removeEventListener('error', imageError);
}
window.addEventListener('load', () => {
Array.from(document.getElementsByTagName('img')).forEach(img => {
const src = img.getAttribute('data-src');
if (src) {
// Listen for both events:
img.addEventListener('load', imageLoaded);
img.addEventListener('error', imageError);
// Just to simulate a slow network:
setTimeout(() => {
img.setAttribute('src', src);
}, 2000 + Math.random() * 2000);
}
});
})
html,
body {
margin: 0;
height: 100%;
}
.images {
width: 100%;
height: 100%;
background: #000;
display: flex;
align-items: center;
overflow-x: scroll;
}
.margin-fix {
border-right: 1px solid transparent;
height: 16px;
}
img {
width: 16px;
height: 16px;
margin: 0 64px;
background-image: url("data:image/gif;base64,R0lGODlhEAAQAPYAAAAAAP///yoqKmpqap6enr6+vrq6upCQkFxcXCIiIlpaWtra2tbW1s7OzsjIyMDAwJSUlEREROLi4oyMjBISEhAQEDw8PHR0dK6urqCgoEBAQC4uLsTExOjo6HJyclRUVKKiooKCghwcHHh4ePDw8JaWlmJiYpiYmEhISLi4uPT09E5OTmhoaObm5vj4+BYWFgoKCoaGhnp6eggICHx8fFZWVgQEBAICAj4+PjQ0NAYGBigoKFBQUA4ODiwsLBoaGiAgIDAwMDg4OEJCQh4eHiYmJgwMDCQkJISEhEpKSkxMTLKysqysrKSkpJycnLy8vMLCwjo6OoiIiMzMzBQUFNTU1HBwcKamptLS0uDg4F5eXrCwsOzs7HZ2dpqamsrKyjY2NjIyMhgYGEZGRoCAgGxsbGBgYKioqG5ubrS0tLa2ttzc3FhYWO7u7vLy8lJSUvr6+mRkZNjY2Orq6sbGxoqKitDQ0Pb29o6Ojt7e3qqqqpKSkn5+fgAAAAAAAAAAACH+GkNyZWF0ZWQgd2l0aCBhamF4bG9hZC5pbmZvACH5BAAKAAAAIf8LTkVUU0NBUEUyLjADAQAAACwAAAAAEAAQAAAHjYAAgoOEhYUbIykthoUIHCQqLoI2OjeFCgsdJSsvgjcwPTaDAgYSHoY2FBSWAAMLE4wAPT89ggQMEbEzQD+CBQ0UsQA7RYIGDhWxN0E+ggcPFrEUQjuCCAYXsT5DRIIJEBgfhjsrFkaDERkgJhswMwk4CDzdhBohJwcxNB4sPAmMIlCwkOGhRo5gwhIGAgAh+QQACgABACwAAAAAEAAQAAAHjIAAgoOEhYU7A1dYDFtdG4YAPBhVC1ktXCRfJoVKT1NIERRUSl4qXIRHBFCbhTKFCgYjkII3g0hLUbMAOjaCBEw9ukZGgidNxLMUFYIXTkGzOmLLAEkQCLNUQMEAPxdSGoYvAkS9gjkyNEkJOjovRWAb04NBJlYsWh9KQ2FUkFQ5SWqsEJIAhq6DAAIBACH5BAAKAAIALAAAAAAQABAAAAeJgACCg4SFhQkKE2kGXiwChgBDB0sGDw4NDGpshTheZ2hRFRVDUmsMCIMiZE48hmgtUBuCYxBmkAAQbV2CLBM+t0puaoIySDC3VC4tgh40M7eFNRdH0IRgZUO3NjqDFB9mv4U6Pc+DRzUfQVQ3NzAULxU2hUBDKENCQTtAL9yGRgkbcvggEq9atUAAIfkEAAoAAwAsAAAAABAAEAAAB4+AAIKDhIWFPygeEE4hbEeGADkXBycZZ1tqTkqFQSNIbBtGPUJdD088g1QmMjiGZl9MO4I5ViiQAEgMA4JKLAm3EWtXgmxmOrcUElWCb2zHkFQdcoIWPGK3Sm1LgkcoPrdOKiOCRmA4IpBwDUGDL2A5IjCCN/QAcYUURQIJIlQ9MzZu6aAgRgwFGAFvKRwUCAAh+QQACgAEACwAAAAAEAAQAAAHjIAAgoOEhYUUYW9lHiYRP4YACStxZRc0SBMyFoVEPAoWQDMzAgolEBqDRjg8O4ZKIBNAgkBjG5AAZVtsgj44VLdCanWCYUI3txUPS7xBx5AVDgazAjC3Q3ZeghUJv5B1cgOCNmI/1YUeWSkCgzNUFDODKydzCwqFNkYwOoIubnQIt244MzDC1q2DggIBACH5BAAKAAUALAAAAAAQABAAAAeJgACCg4SFhTBAOSgrEUEUhgBUQThjSh8IcQo+hRUbYEdUNjoiGlZWQYM2QD4vhkI0ZWKCPQmtkG9SEYJURDOQAD4HaLuyv0ZeB4IVj8ZNJ4IwRje/QkxkgjYz05BdamyDN9uFJg9OR4YEK1RUYzFTT0qGdnduXC1Zchg8kEEjaQsMzpTZ8avgoEAAIfkEAAoABgAsAAAAABAAEAAAB4iAAIKDhIWFNz0/Oz47IjCGADpURAkCQUI4USKFNhUvFTMANxU7KElAhDA9OoZHH0oVgjczrJBRZkGyNpCCRCw8vIUzHmXBhDM0HoIGLsCQAjEmgjIqXrxaBxGCGw5cF4Y8TnybglprLXhjFBUWVnpeOIUIT3lydg4PantDz2UZDwYOIEhgzFggACH5BAAKAAcALAAAAAAQABAAAAeLgACCg4SFhjc6RhUVRjaGgzYzRhRiREQ9hSaGOhRFOxSDQQ0uj1RBPjOCIypOjwAJFkSCSyQrrhRDOYILXFSuNkpjggwtvo86H7YAZ1korkRaEYJlC3WuESxBggJLWHGGFhcIxgBvUHQyUT1GQWwhFxuFKyBPakxNXgceYY9HCDEZTlxA8cOVwUGBAAA7AAAAAAAAAAAAPGJyIC8+CjxiPldhcm5pbmc8L2I+OiAgbXlzcWxfcXVlcnkoKSBbPGEgaHJlZj0nZnVuY3Rpb24ubXlzcWwtcXVlcnknPmZ1bmN0aW9uLm15c3FsLXF1ZXJ5PC9hPl06IENhbid0IGNvbm5lY3QgdG8gbG9jYWwgTXlTUUwgc2VydmVyIHRocm91Z2ggc29ja2V0ICcvdmFyL3J1bi9teXNxbGQvbXlzcWxkLnNvY2snICgyKSBpbiA8Yj4vaG9tZS9hamF4bG9hZC93d3cvbGlicmFpcmllcy9jbGFzcy5teXNxbC5waHA8L2I+IG9uIGxpbmUgPGI+Njg8L2I+PGJyIC8+CjxiciAvPgo8Yj5XYXJuaW5nPC9iPjogIG15c3FsX3F1ZXJ5KCkgWzxhIGhyZWY9J2Z1bmN0aW9uLm15c3FsLXF1ZXJ5Jz5mdW5jdGlvbi5teXNxbC1xdWVyeTwvYT5dOiBBIGxpbmsgdG8gdGhlIHNlcnZlciBjb3VsZCBub3QgYmUgZXN0YWJsaXNoZWQgaW4gPGI+L2hvbWUvYWpheGxvYWQvd3d3L2xpYnJhaXJpZXMvY2xhc3MubXlzcWwucGhwPC9iPiBvbiBsaW5lIDxiPjY4PC9iPjxiciAvPgo8YnIgLz4KPGI+V2FybmluZzwvYj46ICBteXNxbF9xdWVyeSgpIFs8YSBocmVmPSdmdW5jdGlvbi5teXNxbC1xdWVyeSc+ZnVuY3Rpb24ubXlzcWwtcXVlcnk8L2E+XTogQ2FuJ3QgY29ubmVjdCB0byBsb2NhbCBNeVNRTCBzZXJ2ZXIgdGhyb3VnaCBzb2NrZXQgJy92YXIvcnVuL215c3FsZC9teXNxbGQuc29jaycgKDIpIGluIDxiPi9ob21lL2FqYXhsb2FkL3d3dy9saWJyYWlyaWVzL2NsYXNzLm15c3FsLnBocDwvYj4gb24gbGluZSA8Yj42ODwvYj48YnIgLz4KPGJyIC8+CjxiPldhcm5pbmc8L2I+OiAgbXlzcWxfcXVlcnkoKSBbPGEgaHJlZj0nZnVuY3Rpb24ubXlzcWwtcXVlcnknPmZ1bmN0aW9uLm15c3FsLXF1ZXJ5PC9hPl06IEEgbGluayB0byB0aGUgc2VydmVyIGNvdWxkIG5vdCBiZSBlc3RhYmxpc2hlZCBpbiA8Yj4vaG9tZS9hamF4bG9hZC93d3cvbGlicmFpcmllcy9jbGFzcy5teXNxbC5waHA8L2I+IG9uIGxpbmUgPGI+Njg8L2I+PGJyIC8+CjxiciAvPgo8Yj5XYXJuaW5nPC9iPjogIG15c3FsX3F1ZXJ5KCkgWzxhIGhyZWY9J2Z1bmN0aW9uLm15c3FsLXF1ZXJ5Jz5mdW5jdGlvbi5teXNxbC1xdWVyeTwvYT5dOiBDYW4ndCBjb25uZWN0IHRvIGxvY2FsIE15U1FMIHNlcnZlciB0aHJvdWdoIHNvY2tldCAnL3Zhci9ydW4vbXlzcWxkL215c3FsZC5zb2NrJyAoMikgaW4gPGI+L2hvbWUvYWpheGxvYWQvd3d3L2xpYnJhaXJpZXMvY2xhc3MubXlzcWwucGhwPC9iPiBvbiBsaW5lIDxiPjY4PC9iPjxiciAvPgo8YnIgLz4KPGI+V2FybmluZzwvYj46ICBteXNxbF9xdWVyeSgpIFs8YSBocmVmPSdmdW5jdGlvbi5teXNxbC1xdWVyeSc+ZnVuY3Rpb24ubXlzcWwtcXVlcnk8L2E+XTogQSBsaW5rIHRvIHRoZSBzZXJ2ZXIgY291bGQgbm90IGJlIGVzdGFibGlzaGVkIGluIDxiPi9ob21lL2FqYXhsb2FkL3d3dy9saWJyYWlyaWVzL2NsYXNzLm15c3FsLnBocDwvYj4gb24gbGluZSA8Yj42ODwvYj48YnIgLz4K");
}
img.loaded {
width: auto;
height: 100%;
margin: 0;
}
img.error {
background: red;
border-radius: 100%;
/* You could add a custom "error" image here using background-image */
}
<div class="images">
<img
src
data-src="https://d39a3h63xew422.cloudfront.net/wp-content/uploads/2014/07/20145029/driven-by-design-the-incomparable-lancia-stratos-1476934711918-1000x573.jpg" />
<img
src
data-src="https://car-images.bauersecure.com/pagefiles/76591/1752x1168/ford_racing_puma_01.jpg?mode=max&quality=90&scale=down" />
<img
src
data-src="http://doesntexist.com/image.jpg" />
<span class="margin-fix"></span>
</div>
Other approaches might be to just hide the images initially with display: none for example, in which case you might want a wrapper around them to show something like an empty box to indicate the users that something will be shown in there, or even a spinner made with other elements if you want/need to get fancy.
What you can do is something like:
$(".sly-main-slider div img").attr('src', "#");
$(".sly-main-slider").addClass("loading");
$(".sly-main-slider div").hide();
$(".sly-main-slider div img").load(function() {
$(".sly-main-slider div").show();
$(".sly-main-slider").removeClass("loading");
}).attr('src', "https://loremflickr.com/400/600");
.loading {
position: absolute;
top: 10%;
left: 10%;
z-index: 2000;
background: url(http://1.bp.blogspot.com/-nfXo9GWbDtM/VOn0vr4yLMI/AAAAAAAABCA/dDNgd7_QCFo/s1600/block-loader.gif) no-repeat center center;
height: 32px;
width: 32px;
}
.loading:after {
position: absolute;
top: 8px;
left: 40px;
content: 'Loading...';
}
body {
position: relative;
display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class='sly-main-slider'>
<div>
<img src="https://loremflickr.com/400/600" alt="" />
</div>
</div>