Masonry Image Organization from JSON - javascript

My goal is to call an API, which returns JSON data. On successfully retrieving the data, I would like to put the retrieved image into a gallery, organized with the Masonry library. This should work for initially loading data, and endless scrolling to load more data when the user scrolls down the page.
The code below works well for grabbing and organizing the first set of data. It loads nine images and uses Masonry to organize them. But, when I scroll down the page to load more images, all of the current images get pushed down the page. When I scroll down again the same thing happens. So I end up with a bunch of white space at the top of the page.
I was attempting to roughly follow the example that I found here;
http://codepen.io/desandro/pen/iHevA
Thanks for any help you can provide.
function loadThumbnailImages(thumbDate) {
// Format the date into YYYY-mm-dd format for the API
formattedDate = this.formatDate(thumbDate);
$.jsonp({
// The URL to parse the JSON from
url: 'dataURLGoesHere' + formattedDate, // any JSON endpoint
// if URL above supports CORS (optional)
corsSupport: false,
// if URL above supports JSONP (optional)
jsonpSupport: false,
// If the JSON from the URL was successfully parsed
success: function(data){
$(document).ready(function() {
if(data.media_type == "image")
{
var item = '<div class="masonry-item"><img src=' + data.url + ' class="thumbImage img-responsive"/></div>';
items += item;
if(doneLoading)
{
var $container = $('#masonry-list').masonry({
itemSelector: '.masonry-item',
columnWidth: 50
})
$container.append(items);
$container.imagesLoaded().progress( function ( imgLoad, image) {
var $item = $(image.img).parents('.masonry-item');
$container.masonry('appended', $item);
});
items = "";
}
}
})
}

I managed to figure it out. Adding the two lines $container.masonry('reloadItems') and $container.masonry('layout') manages to load the images and responsively place them in the correct place. Hopefully this can help someone in the future!
var $container = $('#masonry-list').masonry({
itemSelector: '.masonry-item',
columnWidth: 50
})
$container.append(items);
$container.imagesLoaded().progress( function ( imgLoad, image) {
var $item = $(image.img).parents('.masonry-item');
$container.masonry('appended', $item);
$container.masonry('reloadItems'); //<--- This line and
$container.masonry('layout'); //<-------- This line fixed the problem
});

Related

jCrop resize with correct coords when changing image

Hoping this doesn't get flagged as a duplicate because none of the other q/as on SO have helped me fix this, I think I need a more specific line of help.
I have a profile page on my site that allows the user to change their profile picture without page reloads (via AJAX / jQuery).
This all works fine. The user opens the "Change Profile Picture" modal, selects a file to upload and presses "Crop this image". When this button is pressed, it uploads a file to the website, using the typical way of sending the file and formData (which I append the file data to).
It gets sent backend with the following jQuery:
// Upload the image for cropping (Crop this Image!)
$("#image-upload").click(function(){
// File data
var fileData = $("#image-select").prop("files")[0];
// Set up a form
var formData = new FormData();
// Append the file to the new form for submission
formData.append("file", fileData);
// Send the file to be uploaded
$.ajax({
// Set the params
cache: false,
contentType: false,
processData: false,
// Page & file information
url: "index.php?action=uploadimage",
dataType: "text",
type: "POST",
// The data to send
data: formData,
// On success...
success: function(data){
// If no image was returned
// "not-image" is returned from the PHP script if we return it in case of an error
if(data == "not-image"){
alert("That's not an image, please upload an image file.");
return false;
}
// Else, load the image on to the page so we don't need to reload
$(profileImage).attr("src", data);
// If the API is already set, then we should apply a new image
if(jCropAPI){
jCropAPI.setImage(data + "?" + new Date().getTime());
}
// Initialise jCrop
setJCrop();
//$("#image-profile").show();
$("#send-coords").show();
}
})
});
setJcrop does the following
function setJCrop(){
// Get width / height of the image
var width = profileImage.width();
var height = profileImage.height();
// Var containing the source image
var imgSource = profileImage.attr("src");
// New image object to work on
var image = new Image();
image.src = imgSource;
// The SOURCE (ORIGINAL) width / height
var origWidth = image.width;
var origHeight = image.height;
// Set up the option to jCrop it
$(profileImage).Jcrop({
onSelect: setCoords,
onChange: setCoords,
setSelect: [0, 0, 51, 51],
aspectRatio: 1, // This locks it to a square image, so it fits the site better
boxWidth: width,
boxHeight: height, // Fixes the size permanently so that we can load new images
}, function(){jCropAPI = this});
setOthers(width, height, origWidth, origHeight);
}
And once backend, it does the following:
public function uploadImage($file){
// See if there is already an error
if(0 < $file["file"]["error"]){
return $file["file"]["error"] . " (error)";
}else{
// Set up the image
$image = $file["file"];
$imageSizes = getimagesize($image["tmp_name"]);
// If there are no image sizes, return the not-image error
if(!$imageSizes){
return "not-image";
}
// SIZE LIMIT HERE SOON (TBI)
// Set a name for the image
$username = $_SESSION["user"]->getUsername();
$fileName = "images/profile/$username-profile-original.jpg";
// Move the image which is guaranteed a unique name (unless it is due to overwrite), to the profile pictures folder
move_uploaded_file($image["tmp_name"], $fileName);
// Return the new filename
return $fileName;
}
}
Then, the user selects their area on the image with the selector and pressed "Change Profile Picture" which does the following
// Send the Coords and upload the new image
$("#send-coords").click(function(){
$.ajax({
type: "POST",
url: "index.php?action=uploadprofilepicture",
data: {
coordString: $("#coords").text() + $("#coords2").text(),
imgSrc: $("#image-profile").attr("src")
},
success: function(data){
if(data == "no-word"){
alert("Can not work with this image type, please try with another image");
}else{
// Append a date to make sure it reloads the image without using a cached version
var dateNow = new Date();
var newImageLink = data + "?" + dateNow.getTime();
$("#profile-picture").attr("src", newImageLink);
// Hide the modal
$("#profile-picture-modal").modal("hide");
}
}
});
})
The backend is:
public function uploadProfilePicture($coordString, $imgSrc){
// Target dimensions
$tarWidth = $tarHeight = 150;
// Split the coords in to an array (sent by a string that was created by JS)
$coordsArray = explode(",", $coordString);
//Set them all from the array
$x = $coordsArray[0];
$y = $coordsArray[1];
$width = $coordsArray[2];
$height = $coordsArray[3];
$newWidth = $coordsArray[4];
$newHeight = $coordsArray[5];
$origWidth = $coordsArray[6];
$origHeight = $coordsArray[7];
// Validate the image and decide which image type to create the original resource from
$imgDetails = getimagesize($imgSrc);
$imgMime = $imgDetails["mime"];
switch($imgMime){
case "image/jpeg":
$originalImage = imagecreatefromjpeg($imgSrc);
break;
case "image/png":
$originalImage = imagecreatefrompng($imgSrc);
break;
default:
return "no-work";
}
// Target image resource
$imgTarget = imagecreatetruecolor($tarWidth, $tarHeight);
$img = imagecreatetruecolor($newWidth, $newHeight);
// Resize the original image to work with our coords
imagecopyresampled($img, $originalImage, 0, 0, 0, 0,
$newWidth, $newHeight, $origWidth, $origHeight);
// Now copy the CROPPED image in to the TARGET resource
imagecopyresampled(
$imgTarget, // Target resource
$img, // Target image
0, 0, // X / Y Coords of the target image; this will always be 0, 0 as we do not want any black nothingness
$x, $y, // X / Y Coords (top left) of the target area
$tarWidth,
$tarHeight, // width / height of the target
$width,
$height // Width / height of the source image crop
);
$username = $_SESSION["user"]->getUsername();
$newPath = "images/profile/$username-profile-cropped.jpg";
// Create that shit!
imagejpeg($imgTarget, $newPath);
// Return the path
return $newPath;
}
So basically this the returns the path of the new file, which gets changed to the user's profile picture (same name every time) and uploaded live with a time appended after ? to refresh the image properly (no cache).
This all works fine, however if the user selects another image to upload, after already uploading one, the coords get all messed up (e.g. they go from 50 to 250) and they end up cropping a totally different part of the image, leaving most of it black nothing-ness.
Really sorry for the ridiculous amount of code that is in this question but I'd appreciate any help from people who might have worked around this before.
Some of the code may seem out of place but that's just me trying to debug it.
Thanks, and again, sorry for the size of this question.
-Edit-
My setCoords() and setOthers() functions look like so:
//Set the coords with this method, that is called every time the user makes / changes a selection on the crop panel
function setCoords(c){
$("#coords").text(c.x + "," + c.y + "," + c.w + "," + c.h + ",");
}
//This one adds the other parts to the second div; they will be concatenated in to the POST string
function setOthers(width, height, origWidth, origHeight){
$("#coords2").text(width + "," + height + "," + origWidth + "," + origHeight);
}
I have now resolved this issue.
The problem for me was that when using setJCrop(); - it was not re-loading the image. The reason for this is that the image uploaded and then loaded in to the JCrop window had the same name every time (username as a prefix, and then profile-cropped.jpg).
So to try and resolve this, I used the setImage method which loaded a full-sized image instead.
I got around this by setting the boxWidth / boxHeight params but they just left me with the issue of the coordinates being incorrect every time I loaded a new image in.
Turns out, it was loading the image from the cache every time, even when I was using new Image(); within jQuery.
To solve this, I have now used destroy(); on the jCropAPI and then re-initialised it every time, witout using setImage();
I set a max-width in the CSS on the image itself, which stopped it from being locked to a specific width.
The next problem was that every time I loaded an image a second time, it left the width / height of the old image there, which made the image look all skewed and wrong.
To solve this, I reset the width & height of the image that I use jCrop on, to "" with $(profileImage).css("width", ""); $(profileImage).css("height", ""); before re-setting the source of the image from the new uploaded image.
But I was still left with the issue of using the same name on the images, and then causing it to load from cache every time.
My solution to this was to add an "avatar" column in the database and save the image name in the Database each time. The image was named as $username-$time.jpg and $username-$time.jpg-cropped.jpg where $username is the username of the user (derp) and $time is simply time(); inside PHP.
This meant that every time I uploaded an image, it had a new name, so when any calls were made to this image, there was no cache of it.
Appending like imageName + ".jpg?" + new Date.getTime(); worked for some things but then when sending the image name backend, it didn't work properly, and deciding when to append it / not append it was a pain, and then one thing required it to be appended to force a re-load, but then when appended it didn't work properly, so I had to re-work it.
So the key: (TL;DR)
Don't use the same image name with jCrop, if you are loading a new image; upload an image with a different name and then refer to that one. Cache problems are a pain, and you can't really work around them properly without just using a new name every time, as this ensures that there will be absolutely no more problems (so long as the name is always unique).
Then, when you initialise jCrop, destroy the previous one beforehand if there is one. Use max-width instead of width on an image to stop it from locking the width, and re-set the width / height of the image if you're loading a new one in to the same <img> or <div>
Hope this helps somebody!
I used jcrop and I think this has happened to me. When there is a new image, you have to "reset" jcrop. Try something like this:
function resetJCrop()
{
if (jCropAPI) {
jCropAPI.disable();
jCropAPI.release();
jCropAPI.destroy();
}
}
$("#image-upload").click(function(){
success: function(data){
...
resetJCrop(); // RESETTING HERE
// If the API is already set, then we should apply a new image
if(jCropAPI){
jCropAPI.setImage(data + "?" + new Date().getTime());
}
// Initialise jCrop
setJCrop();
...
}
});
I can't remember the details about why I have to use disable() AND release() AND destroy() in my particular case. May be you can use only one of those. Just try it, and see if that works for you!

Cropping a profile picture in Node JS

I want the user on my site to be able to crop an image that they will use as a profile picture. I then want to store this image in an uploads folder on my server.
I've done this using php and the JCrop plugin, but I've recently started to change the framework of my site to use Node JS.
This is how I allowed the user to crop an image before using JCrop:
$("#previewSub").Jcrop({
onChange: showPreview,
onSelect: showPreview,
aspectRatio: 1,
setSelect: [0,imgwidth+180,0,0],
minSize: [90,90],
addClass: 'jcrop-light'
}, function () {
JcropAPI = this;
});
and I would use php to store it in a folder:
<?php
$targ_w = $targ_h = 300;
$jpeg_quality = 90;
$img_r = imagecreatefromjpeg($_FILES['afile']['tmp_name']);
$dst_r = ImageCreateTrueColor( $targ_w, $targ_h );
imagecopyresampled($dst_r,$img_r,0,0,$_POST['x'],$_POST['y'],
$targ_w,$targ_h,$_POST['w'],$_POST['h']);
header("Content-type: image/jpeg");
imagejpeg($dst_r,'uploads/sample3.jpg', $jpeg_quality);
?>
Is there an equivalent plugin to JCrop, as shown above, using Node JS? There are probably multiple ones, if there are, what ones would you recommend? Any simple examples are appreciated too.
EDIT
Because the question is not getting any answers, perhaps it is possible to to keep the JCrop code above, and maybe change my php code to Node JS. If this is possible, could someone show me how to translate my php code, what would would be the equivalent to php above?
I am very new to Node, so I'm having a difficult time finding the equivalent functions and what not.
You could send the raw image (original user input file) and the cropping paramenters result of JCrop.
Send the image enconded in base64 (string), when received by the server store it as a Buffer :
var img = new Buffer(img_string, 'base64');
ImageMagick Doc : http://www.imagemagick.org/Usage/files/#inline
(inline : working with base64)
Then the server has the image in a buffer and the cropping parameters.
From there you could use something like :
https://github.com/aheckmann/gm ...or...
https://github.com/rsms/node-imagemagick
to cast the modifications to the buffer image and then store the result in the file system.
You have other options, like manipulating it client-side and sending the encoded image result of the cropping.
EDIT : First try to read & encode the image when the user uses the input :
$('body').on("change", "input#selectImage", function(){readImage(this);});
function readImage(input) {
if ( input.files && input.files[0] ) {
var FR = new FileReader();
FR.onload = function(e) {
console.log(e.target.result);
// Display in <img> using the b64 string as src
$('#uploadPreview').attr( "src", e.target.result );
// Send the encoded image to the server
socket.emit('upload_pic', e.target.result);
};
FR.readAsDataURL( input.files[0] );
}
}
Then when received at the server use the Buffer as mentioned above
var matches = img.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/), response = {};
if (matches.length !== 3) {/*invalid string!*/}
else{
var filename = 'filename';
var file_ext = '.png';
response.type = matches[1];
response.data = new Buffer(matches[2], 'base64');
var saveFileAs = 'storage-directory/'+ filename + file_ext;
fs.unlink(saveFileAs, function() {
fs.writeFile(saveFileAs, response.data, function(err) {if(err) { /* error saving image */}});
});
}
I would personally send the encoded image once it has been edited client-side.
The server simply validates and saves the file, let the client do the extra work.
As promised, here is how I used darkroom.js with one of my Express projects.
//Location to store the image
var multerUploads = multer({ dest: './uploads/' });
I upload the image first, and then allow the user to crop it. This is because, I would like to keep the original image hence, the upload jade below:
form(method='post', action='/user/image/submit', enctype='multipart/form-data')
input(type='hidden', name='refNumber', value='#{refNumber}')
input(type='file', name='photograph' accept='image/jpeg,image/png')
br
input(type='submit', value='Upload image', data-role='button')
Here is the form I use to crop the image
//- figure needed for darkroom.js
figure(class='image-container', id='imageContainer')
//specify source from where it should load the uploaded image
img(class='targetImg img-responsive', src='/user/image/view/#{photoName}', id='target')
form(name='croppedImageForm', method='post', enctype='multipart/form-data', id='croppedImageForm')
input(type='hidden', name='refNumber', id='refNumber', value='#{refNumber}')
input(type='hidden', id='encodedImageValue', name='croppedImage')
br
input(type='submit', value='Upload Cropped Image', id='submitCroppedImage' data-role='button')
The darkroom.js is attached to the figure element using this piece of javascript.
new Darkroom('#target', {
// Canvas initialization size
minWidth: 160,
minHeight: 160,
maxWidth: 900,
maxHeight: 900,
});
});
Once you follow the STEP 1, STEP 2 and finally STEP 3 the base64 value of the cropped region is stored in under figure element see the console log shown in the screenshot below:
I then have a piece of javascript that is triggered when the Upload Cropped Image is clicked and it then copy/paste the base64 value of the img from figure into the input element with id encodedImageValue and it then submit it to the server. The javascript function is as follow:
$("#submitCroppedImage").click(function() {
var img = $('#imageContainer img');
var imgSrc = img.attr('src');
if(imgSrc !== undefined && imgSrc.indexOf('base64') > 0) {
$('#encodedImageValue').val(img.attr('src'));
$.ajax({
type: "POST",
url: "/user/image/cropped/submit",
data: $('#croppedImageForm').serialize(),
success: function(res, status, xhr) {
alert('The CROPPED image is UPLOADED.');
},
error: function(xhr, err) {
console.log('There was some error.');
}
});
} else {
alert('Please follow the steps correctly.');
}
});
Here is a screenshot of POST request with base64 field as its body
The post request is mapped to the following route handler in Express app:
router.post('/user/image/cropped/submit',
multerUploads,
function(req, res) {
var photoName = null;
var refNumber = req.body.refNumber;
var base64Data = req.body.croppedImage.replace(/^data:image\/png;base64,/, "");
fs.writeFile("./uploads/cropped-" + 'profile_image.png', base64Data, 'base64',
function(err) {
logger.info ("Saving image to disk ...");
res.status(200).send();
});
});
I have the following .js files relating to awesome Fabric.js and darkroom.js
script(src='/static/js/jquery.min.js')
script(src='/static/js/bootstrap.min.js')
//get the js files from darkroom.js github
script(src='/static/js/fabric.js')
script(src='/static/js/darkroom.js')
script(src='/static/js/plugins/darkroom.crop.js')
script(src='/static/js/plugins/darkroom.history.js')
script(src='/static/js/plugins/darkroom.save.js')
link(href='/static/css/darkroom.min.css', rel='stylesheet')
link(href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.2/css/bootstrap.css', rel='stylesheet')
//get this from darkroom.js github
link(href='/static/css/page.css', rel='stylesheet')
Lastly, also copy the svg icons for selecting, cropping, saving, etc (from darkroo.js github page).

Meteor and Flexbox : pagination slow loading results in flickering

Going back to the previous thumbnail overview results in flickering. Looking at it in slow motion the build up of the thumbnail page becomes visible. If the images are loaded fast, the problem disappears, but is very visible if the connection is slow. Is there a way keep the thumbnail page in cache or render the page after flexbox places all the elements? Or is there any other solution? Thanks!
Slow motion:
https://www.youtube.com/watch?v=x3vvCRIJdRo
All images are small sprites that are then drawn to canvases btw. I'm also using flexbox.
Website: http://eboydb.mod.bz/pool/all/1 (will remove this link – if fixed)
Edit:
Just tried to use waitOn with Iron Router – as suggested below. It doesn't seem to make a difference though.
Router.route('/pool/:slug/:page', {
name: 'pool',
template: 'pool',
after: function() {
document.title = siteName + ' /pool/' + this.params.slug + '/' + this.params.page;
},
waitOn: function() {
// set search slugs that will show everything
var allOptions = [
"all",
"everything",
"show all",
];
// when the slug is part of the array show everything
if (allOptions.indexOf(this.params.slug.toLowerCase()) > -1) {
var searchTerm = ".*";
// console.log('slug: all > ' + this.params.slug);
} else {
var searchTerm = this.params.slug;
}
Meteor.subscribe('PixQuery', searchTerm, this.params.page);
},
data: function() {
templateData = {
slug: this.params.slug,
}
return templateData;
}
});

JS wait for CSS background image to load

It's easy to keep javascript waiting for some images to load if those are classic HTML images.
But I can't figure how to do the same if the image is loaded as a CSS backuground-image!
Is it possible?
The jQuery .load() method doesn't seem to apply.. and I'm short of ideas
It looks for elements with src attribute or backgroundImage css property and calls an action function when theirs images loaded.
/**
* Load and wait for loading images.
*/
function loadImages(images, action){
var loaded_images = 0;
var bad_tags = 0;
$(images).each(function() {
//alert($(this).get(0).tagName+" "+$(this).attr("id")+" "+$(this).css("display"));
var image = new Image();
var src = $(this).attr("src");
var backgroundImage = $(this).css("backgroundImage");
// Search for css background style
if(src == undefined && backgroundImage != "none"){
var pattern = /url\("{0,1}([^"]*)"{0,1}\)/;
src = pattern.exec(backgroundImage)[1];
}else{
bad_tags++;
}
// Load images
$(image).load(function() {
loaded_images++;
if(loaded_images == ($(images).length - bad_tags))
action();
})
.attr("src", src);
});
}
One alternate approach would be to fetch the image data via AJAX as a base64 encoded png and apply it to the element's background-image property.
For example:
$.get('/getmyimage', function(data) {
// data contains base64 encoded image
// for ex: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==
$('#yourElement').css('background-image', 'url("' + data + '")');
});
You will also need a server side script that reads the image, converts it into base64 encoded png (and cache it maybe) and return the same.
Try this one...
Its a jQuery-Plugin which gives you control to wait for images to be loaded
Project-Home
Thread # SO
Official way to ask jQuery wait for all images to load before executing something
Answer # SO
(ShortLink)
this is untested code but try this:
$(document).ready(
function()
{
var imgSrc = $('theTargerElement').css('background-image');
var imgTag = $('<img>').attr('src',imgSrc).appendTo( 'body' );
}
);
$(document)
.load(
function()
{
// do stuff
}
);

Update a link href in prototype

I'm running a prototype script (lightbox) and I want to add an extra link below each image with the url to a page that changes with each image.
I have so far managed to add the a element to the right place, but later in the script I need to update the href of the link based on the image that is loaded.
There's a point in the script where the image is inserted into the lightbox after loading/navigating the lightbox:
showImage: function(){
this.loading.hide();
new Effect.Appear(this.lightboxImage, {
duration: this.resizeDuration,
queue: 'end',
afterFinish: (function(){ this.updateDetails(); }).bind(this)
});
this.preloadNeighborImages();
},
After it's finished it runs updateDetails, which includes the images' caption. This seemed to me the perfect place to also update the link:
updateDetails: function() {
// if caption is not null
if (this.imageArray[this.activeImage][1] != ""){
this.caption.update(this.imageArray[this.activeImage][1]).show();
}
// if image is part of set display 'Image x of x'
if (this.imageArray.length > 1){
this.numberDisplay.update( LightboxOptions.labelImage + ' ' + (this.activeImage + 1) + ' ' + LightboxOptions.labelOf + ' ' + this.imageArray.length).show();
}
/** slider effect cut from example code for the sake of brevity **/
}
I've tried many variations based on the codes used in these functions, but nothing seems to work. How do I update the href of a link with id=toFilePage?
I figured it'd be something like
this.toFilePage.href.update('http://example.com');
But that doesnt seem to work..
I believe the problem may lie within this section (starting at line 175 in lightbox.js)
(function() {
var ids = 'overlay lightbox outerImageContainer imageContainer lightboxImage hoverNav prevLink nextLink loading loadingLink ' + 'imageDataContainer imageData imageDetails caption numberDisplay bottomNav bottomNavClose';
$w(ids).each(function(id) {
th[id] = $(id);
});
}).defer();
This creates references to the objects created in the Builder section above it. Simply adding your id to that list should do the trick.
Otherwise you may also access it through $("myIdHere").

Categories

Resources