Why is image not moveable after setting preserveObjectStacking - javascript

what im trying to do is initially load an image which stays static and if the file input changes that image gets loaded, placed in the canvas, should be behind the initially loaded image and be moveable, rotateable, resizeable. my first problem was that image.sendToBack() wasnt doing anything - i fixed that with setting the initial option preserveObjectStacking to true - it worked but now the image is resizeable, rotateable but not moveable.
import { fabric } from "fabric";
(function($) {
var imageLoader = document.getElementById('imageLoader');
var canvas = document.getElementById('imageCanvas');
imageLoader.addEventListener('change', handleImage, false);
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({
angle: 0,
padding: 10,
cornersize:10,
height:110,
width:110,
});
// canvas.centerObject(image);
canvas.add(image);
canvas.sendToBack(image);
image.setCoords();
canvas.renderAll();
}
}
reader.readAsDataURL(e.target.files[0]);
}
var canvas = new fabric.Canvas('imageCanvas', {
preserveObjectStacking: true
});
canvas.setWidth(300);
// overlayImage
fabric.Image.fromURL('/../img/meinbild_leer_300x600.png', function(oImg) {
oImg.scaleToWidth(300);
canvas.add(oImg);
}, {hasControls: false, selectable: false});
})(jQuery);
what i've tried based on documentation and other stackoverflow posts - i added the image.setCoords() after the sendToBack-Call which actually did not result in any changes. do you guys have any advice for me? working the first time with fabric and im seriously stuck right here. thanks and have a nice one.
edit: i tried adding selected: true to the image.set but that has not changed anything.
additional information: im using fabricjs version ^2.4.2-b

Use object#evented, which will propagate all the events through it.
DEMO
var imageLoader = document.getElementById('imageLoader');
var canvas = document.getElementById('imageCanvas');
imageLoader.addEventListener('change', handleImage, false);
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({
angle: 0,
padding: 10,
cornersize: 10,
height: 110,
width: 110,
});
// canvas.centerObject(image);
canvas.add(image);
canvas.sendToBack(image);
image.setCoords();
canvas.renderAll();
}
}
reader.readAsDataURL(e.target.files[0]);
}
var canvas = new fabric.Canvas('imageCanvas', {
preserveObjectStacking: true
});
canvas.setWidth(300);
// overlayImage
fabric.Image.fromURL('https://raw.githubusercontent.com/fabricjs/fabricjs.com/gh-pages/assets/dragon2.jpg', function(oImg) {
oImg.scaleToWidth(300);
canvas.add(oImg);
}, {
hasControls: false,
evented: false,
opacity: 0.3
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.4.1/fabric.js"></script>
<canvas id='imageCanvas'></canvas>
<input id='imageLoader' type='file'>

Related

Multiple canvas created from fields_for loop not working

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).

Saving JSON as File + Loading JSON from File

I have a FabricJS canvas (1.7.22) with a few buttons to add images and text. I also have a textarea which I can use to load the canvas JSON for copying and to load to the canvas for later editing.
I want to expedite the process by being able to save the canvas JSON as a file to my computer and be able to load the file onto the canvas later.
I feel like I have the essential functionality but how might I achieve the above for better usability?
$(function() {
var canvas = new fabric.Canvas('c', {
/* isDrawingMode: true */
});
$('#text').on('click', addtext);
function addtext() {
var text = new fabric.IText('Some Text!', {
left: 10,
top: 10
});
canvas.add(text);
}
// From Computer
document.getElementById('imgLoader').onchange = function handleImage(e) {
var reader = new FileReader();
reader.onload = function(event) {
console.log('fdsf');
var imgObj = new Image();
imgObj.src = event.target.result;
imgObj.onload = function() {
// start fabricJS stuff
var image = new fabric.Image(imgObj);
image.set({
left: 0,
top: 0,
angle: 20,
padding: 10,
cornersize: 10
});
//image.scale(getRandomNum(0.1, 0.25)).setCoords();
image.scale(0.2);
canvas.add(image);
// end fabricJS stuff
}
}
reader.readAsDataURL(e.target.files[0]);
}
// Add Web IMG
var myImg = 'https://i.imgur.com/q2oGjQ9.jpg';
$('#addImage').on('click', addImg);
function addImg() {
fabric.Image.fromURL(myImg, function(oImg) {
oImg.scale(0.2);
canvas.add(oImg);
});
}
// canvas2json
$("#canvas2json").click(function() {
var json = canvas.toJSON();
$("#myTextArea").text(JSON.stringify(json));
});
// load json2canvas
$("#loadJson2Canvas").click(function() {
canvas.loadFromJSON(
$("#myTextArea").val(),
canvas.renderAll.bind(canvas));
});
});
#myTextArea {
width: 90%;
height: 200px;
}
canvas {
border: 1px solid black
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.22/fabric.min.js"></script>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<button id="text">Add Text</button>
<input type="button" id="addImage" value="Add Web IMG">
<input type="file" id="imgLoader">
<br/><br/>
<canvas id='c' width=500 height=500></canvas>
<br/>
<button id='canvas2json'>2JSON</button>
<button id='loadJson2Canvas'>2CANVAS</button>
<br/><br/>
<textarea id='myTextArea' onfocus="this.select();" onmouseup="return false;"></textarea>
Download the json using createObjectURL and then upload from browser and read the file using readAsText , FileReader api . then load JSON to canvas.
DEMO
$(function() {
var canvas = new fabric.Canvas('c', {
/* isDrawingMode: true */
});
$('#text').on('click', addtext);
function addtext() {
var text = new fabric.IText('Some Text!', {
left: 10,
top: 10
});
canvas.add(text);
}
// From Computer
document.getElementById('imgLoader').onchange = function handleImage(e) {
var reader = new FileReader();
reader.onload = function(event) {
console.log('fdsf');
var imgObj = new Image();
imgObj.src = event.target.result;
imgObj.onload = function() {
// start fabricJS stuff
var image = new fabric.Image(imgObj);
image.set({
left: 0,
top: 0,
angle: 20,
padding: 10,
cornersize: 10
});
//image.scale(getRandomNum(0.1, 0.25)).setCoords();
image.scale(0.2);
canvas.add(image);
// end fabricJS stuff
}
}
reader.readAsDataURL(e.target.files[0]);
}
// Add Web IMG
var myImg = 'https://i.imgur.com/q2oGjQ9.jpg';
$('#addImage').on('click', addImg);
function addImg() {
fabric.Image.fromURL(myImg, function(oImg) {
oImg.scale(0.2);
canvas.add(oImg);
});
}
// canvas2json
$("#canvas2json").click(function() {
var json = canvas.toJSON();
$("#myTextArea").text(JSON.stringify(json));
var a = document.createElement("a");
var file = new Blob([JSON.stringify(json)], {
type: 'text/plain'
});
a.href = URL.createObjectURL(file);
a.download = 'json.txt';
a.click();
});
// load json2canvas
$("#loadJson2Canvas").click(function() {
canvas.loadFromJSON(
$("#myTextArea").val(),
canvas.renderAll.bind(canvas));
});
$('#jsonload').change(function(e) {
var file = e.target.files[0];
if(!file) return;
var reader = new FileReader();
reader.onload = function(f) {
var data = f.target.result;
canvas.loadFromJSON(
JSON.parse(data),
canvas.renderAll.bind(canvas));
};
reader.readAsText(file);
});
$(this).val(null);
return;
});
#myTextArea {
width: 90%;
height: 200px;
}
canvas {
border: 1px solid black
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.22/fabric.min.js"></script>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<button id="text">Add Text</button>
<input type="button" id="addImage" value="Add Web IMG"/><input type="file" id="imgLoader"/>
<br>upload json<input type="file" id="jsonload" />
<br/><br/>
<canvas id='c' width=500 height=500></canvas>
<br/>
<button id='canvas2json'>2JSON</button>
<button id='loadJson2Canvas'>2CANVAS</button>
<br/><br/>
<textarea id='myTextArea' onfocus="this.select();" onmouseup="return false;"></textarea>
Use localStorage to save data to your hard drive. For example
$("#canvas2json").click(function() {
localStorage.myFabricJSCanvas = JSON.stringify(canvas.toJSON());
$("#myTextArea").text('Saved');
});
$("#loadJson2Canvas").click(function() {
canvas.loadFromJSON(
JSON.parse(localStorage.myFabricJSCanvas),
canvas.renderAll.bind(canvas)
);
});
If you want to make it more flexible, such as with more load/save slots, then load/save from a container object such as myCanvases, assign to / get data from its values, and save the myCanvases object to localStorage instead.

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.

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]);
}

Categories