I am using Materialize CSS and have the "Material Box" which is a lightbox plugin. I want all of the thumbnails to be the same size. When clicked I want the full photo to load.
I am using onclick to change the src. How do I change it back to the thumbnail when the large photo closes (either with a click or the escape key)?
<div class="col s6 m3">
<img class="materialboxed responsive-img" src="images/thumb1.jpg" onclick='this.src="images/photo1"'>
</div>
Material Box Javascript
document.addEventListener('DOMContentLoaded', function() {
var elems = document.querySelectorAll('.materialboxed');
var options = {}
var instances = M.Materialbox.init(elems, options);
});
// Or with jQuery
$(document).ready(function(){
$('.materialboxed').materialbox();
});
Materializecss.com - https://materializecss.com/media.html
I haven't found an easy other way of achieving the lightbox effect with cropped square thumbnails. Any advice would be greatly appreciated!
Here is one implementation of what you want, keeping track of the image click state.
$(document).ready(function(){
$('.materialboxed').materialbox();
// Image sources
const srcThumb = '/images/thumb1.jpg'
const srcPhoto = '/images/photo1.jpg'
// Click state
var clicked = false
// Get image element and bind click event
const img = $('.materialboxed')
img.on('click', function() {
img.attr('src', clicked ? srcPhoto : srcThumb)
clicked = !clicked
})
});
No need to rely on onclick in this case.
Materialize is already binding onclick for those images.
And it provides the following native methods we can use for doing exactly what you want using pure JS (no jQuery):
onOpenStart Function null Callback function called before materialbox is opened.
onCloseEnd Function null Callback function called after materialbox is closed.
In this example below, we assume there is a normal materialboxed photo gallery containing thumbnails named thumb_whatever.jpg, for example. But we're also serving the original sized photo named whatever.jpg in the same directory.
Then we're changing src attribute dynamically removing the thumb_ prefix to get the original image, which in this case will be imediately lightboxed by materialize.
And after closing the lightbox, the src attribute is being set back again without the thumb_ prefix.
We do that while initializing Materialbox:
// Initializing Materialbox
const mb = document.querySelectorAll('.materialboxed')
M.Materialbox.init(mb, {
onOpenStart: (el) => {
var src = el.getAttribute('src') // get the src
var path = src.substring(0,src.lastIndexOf('/')) // get the path from the src
var fileName = src.substring(src.lastIndexOf('/')).replace('thumb_','') // get the filename and removes 'thumb_' prefix
var newSrc = path+fileName // re-assemble without the 'thumb_' prefix
el.setAttribute('src', newSrc)
},
onCloseEnd: (el) => {
var src = el.getAttribute('src') // get the src
var path = src.substring(0,src.lastIndexOf('/')) // get the path from the src
var fileName = src.substring(src.lastIndexOf('/')).replace('/', '/thumb_') // get the filename and adds 'thumb_' prefix
var newSrc = path+fileName // re-assemble with the 'thumb_' prefix
el.setAttribute('src', newSrc)
}
})
This solution is also working like a charm for me, crossplatform.
Related
I am building an image gallery that is populated from a JSON file. Everything works as intended currently, but as of right now there is no pre-loading of content after the initial page load. What I would like to happen is after the "view more" button is clicked I will have some "loading" text show, the batch of images will preload, the "loading" text will disappear, then the images will be added to the page once all items have loaded.
Here is the section of the code that involves the JSON fetch request and the building of elements on the page:
var HTML = '';
var itemsStart = 6; // Starting number of items on page.
var itemsAdd = 9; // Number of items to add to page at a time via button click.
var pItems = document.getElementById('pItems');
var pWrapper = document.getElementById('pItemWrapper');
function addProjects() {
pItems.insertAdjacentHTML('beforeend', HTML);
console.log('BUILD PROJECTS');
}
//Load json
fetch('data/projects.json').then(function (response) {
return response.json();
}).then(function (data){
//Loop through first set of items to load on page.
for (var i = 0; i < itemsStart; i++) {
HTML += '<img src="' + data.projects[i].Image + '" alt=""></img>';
if (i == (itemsStart - 1)) {
addProjects();
}
}
//Load additional items when clicking 'view more'.
document.getElementById('view-more').addEventListener('click', function() {
HTML = '';
for (var i = itemsStart; i < itemsStart + itemsAdd; i++) {
if ((i < data.projects.length)) {
HTML += '<img src="' + data.projects[i].Image + '" alt=""></img>';
}
if (i == ((itemsStart + itemsAdd) - 1) ) {
addProjects();
}
}
itemsStart = itemsStart + itemsAdd;
});
}).catch(function(error) {
console.error('Something went wrong');
});
I'm not using jQuery so I'd like to stick to vanilla js. I don't know what I need to add to my button event listener beyond what I have, I've never tried preloading images like this without using a plugin but I feel like I don't need to load an entire plugin just for this one thing and I'd like to understand how this would work.
EDIT
I feel like I'm almost there, but I still have something wrong. I made some modifications to have each item inside its own container, but instead of that happening I am creating an empty container for each pass of the loop, then the last container gets each image added to it. My code looks like this:
var itemsAdd = 3;
//Load additional items when clicking 'view more'.
document.getElementById('view-more').addEventListener('click', function() {
//The loop will add the next 3 items in the json file per click.
for (var i = itemsStart; i < itemsStart + itemsAdd; i++) {
var placeholder = document.createElement('div');
var src = 'img/portfolio/' + data.projects[i].url;
placeholder.innerHTML= '<div class="img-container">' + data.projects[i].Title + '</div>';
var galleryItem = placeholder.firstChild;
preloadImage(src).then(function (image) {
galleryItem.append(image);
});
pItems.append(galleryItem);
}
itemsStart = itemsStart + itemsAdd;
});
The result I get is this:
Is this because of how the promise works for the preloadImage function?
Generally you would create an image with JavaScript through either document.createElement('img') or the Image() constructor. Both result an in instance of an HTMLImageElement.
With this, you'll have an image that is not connected to the DOM, so it's not visible to you or the user. You can use this element to load the image behind the scenes by setting the image' src property.
Then by listening to the onload event you can determine whenever the image has finished loading. From here you could continue your flow by adding the image to the DOM and, for example, fade it in with animation.
The example below shows this process in the form of a function that returns a Promise. This promise will resolve whenever the load event has been triggered.
const preloadImage = src =>
new Promise(resolve => {
const image = new Image();
const onLoad = () => {
resolve(image);
};
image.addEventListener('load', onLoad, {once: true});
image.src = src;
});
Using it should be like this:
const src = 'http://example.com/my-image.jpg';
preloadImage(src).then(image => {
// Here the image has been loaded and is available to be appended, or otherwise.
document.body.append(image);
});
In your case you would loop over each image, call the function while passing the URL of the image, and append the image to the DOM when it's finished loading.
You can handle any animations, like fade-ins with CSS.
Real world implementation
So how should you implement this in your project? You'll need to start at the point where you create your images. Currently your images are created as strings. But strings are just strings, they aren't HTML elements, yet.
I'd recommend that you'll create a placeholder for each image. This placeholder could visually indicate that an image is loading and act as a wrapper for the image. Add this placeholder immediately to the pItems element.
Then load the image for each Image in your data.projects array by calling the preloadImage. Whenever the image is loaded, append it to the placeholder we've just created. You should now have the effect that first a placeholder is added and the images are starting to appear one by one.
The same logic should be applied for the load more loop.
...
}).then(function (data){
for (let i = 0; i < itemsStart; i++) {
// Create a <div class="placeholder"> which should act as a placeholder / wrapper.
const placeholder = document.createElement('div');
placeholder.classList.add('placeholder');
// Create the image based on the Image value.
// Whenever the image is loaded, add it to the placeholder.
const src = data.projects[i].Image;
preloadImage(src).then(image => {
placeholder.append(image);
});
// Immediately add the placeholder.
// This line doesn't wait for preloadImage to finish because preloadImage is asynchronous. Look into Promises if that is new to you.
pItems.append(placeholder);
}
...
});
From here you've got control over how the placeholder should look and any animations an image inside that placeholder should have.
I think you could put a <div> with black background over the loading image using css and when the image is ready remove it with js. You can detect when the image is loaded using the img.onload = () => {} function.
Or you could place there an img with the loading screen and replace it with the actual image when the image has loaded.
This is my first post here, I always found solutions on this page, so thank you for that.
I have a problem with .removeClass and .addClass in my last program.
I load multiple pictures into array Frames and I want change all (previous-image) to (current-image) in frames[0]. Here is my code, it is change class only on second image. Here is code:
function loadImage() {
// Creates a new <li>
var li = document.createElement("li");
// Generates the image file name using the incremented "loadedImages" variable
var imageName = "graphics/img/Dodge_Viper_SRT10_2010_360_720_50-" + (loadedImages + 1) + ".jpg";
var imageName1 = "graphics/img/Dodge_Viper_SRT10_2010_360_720_50-" + (loadedImages + 1) + ".jpg";
/*
Creates a new <img> and sets its src attribute to point to the file name we generated.
It also hides the image by applying the "previous-image" CSS class to it.
The image then is added to the <li>.
*/
var image = $('<img>').attr('src', imageName).addClass("previous-image").appendTo(li) && $('<img>').attr('src', imageName1).addClass("previous-image light-image").appendTo(li);
// We add the newly added image object (returned by jQuery) to the "frames" array.
frames.push(image);
// We add the <li> to the <ol>
$images.append(li);
/*
Adds the "load" event handler to the new image.
When the event triggers it calls the "imageLoaded" function.
*/
$(image).load(function() {
imageLoaded();
});
};
function imageLoaded() {
// Increments the value of the "loadedImages" variable
loadedImages++;
// Updates the preloader percentage text
$("#spinner span").text(Math.floor(loadedImages / totalFrames * 100) + "%");
// Checks if the currently loaded image is the last one in the sequence...
if (loadedImages == totalFrames) {
// ...if so, it makes the first image in the sequence to be visible by removing the "previous-image" class and applying the "current-image" on it
frames[0].removeClass("previous-image").addClass("current-image");
/*
Displays the image slider by using the jQuery "fadeOut" animation and its complete event handler.
When the preloader is completely faded, it stops the preloader rendering and calls the "showThreesixty" function to display the images.
*/
$("#spinner").fadeOut("slow", function() {
spinner.hide();
showThreesixty();
});
} else {
// ...if not, Loads the next image in the sequence
loadImage();
}
};
This is, how it looks in browser:
<ol><li><img src="graphics/img/Dodge_Viper_SRT10_2010_360_720_50-1.jpg" class="previous-image"><img src="graphics/img/Dodge_Viper_SRT10_2010_360_720_50-1.jpg" class="light-image current-image"></li></ol>
This is, what I want:
<ol><li><img src="graphics/img/Dodge_Viper_SRT10_2010_360_720_50-1.jpg" class="current-image"><img src="graphics/img/Dodge_Viper_SRT10_2010_360_720_50-1.jpg" class="light-image current-image"></li></ol>
When I change this
var image = $('<img>').attr('src', imageName).addClass("previous-image").appendTo(li) && $('<img>').attr('src', imageName1).addClass("previous-image light-image").appendTo(li);
to this
var image = $('<img>').attr('src', imageName1).addClass("previous-image light-image").appendTo(li) && $('<img>').attr('src', imageName).addClass("previous-image").appendTo(li);
it still change only second img. Any help?
var image = $('<img>').attr('src', imageName).addClass("previous-image").appendTo(li) && $('<img>').attr('src', imageName1).addClass("previous-image light-image").appendTo(li);
is not doing what you think it is. It's only using the second element you declare. They both get appended to the page (because the appendTo method runs), but && is a logical operator, it's not used for concatenation, so the variable "image" only contains the second image you declared.
This will work instead:
var image = $('<img>', { "src": imageName, "class": "previous-image" });
image = image.add($('<img>', { "src": imageName1, "class": "previous-image light-image" }));
image.appendTo(li);
If you are just trying to replace all the previous-image classes with current image then you can do this:
$('img.previous-image').each(function(){
$(this).addClass("current-image").removeClass("previous-image");
});
I've been working on a flowchart type program that changes various parts of the html each time a new question is posed (this question is represented by the 'state' variable below), things like Title, description and images.
Currently, my HTML for the images is:
<a id="imageLink" target="_blank" href=""><img class= 'right' id= 'imageBox' style ='max-height:50%;' src='' /></a>
<h3 id ='imageBoxText' ></h3>
and the image link and the legend for the image are found in imageArray which looks something like this:
imageArray = {'questionOne': ['www.link to image.com,'Legend for image']}
In this case 'questionOne' would be 'state'.
So far, I can load a single image asynchronously using:
document.getElementById("imageBox").src = "http://www.ktcagency.com/img/loader.gif"; // load spinner
var img = new Image(); // load image asynchronously
var newsrc = imageArray[state][0];
img.onload = function () { // onload handler
document.getElementById("imageBox").src = newsrc;
};
img.src = newsrc;
I also add the description and link to the image while I'm at it:
document.getElementById("imageLink").setAttribute('href',imageArray[state][0])
document.getElementById('imageBoxText').innerHTML = imageArray[state][1];
OK, so now what I need to do is write a function that can do this for multiple images, side by side, reading from a modified imageArray that looks like:
imageArray = {'questionOne': [['www.linktoimage1.com,'Legend for image1'],['www.linktoimage2.com,'Legend for image2']]}
The code should be able to handle up to three images, but I certainly wouldn't mind if it could do any more.
I tried to do it using a table to represent the images and used a for loop with the code from earlier. The image would never load, stuck on the spinner for ever and the images would all bunch up on one side of the screen.
Any help appreciated, thanks very much.
Well, I think I see what you're trying to do.
It sounds like you're trying to make a slideshow of some kind.
This is an example of how I would have done it.
Here's the jsfiddle so you can see what's going on. http://jsfiddle.net/VodkaTonic/4KWLr/
(function () {
var imageArray = {
link: [['http://i.imgur.com/7OfqRbF.jpg', 'http://i.imgur.com/AHbc6zO.jpg','http://i.imgur.com/gW144OQ.jpg'],['http://i.imgur.com/v0V8hrB.jpg', 'http://i.imgur.com/9l40vIT.png','http://i.imgur.com/ZXYUttz.png']],
label: [['Legend for image1','Legend for image2', 'Legend for image3'],['Legend for image4','Legend for image5', 'Legend for image6']]
},
doc = document,
question = 0,
img = doc.getElementsByClassName('images'),
links = doc.getElementsByClassName('links'),
label = doc.getElementsByClassName('label'),
button = doc.getElementById('button');
function add (state) {
var arr = imageArray.link[state],
arr2 = imageArray.label[state];
arr.forEach(function(value,index,array) {
img[index]['src'] = value;
links[index]['href'] = value;
});
arr2.forEach(function(value,index,array) {
label[index]['innerHTML'] = value;
});
question++;
}
window.addEventListener('load', add(question), false);
button.addEventListener('click', function () {
add(question);
}, false)
}());
I was given this fantastic script, from #Phil, that helped get my out of a stump and it works perfectly in my application. But because I'm so new to javascript I can't figure out how to make the images animate opacity in and animate opacity out.
// jQuery syntactic sugar to run after page loads
$(function () {
// attach a click event to anything with a data-file attribute
$("[data-file]").on('click', function (evt) {
// figure out what was clicked
var clickedEl = $(evt.target);
// get the data-file attribute
var dataFile = clickedEl.attr('data-file');
var container = $(".bom_container");
// empty the div
container.empty();
// create image element
var img = $("<img/>").attr("src", dataFile)
// add it to the container
container.append(img);
// or update the background image
// container.css('background-image','url('+ dataFile +')');
});
});
When the links are clicked on, these images open in a container. But again, I would like the images to ease in instead of just BOOM APPEAR. Is there somewhere I can add animate opacity to this script or do I have to add an whole new script?
jQuery has great .fadeIn() and .fadeOut() methods just for this.
// jQuery syntactic sugar to run after page loads
$(function () {
// attach a click event to anything with a data-file attribute
$("[data-file]").on('click', function (evt) {
// figure out what was clicked
var clickedEl = $(evt.target);
// get the data-file attribute
var dataFile = clickedEl.attr('data-file');
var container = $(".bom_container");
// empty the div
container.empty();
// create image element
var img = $("<img/>").attr("src", dataFile).css("display","none") // <----- see here
// add it to the container
container.append(img);
img.fadeIn(/*duration in ms*/) // <----- see here
// or update the background image
// container.css('background-image','url('+ dataFile +')');
});
});
Before changing the image src you can fade out the image, change the source, then fade in the new image.
$('#img_selector').fadeOut('fast', function() {
$(this).attr("src", "newsource.jpg")
.fadeIn("fast");
});
I can't get the following function to work.
On click of class .image, content in 3 divs (gallerytext, image, and thumbset) is to be replaced. I have been trying to get this to work for days now and I cannot figure out what I am doing wrong.
My code:
// Init load
$(document).ready(function() {
var $doc = $(document.body);
var text = $('#gallerytext1').html(); //blank divs are filled with content on page load
$('#gallerytext').html(text);
var thumbset = $('#thumbset1').html();
$('#thumbset').html(thumbset);
$('#image').html('<img src="images/edwards1.jpg" border="0"/>');
//click handler
$doc.on("click",".image",function(){
var image=$(".image").attr("rel");
var imid=$(".image").attr("data");
var text=$('#gallerytext'+imid).html();
var thumbset=('#thumbset'+imid).html();
$('#debug').html(imid);
$('#gallerytext').fadeIn('slow');
$('#gallerytext').html(text);
$('#thumbset').fadeIn('slow');
$('#thumbset').html(thumbset);
$('#image').hide();
$('#image').fadeIn('slow');
$('#image').html('<img src="' + image + '"/>');
return false;
});
});
The initial content loads just fine but I cannot get the click handler to work. Objects with the class .image are formatted as such:
<img src="images/edwardsthumb2.jpg" class="thumb" border="0"/>
Any assistance would be highly appreciated, thank you.
These are probably your problematic lines:
var image=$(".image").attr("rel");
var imid=$(".image").attr("data");
$(".image") selects all of the elements with a class of image. You want just the one that was clicked, so use this to refer to it:
var image = $(this).attr("rel");
var imid = $(this).attr("data");
Also, don't make up custom attributes like data. HTML5 introduced data- attributes which are used to store custom data types, so use them:
data-number="1"
And you can access it with the .data() method:
var imid = $(this).data('number');