layer system in fabric.js canvas - javascript

I am designing business card designer application with fabric.js canvas in this application i need a system for managing objects z-index and for that i created two buttons for sending selected object in back and front its working fine but in all my designs i have a background image on which all text and objects placed when i send a object back it send back under that BG image how can stop that i mean i want that BG image to always in last position
preview of application
Code i am using now to send object in front and back
var selectedObject;
canvas.on('object:selected', function(event) {
selectedObject = event.target;
});
var sendSelectedObjectBack = function() {
canvas.sendToBack(selectedObject);
}
var sendSelectedObjectToFront = function() {
canvas.bringToFront(selectedObject);
}

var canvas = new fabric.Canvas('c');
fabric.Image.fromURL('https://images2.alphacoders.com/147/147320.png', function(img) {
var oImg = img.set({
left: 0,
top: 0,
angle: 00,
width: canvas.width,
height: canvas.height
});
canvas.setBackgroundImage(oImg).renderAll();
});
canvas.add(new fabric.Circle({
left: 50,
top: 50,
radius: 50,
stroke: 'red',
fill: 'yellow'
}))
canvas.add(new fabric.Rect({
left: 50,
top: 50,
height: 150,
width:150,
stroke: 'red',
fill: 'green'
}))
var sendSelectedObjectBack = function() {
selectedObject = canvas.getActiveObject();
if(selectedObject)
canvas.sendToBack(selectedObject);
canvas.deactivateAll();
canvas.renderAll();
}
var sendSelectedObjectToFront = function() {
selectedObject = canvas.getActiveObject();
if(selectedObject)
canvas.bringToFront(selectedObject);
canvas.deactivateAll();
canvas.renderAll();
}
canvas{
border:2px dotted blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.16/fabric.min.js"></script>
<button onclick='sendSelectedObjectBack();'>Send back</button>
<button onclick='sendSelectedObjectToFront();'>Bring Front</button>
<canvas id='c' width='400' height='400'></canvas>
Set your background using canvas.setBackgroundImage(obj), then it will stay ion back all the time.

First Option
Use canvas.setBackgroundImage(obj)
Second Option
If you don't want to set backgroundImage
You can do it after you send back your selected object, you need to again send your BG object to back.
Try this.
var sendSelectedObjectBack = function() {
canvas.sendToBack(selectedObject); //this will send selected object to back.
// Now loop through all your obj
canvas.forEachObject(function (obj) {
// get your background with id or any other property
if (obj && obj.id.indexOf('BG_') > -1) {
canvas.bringToBack(obj, true); // this will send your BG always at back
}
});
}

Related

Offset issues images when inside group (scaleToWidth)

I'm experiencing an issue when scaling an image inside a group the offset of the image changes. The group consists of a background (Rect) and an image. When scaling the group the offset if the image becomes different. Expected is that the offset remains the same as the background.
I'm using Fabric 4.0.0-beta.6
const canvas = new fabric.Canvas('canvas');
canvas.setWidth(document.body.clientWidth);
canvas.setHeight(document.body.clientHeight);
const bg = new fabric.Rect({
width: 100,
height: 100,
fill: 'red',
});
fabric.Image.fromURL("https://dummyimage.com/300x150/000/fff", function(img) {
img.scaleToWidth(bg.width)
const post = new fabric.Group([bg, img], {
left: 100,
top: 100,
});
post.scaleToWidth(400);
canvas.add(post);
canvas.renderAll();
});
https://jsbin.com/migogekoxu/1/edit?html,css,js,output
Setting the strokeWidth property of the background element to 0 fixed the offset for me.
const canvas = new fabric.Canvas('canvas');
canvas.setWidth(document.body.clientWidth);
canvas.setHeight(document.body.clientHeight);
const bg = new fabric.Rect({
width: 100,
height: 100,
fill: 'red',
strokeWidth: 0, //<---
});
fabric.Image.fromURL("https://dummyimage.com/300x150/000/fff", function(img) {
img.scaleToWidth(bg.width)
const post = new fabric.Group([bg, img], {
left: 100,
top: 100,
});
post.scaleToWidth(400);
canvas.add(post);
canvas.renderAll();
});
<canvas id="canvas"></canvas>
<script src="https://cdn.jsdelivr.net/npm/fabric#4.0.0-beta.6-browser/dist/fabric.min.js"></script>
Obviously that's a bug with fabricJS. If you don't want to fix it on your own in it's source code you can workaround it however.
You can manually correct the position by adding an offset of 0.5 to the image's top and left properties.
For example:
const canvas = new fabric.Canvas('canvas');
const bg = new fabric.Rect({
width: 100,
height: 100,
fill: 'red'
});
fabric.Image.fromURL("https://dummyimage.com/300x150/000/fff", function(img) {
img.scaleToWidth(bg.width);
img.left += 0.5;
img.top += 0.5;
const post = new fabric.Group([bg, img], {
left: 100,
top: 100
});
post.scaleToWidth(400);
canvas.add(post);
canvas.renderAll();
});
<script src="https://cdn.jsdelivr.net/npm/fabric#4.0.0-beta.6-browser/dist/fabric.min.js"></script>
<canvas id="canvas" width="600" height="400"></canvas>

FabricJS v.2 and AngularJS – set canvas background image dynamically

I am using this function to define my fabricJS canvas background image:
$scope.bgImage = function () {
const {
ipcRenderer
} = require('electron')
ipcRenderer.send('openFile', () => {
})
ipcRenderer.once('fileData', (event, filepath) => {
fabric.Image.fromURL(filepath, function (image) {
var hCent = canvas.getHeight / 2;
var wCent = canvas.getWidth / 2;
canvas.setBackgroundColor({
source: filepath,
top: hCent,
left: wCent,
originX: 'center',
originY: 'middle',
repeat: 'no-repeat',
scaleX: 10,
scaleY: 10
}, canvas.renderAll.bind(canvas));
})
})
};
All the parameters shoulkd be change on the fly with some html inputs. Setting the repeat is working fine and i can change it dynamically.
But I am simply not able to change the scale of my background image. Using the above function I would expect that my background image would be:
would be not repeated (ok. is working)
positioned in the middle/center of my canvas (it is not)
would be scaled by a factor of 10 (it is not)
I am making again a newbish mistake? Or ist this realted to some changes in image handling in FabricJS v.2
canvas.setBackgroundImage() Use this function to add Image as a background.
To get Height/Width of canvas use canvas.getHeight()/canvas.getWidth(),these are functions. Or else you can use the properties > canvas.height/canvas.width respectively.
DEMO
// initialize fabric canvas and assign to global windows object for debug
var canvas = window._canvas = new fabric.Canvas('c');
var hCent = canvas.getHeight() / 2;
var wCent = canvas.getWidth() / 2;
canvas.setBackgroundImage('http://fabricjs.com/assets/jail_cell_bars.png', canvas.renderAll.bind(canvas), {
originX: 'center',
originY: 'center',
left: wCent,
top: hCent,
scaleX: 1,
scaleY: 1
});
fabric.util.addListener(document.getElementById('toggle-scaleX'), 'click', function () {
if (!canvas.backgroundImage) return;
canvas.backgroundImage.scaleX = canvas.backgroundImage.scaleX < 1 ? 1 : fabric.util.toFixed(Math.random(), 2);
canvas.renderAll();
});
fabric.util.addListener(document.getElementById('toggle-scaleY'), 'click', function () {
if (!canvas.backgroundImage) return;
canvas.backgroundImage.scaleY = canvas.backgroundImage.scaleY < 1 ? 1 : fabric.util.toFixed(Math.random(), 2);
canvas.renderAll();
});
canvas {
border: 1px solid #999;
}
button {
margin-top: 20px;
}
<script src="https://rawgithub.com/kangax/fabric.js/master/dist/fabric.js"></script>
<canvas id="c" width="600" height="600"></canvas>
<button id="toggle-scaleX">Toggle scaleX</button>
<button id="toggle-scaleY">Toggle scaleY</button>

Fabricjs - How to detect total width/height of canvas where objects exist

So I want to save the canvas based on scale 1 and I want to include all existing/visible objects.
So right now, my canvas is 600x400 and if I were to save it, it would only save what's inside that 600x400, and it respects zoom level (a higher zoom will see less things, a lower zoom will see more things but smaller).
I've gotten around this by running this code:
let zoom = this.canvas.getZoom();
this.canvas.setZoom(1);
let url = this.canvas.toDataURL({width: 1000, height: 700, multiplier: 1});
this.canvas.setZoom(zoom);
window.open(
url,
'_blank' // <- This is what makes it open in a new window.
);
What this does is save the image as 1000x700, so stuff that were past 600px but under 1000 still gets saved.
However, this is hard coded. So I was wondering if there was an existing function or a clean/simple way to detect where all the objects are in and returns the full size (width and height).
fiddle: http://jsfiddle.net/1pxceaLj/3/
Update 1:
var gr = new this.fabric.Group([], {
left: 0,
top: 0
});
this.canvas.forEachObject( (o) => {
gr.add(o);
});
this.canvas.add(gr);
this.canvas.renderAll();
console.log(gr.height, gr.width); // 0 0
Solution
Using group was the best idea. Best example: http://jsfiddle.net/softvar/mRA8Z/
function selectAllCanvasObjects(){
var objs = canvas.getObjects().map(function(o) {
return o.set('active', true);
});
var group = new fabric.Group(objs, {
originX: 'center',
originY: 'center'
});
canvas._activeObject = null;
canvas.setActiveGroup(group.setCoords()).renderAll();
console.log(canvas.getActiveGroup().height);
}
One possibility would be to create a kind of Container using a fabricjs group and adding all created objects to this container.
I updated your fiddle here: http://jsfiddle.net/1pxceaLj/4/
Thus, you could just use group.width and group.height, perhaps adding a little offset or minimum values, and there you are having dynamical value to pass into toDataUrl even when having a smaller canvas.
code:
var canvas = new fabric.Canvas('c');
var shadow = {
color: 'rgba(0,0,0,0.6)',
blur: 20,
offsetX: 10,
offsetY: 10,
opacity: 0.6,
fillShadow: true,
strokeShadow: true
}
var rect = new fabric.Rect({
left: 100,
top: 100,
fill: "#FF0000",
stroke: "#000",
width: 100,
height: 100,
strokeWidth: 10,
opacity: .8
});
var rect2 = new fabric.Rect({
left: 800,
top: 100,
fill: "#FF0000",
stroke: "#000",
width: 100,
height: 100,
strokeWidth: 10,
opacity: .8
});
rect.setShadow(shadow);
//canvas.add(rect);
//canvas.add(rect2);
var gr = new fabric.Group([ rect, rect2 ], {
left: 0,
top: 0
});
canvas.add(gr);
function save()
{
alert(gr.width);
alert(gr.height);
let zoom = canvas.getZoom();
var minheight = 700;
canvas.setZoom(1);
let url = this.canvas.toDataURL({width: gr.width, height: gr.height > minheight ? gr.height : minheight, multiplier: 1});
canvas.setZoom(zoom);
window.open(
url,
'_blank'
);
}

Drag, drop and delete objects on FabricJS canvas

I am having issues getting drag, drop and delete to work on a FabricJS using JQuery.
Select an object and click delete is working with no issues.
Here's a demo of my code Fiddle
I can get drag, drop and delete to work outside of FabricJS (Just dragging unrelated elements to delete) but I'm having difficulty combing the two.
Maybe I need to incorporate the delete button inside the canvas in order for the 'drop' to work? When I try this the #delete element disappears.
JS
var canvas = new fabric.Canvas('canvas');
$("#delete").click(function() {
deleteObjects();
});
//Test objects on the canvas
//Circle
var circle = new fabric.Circle({
left: 200,
top: 150,
radius: 30,
fill: "#ff0072"
});
circle.hasRotatingPoint = true;
canvas.add(circle);
// adding triangle
var triangle = new fabric.Triangle({
left: 130,
top: 150,
strokeWidth: 1,
width: 70,
height: 60,
fill: '#00b4ff',
selectable: true,
originX: 'center'
});
triangle.hasRotatingPoint = true;
canvas.add(triangle);
//Select all objects
function deleteObjects() {
var activeObject = canvas.getActiveObject(),
activeGroup = canvas.getActiveGroup();
if (activeObject) {
canvas.remove(activeObject);
}
}
//Drag and drop delete
$(function() {
$('#delete').droppable({
drop: function(event, ui) {
deleteObjects();
}
});
});
HTML
<section class="canvas__container">
<canvas id="canvas" width="400" height="400"></canvas>
<div id="delete">♻</div>
</section>
(I didn't paste my CSS as that's not particularly relevant)
This should get you close:
var bin;
var selectedObject;
fabric.Image.fromURL('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADUAAAA1CAIAAABuhDQnAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAPPSURBVGhDzZd9U+IwEIfv+3+2Gxln9PAEcYRBURQVkVeFe9rdC2laIFsK+Pzh2Cab/ZF9Sfpr9bMpr+/r6+v5+fn+/v5vyuXl5e8U/uGx2Wwy9Pr6yjQ1KIVZ32w2e3x8xL2oiYHJmGCoS1gw6MPB7e2t+iwF5laVUfqIEcGq1WrqZw9YhKXig75bHzlUiTIfFmRZdbCVHfrIG13yALC4utnMRn2EYM9siwEX22NdrA8b1y8ODY62SCzWd4Sd88GdOs5RoO+gObeJTbkY6qOs1OLoFFZ0Rh95UHkriQfX+UTM6Ot2uzr3RCBApfxnrY+T54SbJyAgOADX+krX7NnZGTeAm5ub8/NzfbUHQS2rPlTruJFOp7NYLGQR+P7+5vHz8/Pl5eX6+lonGfG3UPWV6ykPDw/L5VJWyIPQcjHxe43qM93nBEz8nSvk4+OD6KtBNKys9qKPqtYRC3d3d/P5XFYRptMpBei/ZGXyUg0suEaT6OOarq/toHI8HrPIZDLhWn91dTUajdKVE4g+tz2dasH16kSfte1Rp29vb71e7+LiQt78SeFRtPqQ7ExmVGZG4hphoo8fra+jQRx7A5QqJeySjJoNgs5jueQW8zL6Wq0WqSb2wnA41LG0gbm6YRqKUW+9rSFJVkj0mYxJsqDFs4s0Gh1OISlJcGJdr9cRR3ytvQZJsniiT9/FQeMVSwe+86240WiQjuDKxXozEiuzPupULB00OR3Lgmg/DTA09UKxMsfX10fuE8cguA6izHGnU9PJ5IaO7SITX1N9uPjijx1CXKFXCpDR4MfEO8rUh6n+3a5QFqQUGSbvaYrtdnswGBBThqghioO6Tr0kUCUyM4ZMfzH1Z3IoSEGksFVPT0/6nELcqQz2TB4R7X5JDJn+bD3fAilsJx0xyDYftDKqxnFkzjfs9XUcHPmYiD0QTQ5ZttDtVkC/31fLaNz6iT6wHkFsuRgKoiB/+ML7+7uprYBLPlB91vspLjFxv1J6L3/JRU5kV0C0RlPaCQX3U9bVQSOcEySftBguCpSwvN8H//xUfVDuLl45yFBBKWt9qP4J35d+5cFaH1gvqpXj2p4jow/tJ9zC/OZBRh9Yb0EV4nqyT6gPyn0L74nfU3wK9MGRazmoWZ9ifeSB9YuhNDjKp52jWB9gc4RdxMUWcbBRn3DQXNyUcz479AFlVXnTYcHCas2zWx8QAjpnJSpZhKW2x9QnSp/AAbhnRmLun/0xGPQJOCBvTPdFJmNiVSaY9TmIETlEsHDP55brR/zDIy8ZYkJ8KAspr+84/Gx9q9U/+FfCZwDz5ogAAAAASUVORK5CYII=', function(img) {
img.set({
left: 174,
top: 329,
selectable: false
});
bin = img;
canvas.add(img, circle, triangle);
});
canvas.on('object:selected', function(evn) {
selectedObject = evn.target;
})
canvas.on('mouse:up', function(evn) {
var x = evn.e.offsetX;
var y = evn.e.offsetY;
if (x > bin.left && x < (bin.left + bin.width) &&
y > bin.top && y < (bin.top + bin.height)) {
canvas.remove(selectedObject);
}
});
Here's your JSFiddle updated, https://jsfiddle.net/rekrah/jgruwse0/.
Let me know if you have any further questions.

fabric.js: bind event to background image

I'm trying to bind an event to a background image:
var imgInstance = fabric.Image.fromURL(path, function(oImg) {
oImg.on("selected", function() {
alert();
});
canvas.setBackgroundImage(oImg, canvas.renderAll.bind(canvas), {
top: 0,
left: 0
});
oImg.on("selected", function() {
alert();
});
});
I can get it work. I'm trying to bind mouse events to handle drag, so i can move the image when it's bigger than the canvas, like background-cover css property.
Thoughts?
Thanks guys!
SOLUTION:
var DragStart = false;
canvas.on('mouse:down', function(options) {
if (!options.target) {
DragStart = true;
}else {
DragStart = false;
}
});
canvas.on('mouse:move', function(options) {
if (DragStart) {
var bounds = getBounds(options.e); // validate boundaries between image and canvas
canvas.backgroundImage.top = bounds.y;
canvas.backgroundImage.left = bounds.x;
canvas.renderAll();
}
});
It doesn't seem like you can add events to the background image... but the canvas does emit events and if you click the canvas and no target is set then they must be clicking the background. Background image are not forced to take up the whole background but I'm not sure reasons to not make it take up the whole background.
You do get the mouse event passed in though so if the background image does not take up the whole background you could do that math and figure out where they clicked.
window.canvas = new fabric.Canvas('c', { selection: false, preserveObjectStacking:true });
fabric.Image.fromURL('http://fabricjs.com/assets/pug_small.jpg', function(o) {
canvas.setBackgroundImage(o, canvas.renderAll.bind(canvas), {
top: 0,
left: 0
});
});
canvas.on('mouse:down', function(options) {
if (!options.target) {
console.log('target is null')
}
});
canvas.add(new fabric.Rect({
left: 100,
top: 100,
width: 50,
height: 50,
fill: '#faa',
originX: 'left',
originY: 'top',
centeredRotation: true
}));
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.4/fabric.min.js"></script>
<canvas id="c" width="600" height="600"></canvas>

Categories

Resources