Unable to select an object added using fabricjs - javascript

I'm adding an object into canvas using fabricjs. After adding that object, i an centring it's position using obj.center() method of fabricjs. That object is added to the center. Then i used renderAll() method.
var canvas = new fabric.Canvas('c');
var rect = new fabric.Rect({
fill: 'red',
width: 20,
height: 20,
angle: 45
});
canvas.add(rect);
rect.center();
canvas.renderAll();
But there's one problem. I don't get the hover cursor(cursor with the hand) if i hover on that object. However, if i move my cursor to (0,0) coordinate position of the canvas, hover cursor is shown and clicking at that point selects that added object.
What am i dont wrong?

var canvas = new fabric.Canvas('canvas');
var rect = new fabric.Rect({
fill: 'red',
width: 20,
height: 20,
angle: 45
});
canvas.add(rect);
rect.center();
rect.setCoords();
canvas.renderAll();
canvas {
border: 2px dotted green;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.16/fabric.min.js" charset="utf-8"></script>
<canvas id="canvas" width="400" height="400"></canvas>
after using center() you need to call setCoords().

Related

How to get id by selected item in Fabric.js

I have a canvas and I add a rectangle in it. Each rectangle has its own ID. How can I get the ID by double clicking on the rectangle on the canvas? It can be either Alert or consolLog.
Example: http://fabricjs.com/manage-selection
You can add your own properties to an object. For example[1]:
var rect = new fabric.Rect({
// ...
metaData: {
id: 'myId'
}
});
Then you can get if on select for example:
rect.on('selected', e => {
console.log(canvas.getActiveObject().metaData?.id);
});
Live example:
const canvas = new fabric.Canvas('c');
// create a rectangle object
const rect = new fabric.Rect({
left: 10,
top: 10,
fill: 'red',
width: 40,
height: 40,
metaData: {
id: 'myId'
}
});
// add "selected" listener
rect.on('selected', e => {
console.log(canvas.getActiveObject().metaData?.id);
});
// "add" rectangle onto canvas
canvas.add(rect);
canvas {
border: 1px solid;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.2.0/fabric.min.js" integrity="sha512-Pdu3zoEng2TLwwjnDne3O7zaeWZfEJHU5B63T+zLtME/wg1zfeSH/1wrtOzOC37u2Y1Ki8pTCdKsnbueOlFlMg==" crossorigin="anonymous"></script>
<canvas id="c" width="200" height="200"></canvas>
[1] Of course you can also add it as a direct property (without the metaData) but maybe it will be better to remind you that it's not fabris property but yours

Free drawing inside a object using fabric js

I loaded multiple objects from svg by following code:
var canvas = new fabric.Canvas('drawing');
fabric.loadSVGFromURL('images/circle.svg', function(objects) {
canvas.add.apply(canvas, objects);
canvas.forEachObject(function(o){ o.hasBorders = o.hasControls = false;
});
canvas.renderAll();
});
Now I want to free draw only inside one object(like the image below).How can I achieve that using fabric.js?
You can mask the canvas (using canvas.clipTo) and make it match your SVG form. If it is a circle as in your example image it would be simple.
See the following example:
// define a drawing canvas
const canvas = new fabric.Canvas(
'drawing',
{ isDrawingMode: true }
);
canvas.freeDrawingBrush.color = 'red'
// create a circle (here you could load your svg circle instead)
const circle = new fabric.Circle({
top: 25,
left: 25,
radius: 50,
fill: 'transparent',
stroke: 'black'
});
// create the clipping mask using the circle coordinates
canvas.clipTo = function (ctx) {
ctx.arc(
circle.top + circle.radius,
circle.left + circle.radius,
circle.radius + 1, // +1px for the circle stroke
circle.radius + 1,
Math.PI*2,
true
)
}
// add the circle to the canvas
canvas.add(circle);
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.4.5/fabric.min.js"></script>
<canvas id="drawing" width="600" height="600"></canvas>

Drag object programmatically

I have 3 rectangles on top of eachother like so:
new Fabric.Rect({
width: 200 - index * 30,
height: 20,
hasBorders: false,
selectable: false,
hasControls: false
});
and then I have a click event which detects clicks NEAR (not on the rectangle) the rectangles and makes the top rectangle (highest one of the stack of 3) selectable (to drag it):
var first = this.first().shape; // Fabric.Rect
canvas.setActiveObject(first);
However, this does not set the object on the cursor, to drag the object.
How can I make it so the object is selected, immediately moved to the cursor and enabled to be dragged around once the click event fires?
This should get you fairly close, if I understood you correctly.
Click anywhere inside the black square of the canvas and outside the red object.
var canvas = new fabric.Canvas('c', {
selection: false,
});
var rectangle = new fabric.Rect({
fill: 'red',
left: 10,
top: 10,
width: 100,
height: 100 //,
//padding: 50
});
canvas.on('mouse:down', function(env) {
var x = env.e.offsetX;
var y = env.e.offsetY;
rectangle.setLeft(x - rectangle.width / 2);
rectangle.setTop(y - rectangle.height / 2);
canvas.setActiveObject(rectangle);
rectangle.setCoords();
canvas.renderAll();
canvas.on('mouse:move', function(env) {
var x = env.e.offsetX;
var y = env.e.offsetY;
rectangle.setLeft(x - rectangle.width / 2);
rectangle.setTop(y - rectangle.height / 2);
rectangle.setCoords();
canvas.renderAll();
});
canvas.on('mouse:up', function(env) {
canvas.off('mouse:move');
});
});
canvas.add(rectangle);
canvas.renderAll();
#c {
border: 1px solid black;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/fabric.js/1.4.0/fabric.min.js"></script>
<canvas id="c" width="200" height="200"></canvas>
I intentionally commented out padding on the rectangle, but left it in the code, in case you wanted to use that as your NEAR logic instead of what you already have. If you do choose to use padding as your NEAR logic you will then need to change the on canvas mouse:down event to an on canvas object:selected event.
Also, if you haven't done so already, you might like to take a close look at this Objects Bounding Rectangles example for some further ideas for your NEAR logic, http://fabricjs.com/bounding-rectangle.
Any-who, let me know how you get on, matey!

Use a group as a mask in fabric.js

I want to create a group that is 300px wide and 200px high, and then load a few things inside that group. When I load images in that are larger than the group dimensions, it bleeds outside the group. I'd love to "crop" the image (similar to a CSS overflow:hidden property).
Is this possible?
To accomplish your task you should use the clipTo function on your image, the clipTo function on a group already has an open bug, btw you can work around there, by transpose the dimension and the position of your group to clipTo function:
clipTo :Function ยง Function that determines clipping of an object
(context is passed as a first argument) Note that context origin is at
the object's center point (not left/top corner)
Take a look to official demo, then after the clip operation on your image you can add it to a group(run below script to see an example).
var canvas = window.__canvas = new fabric.Canvas('c');
var path = 'http://fabricjs.com/lib/pug.jpg';
var _img = new Image();
_img.onload = function(img) {
var dog = new fabric.Image(_img, {
left: 100,
top: 100,
width: 300,
height: 300,
selectable: false,
clipName: 'dog',
clipTo: function(ctx) {
ctx.rect(0, 0, 50, 50);
}
});
var group = new fabric.Group([dog], {
left: 100,
top: 100,
width: 100,
height: 100,
borderColor: 'black',
});
canvas.add(group);
};
_img.src = path;
canvas.renderAll();
<script src="//cdnjs.cloudflare.com/ajax/libs/fabric.js/1.4.13/fabric.min.js"></script>
<canvas id="c" height="300" width="300" style="border:1px dashed #333;"></canvas>

Hole in rectangle by SVG Path in KineticJS

globalCompositeOperation with KineticJS describes how to make a hole in a rectangle by a circle. Instead of a circle I would like to use SVG path to make the hole, such as:
m 876.30799 492.53209 c -36.64554 -0.29484 -69.69962 33.8121 -67.84069 70.49382 3.60444 27.60835 34.32996 46.34894 60.8096 40.13747 10.35153 -2.31261 21.0251 -4.39193 30.54799 -9.18203 10.45071 -6.35814 19.46448 -14.76346 29.73686 -21.39213 10.83886 -8.06083 21.32637 -16.94052 29.19035 -28.02964 -1.53049 -9.55445 -13.2442 -8.25504 -20.39998 -9.87533 -12.44629 -2.06296 -25.58989 -5.04642 -34.93228 -14.14783 -10.44361 -7.80509 -20.00756 -17.00681 -27.11185 -28.00433 z
How can I implement the hole, i.e. context.globalCompositeOperation="destination-out";, into the new Kinetic.Path({ data: path });?
EDIT: I have just found an updated version of the circular hole here:
use globalcompositeoperations on KineticJS 4.7.2
Now it is just a question of making it work for SVG path ;)
Using an SVG drawing to reveal an obscured image is fairly complicated.
Here are the steps required:
Create a background layer with an image
Create a foreground layer
Create a rectangle covering the foreground to obscure the background image
Create a Kinetic.Path with SVG data
Convert that SVG path to an image using path.toImage
Create a Kinetic.Shape that draws the SVG image using "destination-out" compositing to reveal the background
The Kinetic.Shape is not draggable, so create another Kinetic.Path with the same SVG data to act as a handle to drag the revealing Kinetic.Shape. When this path-handle is dragged, move the revealing shape to the same coordinates.
Demo: http://jsfiddle.net/m1erickson/7Yvt5/
This demo uses a simple SVG rectangle, but you can use any SVG drawing that you need.
Here is example code:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Prototype</title>
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v5.0.1.min.js"></script>
<style>
body{padding:20px;}
#container{
border:solid 1px #ccc;
margin-top: 10px;
width:300px;
height:300px;
}
</style>
<script>
$(function(){
var stage = new Kinetic.Stage({
container: 'container',
width: 300,
height: 300
});
var bklayer = new Kinetic.Layer();
stage.add(bklayer);
var layer = new Kinetic.Layer();
stage.add(layer);
var path;
var reveal;
var cutoutImage;
//var pathData="M 0,0 L50,0 50,50 0,50 z";
var pathData="M 0,0 L50,0 50,50 0,50 z";
var img=new Image();
img.onload=function(){
start();
}
img.src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/KoolAidMan.png";
function start(){
var image=new Kinetic.Image({
x:0,
y:0,
width:300,
height:300,
image:img
});
bklayer.add(image);
bklayer.draw();
var rect = new Kinetic.Rect({
x: 0,
y: 0,
width: 300,
height: 300,
fill: 'skyblue',
stroke: 'lightgray',
strokeWidth: 3
});
layer.add(rect);
// path filled
var path = new Kinetic.Path({
x: 0,
y: 0,
data:pathData,
fill: 'green',
});
layer.add(path);
// turn the path into an image
cutoutImage=path.toImage({
callback: function(img){
reveal = new Kinetic.Shape({
sceneFunc: function(context) {
var ctx=this.getContext()._context;
var pos=this.pos;
ctx.save();
ctx.globalCompositeOperation="destination-out";
ctx.drawImage(this.image,pos.x,pos.y);
ctx.restore();
},
});
reveal.pos={x:0,y:0};
reveal.image=img;
reveal.position(path1.position());
layer.add(reveal);
// remove the original path
path.remove();
layer.draw();
}
});
// draggable path
path1 = new Kinetic.Path({
x: 0,
y: 0,
data:pathData,
stroke: 'red',
draggable:true
});
path1.on("dragmove",function(){
reveal.pos=this.position();
layer.draw();
});
layer.add(path1);
layer.draw();
} // end start
function addReveal(img){
reveal = new Kinetic.Shape({
sceneFunc: function(context) {
var ctx=this.getContext()._context;
var pos=this.pos;
ctx.save();
ctx.globalCompositeOperation="destination-out";
ctx.drawImage(this.image,pos.x,pos.y);
ctx.restore();
},
});
reveal.pos={x:0,y:0};
reveal.image=img;
reveal.position(path1.position());
layer.add(reveal);
layer.draw();
}
}); // end $(function(){});
</script>
</head>
<body>
<div id="container"></div>
</body>
</html>

Categories