Multiple canvas created from fields_for loop not working - javascript

I have a form with a fields_for and I need a canvas for each ff.object.print_location.id
Here's the entire form with fabric canvas:
<%= f.fields_for :shop_product_print_files do |ff| %>
<%= ff.object.print_location.title %>
...
...
...
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.0.0-beta.7/fabric.min.js"></script>
<label title="Add a background" class="myFile2%>"><span class="mdi mdi-image"> Add Background</span>
<input type="file" id="file2-<%= "#{ff.object.print_location.id}" %>" />
</label>
<label title="Add an image" class="myFile"><span class="mdi mdi-image"> Add Photo</span>
<input type="file" id="file-<%= "#{ff.object.print_location.id}" %>" />
</label>
<a id="lnkDownload-<%= "#{ff.object.print_location.id}" %>" title="Save"><span class="mdi mdi-download"> Save</span></a>
<canvas id="fCanvas-<%= "#{ff.object.print_location.id}" %>" width="400" height="400"></canvas>
<script>
var canvas = new fabric.Canvas('fCanvas-<%= "#{ff.object.print_location.id}" %>');
var myImage = document.createElement('canvas');
document.getElementById('file-<%= "#{ff.object.print_location.id}" %>').addEventListener("change", function(e) {
var file = e.target.files[0];
var reader = new FileReader();
reader.onload = function(f) {
var data = f.target.result;
fabric.Image.fromURL(data, function(img) {
var oImg = img.set({
left: 0,
top: 0,
angle: 0,
border: '#000',
}).scale(0.2);
canvas.add(oImg).renderAll();
//var a = canvas.setActiveObject(oImg);
var dataURL = canvas.toDataURL({
format: 'png',
quality: 1
});
});
};
reader.readAsDataURL(file);
});
document.getElementById('file2-<%= "#{ff.object.print_location.id}" %>').addEventListener("change", function(e) {
var file = e.target.files[0];
var reader = new FileReader();
reader.onload = function(f) {
var data = f.target.result;
fabric.Image.fromURL(data, function(img) {
// add background image
canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas), {
scaleX: canvas.width / img.width,
scaleY: canvas.height / img.height
});
});
};
reader.readAsDataURL(file);
});
// Download
var imageSaver = document.getElementById('lnkDownload-<%= "#{ff.object.print_location.id}" %>');
imageSaver.addEventListener('click', saveImage, false);
function saveImage(e) {
this.href = canvas.toDataURL({
format: 'png',
quality: 0.8
});
this.download = 'custom-<%= "#{ff.object.print_location.id}" %>.png'
}
</script>
When I do this ^, The canvas's appear for each print_location but I am unable to upload an image to all (only works on the last canvas in the loop and all file upload inputs get put into the last canvas from the loop.
Ex: If i upload a file for the first (second, or fourth. etc.) canvas, it will appear in the last one) and have it appear.
When I use:
const canvas = new fabric.Canvas('fCanvas-<%= "#{ff.object.print_location.id}" %>');
const myImage = document.createElement('canvas');
...The canvas works as it should but only for the first print_location and none of the others.
There are a total of 6 print_locations to iterate through.
What is stopping the file/image from appearing in the canvas when using var within the fields_for loop?
What can I do to allow his to work?
Here is a codepen of this working: https://codepen.io/anon/pen/YmEGyW
Attempt (removed file-2 and download to simply it just to see if i can get the canvas to work alone then apply it):
var canvas = new fabric.Canvas('fCanvas');
document.querySelectorAll('[id$=file-']).forEach((fileInput) => {
fileInput.addEventListener('change', function(e){
let id = this.id.replace('file-','');
let canvas = new fabric.Canvas('fCanvas-'+id);
// the rest of your "change" code
var file = e.target.files[0];
var reader = new FileReader();
reader.onload = function(f) {
var data = f.target.result;
fabric.Image.fromURL(data, function(img) {
var oImg = img.set({
left: 0,
top: 0,
angle: 0,
border: '#000',
}).scale(0.2);
// canvas.add(oImg).renderAll();
canvas.add(oImg);
//var a = canvas.setActiveObject(oImg);
var dataURL = canvas.toDataURL({
format: 'png',
quality: 1
});
});
};
}
reader.readAsDataURL(file);
});

You shouldn't add the scripts on each iteration of the loop. Add it once outside the loop but do something like...
// add the 'change' eventListener to all elements with id starting with "file-"
document.querySelectorAll('[id$=file-']).forEach((fileInput) => {
fileInput.addEventListener('change', function(e){
let id = this.id.replace('file-','');
let canvas = new fabric.Canvas('fCanvas-'+id);
// the rest of your "change" code
})
})
// do something similar for inputs with id starting with "file2-"
The problem with your loop is that you are using the same names everywhere, with "var" you are replacing the previous with the new ones (that's why it only works for the last element), with "const" you are preventing the variables to change it's assignment (that's why it only works for the first one, and your browser console surelly has some errors).

Related

How to add an image to a canvas, scaled to size?

I can add a local image to my canvas. My problem though is that I can only scale the image by something like 0.5 but this isn't very helpful because images are always different. How might I have the image scale to say 400px wide, but the rest resize proportionally so that no matter the size of the chosen image, things fit and it isn't a guessing game (currently I have a link to an image resizer, I'm trying to remove the need)?
I'm using fabricjs 1.7.21.
var canvas = new fabric.Canvas("c");
canvas.setHeight(616);
canvas.setWidth(446);
// New Photo to Canvas
document.getElementById('imgLoader').onchange = function handleImage(e) {
var reader = new FileReader();
reader.onload = function(event) {
var imgObj = new Image();
imgObj.src = event.target.result;
imgObj.onload = function() {
var image = new fabric.Image(imgObj);
image.set({
left: 10,
top: 10,
}).scale(0.5);
canvas.add(image);
}
}
reader.readAsDataURL(e.target.files[0]);
}
canvas {
border: 1px solid #dddddd;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.21/fabric.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<label class="btn btn-default" id="imgLoader">
<span class="oi oi-image"></span> Add Image<input type="file" hidden>
</label>
<canvas id="c"></canvas>
Try this:
var canvas = new fabric.Canvas("c");
canvas.setHeight(616);
canvas.setWidth(446);
// New Photo to Canvas
document.getElementById('imgLoader').onchange = function handleImage(e) {
var reader = new FileReader();
reader.onload = function(event) {
var imgObj = new Image();
imgObj.src = event.target.result;
imgObj.onload = function() {
var image = new fabric.Image(imgObj);
image.width = 400;
image.height = 400;
image.set({
left: 10,
top: 10,
});
canvas.add(image);
}
}
reader.readAsDataURL(e.target.files[0]);
}
scaleToWidth/scaleToHeight was what I was looking for. You can find the documentation here.
Props to #Ben who commented this. I wanted to close the question.

Exclude element from canvas to be saved to json (fabric.js)

I am using
var jsonToPHP= JSON.stringify(canvas.toObject(['id','name']));
to save all from canvas to JSON.
I am also adding background image to canvas.
document.getElementById('imgLoader').addEventListener("change", function (e) { var file = e.target.files[0]; var reader = new FileReader(); reader.onload = function (f) {
var data = f.target.result;
fabric.Image.fromURL(data, function (img) {
var oImg = img.set({left: 0, top: 0, angle: 00,width:canvas.width, height:canvas.height,}).scale(1);
oImg.set('selectable', false);
canvas.add(oImg).renderAll();
var dataURL = canvas.toDataURL({format: 'png', quality: 0.8});
}); }; reader.readAsDataURL(file);
});
But i would like to exclude background image to be saved to JASON.
I vas googling for:
Exclude element from canvas to be saved to json fabric.js
and i have got next:
in the fabricjs docs there is a property for the Object class calles
'excludeFromExport'.
Once set to true it should do exactly what you want.
www.fabricjs.com/docs
I went to:
Source: fabric.js, line 12350 excludeFromExport
And what now?
My knowlage is to less to get to result from this. Does any one can give more informations: maybe one example?
Thank you
DEMO
document.getElementById('imgLoader').addEventListener("change", function(e) {
var file = e.target.files[0];
var reader = new FileReader();
reader.onload = function(f) {
var data = f.target.result;
fabric.Image.fromURL(data, function(img) {
var oImg = img.set({
left: 0,
top: 0,
angle: 00,
width: canvas.width,
height: canvas.height
});
canvas.setBackgroundImage(oImg).renderAll();
var dataURL = canvas.toDataURL({
format: 'png',
quality: 0.8
});
});
};
reader.readAsDataURL(file);
});
var canvas = new fabric.Canvas('c', {
serializeBgOverlay: false //to serialize background data toJson
});
canvas.backgroundColor = 'green';
canvas.add(new fabric.Circle({
left: 50,
top: 50,
radius: 50,
stroke: 'red',
fill: ''
}))
canvas.renderAll();
// override _toObjectMethod and if you want to serialize background , set serializeBgOverlay true, while canvas initialize
fabric.StaticCanvas.prototype._toObjectMethod = function(methodName, propertiesToInclude) {
var data = {
objects: this._toObjects(methodName, propertiesToInclude)
};
if (this.serializeBgOverlay) {
fabric.util.object.extend(data, this.__serializeBgOverlay(methodName, propertiesToInclude));
}
fabric.util.populateWithProperties(this, data, propertiesToInclude);
return data;
}
function exportToJson() {
console.log(canvas.toJSON());
}
canvas{
border:2px dotted blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.16/fabric.min.js"></script>
<input type="file" id="imgLoader" accept="image/*"> <br>
<canvas id='c' width='400' height='400'></canvas>
<button onclick='exportToJson();'>ToJson</button>
Here I added a prototype of _toObjectMethod() , it will exclude background image of canvas toJson export.

Find width and height uploaded image in canvas HTML5.(With file reader)

I am using HTML5 and fabric js for uploading multiple image in canvas. Right now i am uploading multiple image in canvas. But i want to find uploaded image width and height. I have seen one link Check image width and height before upload with Javascript
In this link not using file reader. But in my case using file reader.
var canvas = new fabric.Canvas('canvas');
document.getElementById('file').addEventListener("change",function (e) {
var file = e.target.files[0];
var reader = new FileReader();
console.log("reader " + reader);
reader.onload = function (f) {
var data = f.target.result;
fabric.Image.fromURL(data, function (img) {
var oImg = img.set({ width: 250, height: 200, angle: 0}).scale(0.9);
canvas.add(oImg).renderAll();
var a = canvas.setActiveObject(oImg);
var dataURL = canvas.toDataURL({format: 'png', quality: 0.8});
});
};
reader.readAsDataURL(file);
});
canvas{
border: 1px solid black;
}
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.min.js"></script>
<input type="file" id="file">
<div style="">
<canvas id="canvas" width="450" height="450"></canvas>
</div>
You can create an img tag and get the dimensions from it.
document.getElementById('file').addEventListener("change",function (e) {
var file = e.target.files[0];
var reader = new FileReader();
var output = document.getElementById('output');
reader.onload = function (f) {
var data = f.target.result;
var img = document.createElement('img');
img.src = data;
img.onload = function() {
output.innerHTML = 'width: ' + img.width + '\n' +
'height: ' + img.height;
};
};
reader.readAsDataURL(file);
});
<input type="file" id="file"/>
<pre id="output"></pre>
#jcubic is right about using an <img> tag, (while there would be some ways to get this info without it) but he is absolutely wrong in how he gets the image's dimensions.
.clientWidth and .clientHeight will return the computed width of the <img> element. You don't care about it, since what you are drawing on your canvas is the image contained in the <img>, not the <img> element itself.
What you need then is .width and .height properties or .naturalWidth and .naturalHeight.
document.getElementById('file').addEventListener("change",function (e) {
var file = e.target.files[0];
var reader = new FileReader();
var output = document.getElementById('output');
reader.onload = function () {
var data = this.result;
var img = new Image();
img.src = data;
img.onload = function() {
output.innerHTML = 'width: ' + img.width + '\n' +
'height: ' + img.height;
};
};
reader.readAsDataURL(file);
});
<input type="file" id="file"/>
<pre id="output"></pre>

Two same images displayed at the same time

I am using Fabric js to do image manupulation drag, resize and merge images onto the one image onto the canvas but it shows one bug, i.e when I upload the image it displays two same images at a time, while i need only one.
Please help me to solve this issue.
My code is:
var canvas = new fabric.Canvas('imageCanvas', {
backgroundColor: 'rgb(240,240,240)'
});
var imageLoader = document.getElementById('imageLoader');
imageLoader.addEventListener('change', handleImage, false);
function handleImage(e) {
var reader = new FileReader();
reader.onload = function (event) {
var img = new Image();
img.onload = function () {
var imgInstance = new fabric.Image(img, {
scaleX: 0.2,
scaleY: 0.2
})
canvas.add(imgInstance);
}
img.src = event.target.result;
}
reader.readAsDataURL(e.target.files[0]);
}

Canvas drawImage is not accepting width and height

Image is getting created in full original size, even last two arguments 150, 150 are height and width context.drawImage(img, 0, 0, 150, 150); in the code below:
function (file) { //uploaded files are always images
var reader = new FileReader(); //FileReader for uploading files from local stroge.
reader.onload = function () {
var links = document.createElement('a'); //link when image is clicked
var img = document.createElement('img');
img.src = reader.result; //src = url from uploaded file
img.className = 'images'; //css -> .images { margin-top: 30px; padding: 30px; }
img.onload = function () { //repaint image to 150 - 150 size with canvas, because setting width and height on image itself would just resize the image but I want to create new image with new size
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
context.drawImage(img, 0, 0, 150, 150) //draw image with canvas
}
links.href = reader.result; // url from local storage needed when image is clicked -
links.target = "_blank"; // open new blank page with original image
links.appendChild(img); // image is appended to <a>
document.body.appendChild(links); // <a> is appended to body, that body contains image thumbnail with a link linked to the image source
}
if (file) {
reader.readAsDataURL(file); // read uploaded files url
}
}
img.onload does not making any sense here. result is the same even when I remove it.
You are not drawing back the cropped image to your <img> tag... you will have to create two image Objects, let's call the first originalImage, and the second one croppedImage.
The one you will append to the document is croppedImageand originalImage will just stay in the cache.
When originalImage has loaded, you will paint it to a canvas, and then set croppedImage to the result of the canvas' toDataURL() method.
var read = function() {
var file = this.files[0];
var reader = new FileReader();
reader.onload = function() {
var links = document.createElement('a');
// this will be the appended image
var croppedImage = new Image();
// do your DOM stuff
croppedImage.className = 'images';
links.href = reader.result;
links.target = "_blank";
links.appendChild(croppedImage);
document.body.appendChild(links);
// create a buffer image object
var originalImage = new Image();
// set its load handler
originalImage.onload = function() {
// create a canvas
var canvas = document.createElement('canvas');
// set canvas width/height
canvas.width = canvas.height = 150;
var context = canvas.getContext('2d');
// draw the buffered image to the canvas at required dimension
context.drawImage(originalImage, 0, 0, 150, 150);
// set the appended to doc image's src to the result of the cropping operation
croppedImage.src = canvas.toDataURL();
}
originalImage.src = reader.result;
}
if (file) {
reader.readAsDataURL(file);
}
};
upload.onchange = read;
.images {
margin-top: 30px;
padding: 30px;
}
<input type="file" id="upload" />
You could also have used only a single image object, but this would have required to reset the onload event in the onload event, to avoid an infinite loop, which is a little bit less clear :
var read = function() {
var file = this.files[0];
var reader = new FileReader();
reader.onload = function() {
var links = document.createElement('a');
var img = new Image();
img.className = 'images';
links.href = reader.result;
links.target = "_blank";
links.appendChild(img);
document.body.appendChild(links);
img.onload = function() {
//reset the onload event so it does fire in a loop
img.onload = function(){return;};
var canvas = document.createElement('canvas');
canvas.width = canvas.height = 150;
var context = canvas.getContext('2d');
context.drawImage(this, 0, 0, 150, 150);
this.src = canvas.toDataURL();
}
img.src = reader.result;
}
if (file) {
reader.readAsDataURL(file);
}
};
upload.onchange = read;
.images {
margin-top: 30px;
padding: 30px;
}
<input type="file" id="upload" />
var reader = new FileReader();
reader.onload = function () {
var links = document.createElement('a');
var img = new Image();
img.src = reader.result;
img.className = 'images';
img.onload = function () {
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
context.drawImage(this, 0, 0, 150, 150);
this.src = canvas.toDataURL(); // convert the canvas back to the image
links.appendChild(this); // append the updated image to the document
}
links.href = reader.result;
links.target = "_blank";
document.body.appendChild(links);
}
if (file) {
reader.readAsDataURL(file); //reads the data as a URL
}

Categories