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>
Related
I want to make a drawing app and I can't get the basic functionality to pan a layer nor to edit the svgs and scale, transform.
With paper.js the canvas can be easily resized and fit a div but I need the fabric.js to move and scale objects. Fabric does not resize or fit a div.
In html I have this : <canvas id="canvas" resize></canvas>
and css :
#canvas {
width: 100%;
height: 100%;
background-color: transparent;
}
With this code I can resize the window and the canvas.
But when I use fabric.js it goes back to 300 x 150 and doesn't resize:
var fabric = require('fabric').fabric;
canvas = new fabric.Canvas('canvas');
paper.install(window);
window.onload = function() {
// Setup directly from canvas id:
paper.setup('canvas');
var path = new Path();
path.strokeColor = 'black';
var start = new Point(100, 100);
path.moveTo(start);
path.lineTo(start.add([ 200, -50 ]));
view.draw();
}
I don't think that using fabric.js with paper.js is a good idea as they might have some conflicting points.
If what you lack from fabric.js is the ability to resize the canvas, here is a fiddle demonstrating how you can do.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Debug</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.1.0/fabric.js"></script>
<style>
body {
margin : 0;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
// Init Fabric.js canvas
var canvas = new fabric.Canvas('canvas');
// Draw rectangle
canvas.add(new fabric.Rect({
left: 200,
top: 200,
width: 500,
height: 500,
fill: '#D81B60',
hasControls: true
}));
// Bind and call resize callback
window.onresize = resize;
resize();
// On resize...
function resize() {
// ...set canvas size to window size
canvas.setWidth(window.innerWidth);
canvas.setHeight(window.innerHeight);
}
</script>
</body>
</html>
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>
Is there any option to add pattern with Image AND Color?
Anytime, when I apply an image as a pattern, even though the image is transparent, Fabric adds it with some kind of "gray layer".
Link to Fiddler
var canvas = window.canvas = new fabric.Canvas('c');
var rect = new fabric.Rect({
width: 256,
height: 256,
fill: 'red'
});
canvas.add(rect);
rect.center().setCoords();
fabric.util.loadImage('https://assets-cdn.github.com/images/modules/site/integrators/slackhq.png', function (img) {
rect.setPatternFill({
source: img,
repeat: 'no-repeat'
});
canvas.renderAll();
});
Any idea?
Assuming the image has transparent background, you can use fabric.StaticCanvas as fabric.Pattern's source:
var imgPath = 'https://cdn.sstatic.net/Sites/stackoverflow/company/img/logos/so/so-icon.png'
var imgColor = 'grey'
var canvas = this.__canvas = new fabric.Canvas('c')
fabric.Image.fromURL(imgPath, function(img) {
var patternSourceCanvas = new fabric.StaticCanvas()
patternSourceCanvas.add(img)
patternSourceCanvas.setBackgroundColor(imgColor, patternSourceCanvas.renderAll.bind(patternSourceCanvas))
var pattern = new fabric.Pattern({
source: function() {
return patternSourceCanvas.getElement();
},
repeat: 'repeat'
})
// create a rectangle object
var rect = new fabric.Rect({
fill: pattern,
width: 400,
height: 400
})
// "add" rectangle onto canvas
canvas.add(rect)
})
<script src="http://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.9/fabric.min.js"></script>
<canvas id="c" width="500" height="500"></canvas>
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().
I 'm working on a navigation project.I have a map like this.
I need to rotate this map according to the green point which is on [400,300] px on the stage.I tried a lot of things but it did not work.Here is my rotate function.
function rotateMap(layer, t_angle) {
layer.setOffset(400, 300);
layer.rotateDeg(t_angle);
layer.draw();
layer.move(400, 300);
};
This rotates map only one time.When i tried to use it in a timer event,the map doesn't appears after first rotation.Is there any efficial way to do this rotation?
**EDIT : I have 2 layers in my stage that one of for lines and the other one for the point.
Instead of offsetting the layer, put the lines in a group and rotate the group.
I’m assuming your full screen is 800x600 and you want to rotate around the center 400x300
The Method
Put your lines on a group
Set your group width x height to 800x600
Set your group x/y to 400/300
Set your group offset to 400,300
To rotate, call group.rotateDeg(degrees)
Be sure to layer.draw() after each rotation.
Example of your map-group:
var map=new Kinetic.Group({
x:400,
y:300,
width:800,
height:600,
offset:[400,300]
});
layer.add(map);
Example of your rotation function
function rotateMap(t_angle) {
map.rotateDeg(t_angle);
layer.draw();
};
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/wDxcT/
<!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-v4.7.0.min.js"></script>
<style>
#container{
border:solid 1px #ccc;
margin-top: 10px;
width:400px;
height:400px;
}
</style>
<script>
$(function(){
var stage = new Kinetic.Stage({
container: 'container',
width: 400,
height: 400
});
var layer = new Kinetic.Layer();
stage.add(layer);
var t_angle=30;
function rotateMap(t_angle) {
map.rotateDeg(t_angle);
layer.draw();
};
var map=new Kinetic.Group({
x:200,
y:200,
width:200,
height:200,
offset:[100,100]
});
layer.add(map);
var rect = new Kinetic.Rect({
x: 0,
y: 0,
width: 200,
height: 200,
fill: 'skyblue',
stroke: 'lightgray',
strokeWidth: 3
});
map.add(rect);
var north = new Kinetic.Text({
x: map.getWidth()/2-15,
y: 25,
text: 'N',
fontSize: 42,
fontFamily: 'Calibri',
fill: 'green'
});
map.add(north);
var c=new Kinetic.Circle({
x:200,
y:200,
radius:5,
fill:"green"
});
layer.add(c);
layer.draw();
$("#rotate").click(function(){
rotateMap(t_angle);
});
}); // end $(function(){});
</script>
</head>
<body>
<button id="rotate">rotate</button>
<div id="container"></div>
</body>
</html>