So basically I have a page with a few sections. Each sections contains 5-30 image icons that are fairly small in size but large enough that I want to manipulate the load order of them.
I'm using a library called collagePlus which allows me to give it a list of elements which it will collage into a nice image grid. The idea here is to start at the first section of images, load the images, display the grid, then move on to the next section of images all the way to the end. Once we reach the end I pass a callback which initializes a gallery library I am using called fancybox which simply makes all the images interactive when clicked(but does not modify the icons state/styles).
var fancyCollage = new function() { /* A mixed usage of fancybox.js and collagePlus.js */
var collageOpts = {
'targetHeight': 200,
'fadeSpeed': 2000,
'allowPartialLastRow': true
};
// This is just for the case that the browser window is resized
var resizeTimer = null;
$(window).bind('resize', function() {
resetCollage(); // resize all collages
});
// Here we apply the actual CollagePlus plugin
var collage = function(elems) {
if (!elems)
elems = $('.Collage');
elems.removeWhitespace().collagePlus(collageOpts);
};
var resetCollage = function(elems) {
// hide all the images until we resize them
$('.Collage .Image_Wrapper').css("opacity", 0);
// set a timer to re-apply the plugin
if (resizeTimer) clearTimeout(resizeTimer);
resizeTimer = setTimeout(function() {
collage(elems);
}, 200);
};
var setFancyBox = function() {
$(".covers").fancybox({/*options*/});
};
this.init = function(opts) {
if (opts != null) {
if (opts.height) {
collageOpts.targetHeight = opts.height;
}
}
$(document).ready(function() {
// some recursive functional funk
// basically goes through each section then each image in each section and loads the image and recurses onto the next image or section
function loadImage(images, imgIndex, sections, sectIndex, callback) {
if (sectIndex == sections.length) {
return callback();
}
if (imgIndex == images.length) {
var c = sections.eq(sectIndex);
collage(c);
images = sections.eq(sectIndex + 1).find("img.preload");
return loadImage(images, 0, sections, sectIndex + 1, callback);
}
var src = images.eq(imgIndex).data("src");
var img = new Image();
img.onload = img.onerror = function() {
images[imgIndex].src = src; // once the image is loaded set the UI element's source
loadImage(images, imgIndex + 1, sections, sectIndex, callback)
};
img.src = src; // load the image in the background
}
var firstImgList = $(".Collage").eq(0).find("img.preload");
loadImage(firstImgList, 0, $(".Collage"), 0, setFancyBox);
});
}
}
From my galleries I then call the init function.
It seems like my recursive chain being triggered by img.onload or img.onerror is not working properly if the images take a while to load(on slow networks or mobile). I'm not sure what I'm missing here so if anyone can chip in that would be great!
If it isn't clear what is going wrong from the code I posted you can see a live example here: https://www.yuvalboss.com/albums/olympic-traverse-august-2017
It works quite well on my desktop, but on my Nexus 5x it does not work and seems like the finally few collage calls are not happening. I've spent too long on this now so opening this up to see if I can get some help. Thanks everyone!
Whooooo I figured it out!
Was getting this issue which I'm still unsure about what it means
[Violation] Forced reflow while executing JavaScript took 43ms
Moved this into the callback that happens only once all images are loaded
$(window).bind('resize', function() {
resetCollage(); // resize all collages
});
For some reason it was getting called early even if the browser never resized causing collage to get called when no elements existed yet.
If anyone has any informative input as to why I was getting this js violation would be great to know so I can make a better fix but for now this works :):):)
I have recently downloaded Googles armsglobe chromeexperiment and used it for showing country datas on the globe. but i wanted to refresh the data every 5mins. here is how i tried to update the data:
// ensure the map images are loaded first!!
mapIndexedImage = new Image();
mapIndexedImage.src = 'images/map_indexed.png';
mapIndexedImage.onload = function() {
mapOutlineImage = new Image();
mapOutlineImage.src = 'images/map_outline.png';
mapOutlineImage.onload = function(){
loadCountryCodes(
function(){
loadWorldPins(
function(){
loadContentData(
function(){
initScene();
animate();
}
);
}
);
}
);
};
};
//setInterval(mapIndexedImage.onload, 300000);
The ^ last commented line was my only code. it works and gets the data; loadContentData() gets the data from server(db) and the two functions inside draws and animate() the globe according to the data provided. but whenever a new data is received the globe crashes and doesn't move. is there any better way to implement this without crashing the globe?
Thanks for your help!!! Really appreciated.
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!
I am creating an application for T-shirt customization in which I have put the canvas on image using CSS, but the problem is saving that image as canvas. toDataURL just gives part of the canvas area, but I want the whole image. There are other solutions on Stack Overflow but they do not solve this problem.
Hello,
you have to create an image object (tshirt) with a text object that holds the message.
to do that , load the image with fabric.Image.fromURL() function and inside the function , also create a text object that is going to show the tshirt message.
so, your image and text belong to a group object.
every time you want to load new text , you call the loadText function and you change the text object.
i also added 4 buttons in order to manupulate up/down/left/right the text .
you can export the canvas + image+ text into the function saveImg(),
but on the jsfiddle you will get a security message for Tained canvases.
that happens because on the example i load the image from another domain and the code runs on another domain, you can run that code on your web application with no problem at all.
that is the code :
var canvas = new fabric.Canvas('c');
var scaleFactor=0.4
canvas.backgroundColor = 'yellow';
canvas.renderAll();
var myImg = 'http://izy.urweb.eu/files/tshirt.jpg';
fabric.Image.fromURL(myImg, function(myImg) {
var img1 = myImg.scale(scaleFactor).set({ left: 0, top: 0 });
var text = new fabric.Text('the_text_sample\nand more', {
fontFamily: 'Arial',
fontSize:20,
});
text.set("top",myImg.height*scaleFactor-myImg.height*scaleFactor+150);
text.set("left",myImg.width*scaleFactor/2-text.width/2);
var group = new fabric.Group([ img1,text ], { left: 10, top: 10 });
canvas.add(group);
});
$('#loadText').on('click',loadText);
$('#saveImg').on('click',saveImg);
function loadText(){
console.log($('#logo').val());
canvas._objects[0]._objects[1].text = $('#logo').val();
canvas.renderAll();
}
function saveImg(){
console.log('export image');
if (!fabric.Canvas.supports('toDataURL')) {
alert('This browser doesn\'t provide means to serialize canvas to an image');
}
else {
window.open(canvas.toDataURL('png'));
}
}
$('#left').on('click',function(){
canvas._objects[0]._objects[1].set('left',canvas._objects[0]._objects[1].left-1);
canvas.renderAll();
})
$('#right').on('click',function(){
canvas._objects[0]._objects[1].set('left',canvas._objects[0]._objects[1].left+1);
canvas.renderAll();
})
$('#top').on('click',function(){
canvas._objects[0]._objects[1].set('top',canvas._objects[0]._objects[1].top-1);
canvas.renderAll();
})
$('#bottom').on('click',function(){
canvas._objects[0]._objects[1].set('top',canvas._objects[0]._objects[1].top+1);
canvas.renderAll();
})
that is the jsfiddle example: http://jsfiddle.net/tornado1979/zrazuhcq/1/
hope helps, good luck.
I have this simple image zoom jQuery. Here is a Demo example. It uses the elevateZoom jQuery.
The code behind is very simple:
<img id="zoom_05" src='small_image1.png' data-zoom-image="large_image1.jpg"/>
<script>
$vc("#zoom_05").elevateZoom({
zoomType : "inner",
cursor: "crosshair"
});
</script>
My question, is how can i make the large image load on demand, only when the mouse is over it. Please have a look at this demo example and let me know what i need to add in the code.
img element (id="zoom_05") above, would not load large_image1.jpg on its own.
Large image load happens because elevateZoom() looks into its data-zoom-image value and immediately loads it. One way around this behaviour is to defer elevateZoom() until user hover's over the small image for the first time. Quick example:
jQuery( function () {
var elevate_zoom_attached = false, $zoom_05 = $("#zoom_05") ;
$zoom_05.hover(
// hover IN
function () {
if ( ! elevate_zoom_attached ) {
$zoom_05.elevateZoom({
zoomType : "inner",
cursor : "crosshair"
});
elevate_zoom_attached = true ;
};
},
// hover OUT
function () {
if ( elevate_zoom_attached) { // no need for hover any more
$zoom_05.off("hover");
}
}
);
}) ;
Mind you this is an quick, on-top-of-my-head code, but should work ...
Also in this case elevateZoom() action might not be immediate while large image loading is going on.
I used this idea to initiate zoom on any number of images on the same page by adding a zoom class to the image. It works without any HOVER OUT
<img class="zoom" src="myimage.png" data-zoom-image="mybigimage.png"/>
$('.zoom').hover(
// hover IN
function () {
var currImg = $(this); //get the current image
if (!currImg.hasClass('zoomon')) { //if it hasn't been initialized
currImg.elevateZoom(); //initialize elevateZoom
currImg.addClass('zoomon'); //add the zoomon class to the img so it doesn't re-initialize
}
})