Related
I am trying to create a whiteboarding web app using HTML5 and Canvas.
I have implement a simple pen and paintbrush shaped pen with help from this brilliant article:
http://perfectionkills.com/exploring-canvas-drawing-techniques/
My issue is withe dotted line pen and highlighter pen.
The dotted line looks like a simple unbroken line if the mouse moves slowly, and with large gaps if moved quickly. What I want is a consistently spaced dotted line.
I tried setting the context.setLineDash but this has no effect on the result.
I then tried to calculate a minimum distance between the last point and current point and draw if over the dot gap lenth but this also does not seemeingly affect the result.
Here is my code:
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var lastPoint;
var isDrawing = false;
context.lineWidth = 4;
context.lineJoin = context.lineCap = 'round';
canvas.onmousedown = function(e) {
isDrawing = true;
lastPoint = {
x: e.clientX,
y: e.clientY
};
lastPoint = {
x: e.offsetX,
y: e.offsetY
};
};
canvas.onmousemove = function(e) {
if (!isDrawing) return;
context.beginPath();
context.strokeStyle = 'red';
context.fillStyle = 'red';
mx = e.clientX; // mouse pointer and stroke path is off if use this
my = e.clientY;
mx = e.offsetX; // mouse pointer and stroke path match using this
my = e.offsetY;
context.setLineDash([5, 25]);
xlen = Math.abs(mx - lastPoint.x) + context.lineWidth;
ylen = Math.abs(my - lastPoint.y) + context.lineWidth;
gap = Math.sqrt((ylen * ylen) + (xlen * xlen));
if (gap >= 5) {
context.moveTo(lastPoint.x, lastPoint.y);
context.lineTo(mx, my);
context.stroke();
lastPoint = {
x: mx,
y: my
};
}
};
canvas.onmouseup = function() {
isDrawing = false;
};
html,body,canvas
{
width: 100%;
height: 100%;
margin: 0;
}
<canvas id="canvas" ></canvas>
The result is this:
With the highlighter, I get overlapping points which give dark spots on the path. The code for this is:
context.globalAlpha = 0.3;
context.moveTo(lastPoint.x, lastPoint.y);
context.lineTo(mx, my);
context.stroke();
lastPoint = { x: mx, y: my };
The result:
In the below Image I have three boxes in the canvas and there are three buttons at the bottom of the image. Whenever I click a button, the corresponding object in the canvas gets selected(i,e, when I click the green button, green rectangle in the canvas, gets selected).
My requirement is to highlight only the selected portion and other portion of the canvas should be grayed out. (Ex: If I click the green button green rectangle should be selected and other portion should be overlayed with a gray background).
Js Fiddle Link: https://jsfiddle.net/rnvs2hdk/1/
var canvas = new fabric.Canvas('c');
canvas.backgroundColor = 'yellow';
var li= []
canvas.renderAll();
fabric.Image.fromURL('http://fabricjs.com/assets/pug_small.jpg', function(myImg) {
var img1 = myImg.set({ left: 0, top: 0 ,width:400,height:500});
canvas.add(img1);
var green = new fabric.Rect({
left: 50,
top: 50,
width: 50,
height: 50,
fill: 'rgba(255,255,255,1)',
stroke: 'rgba(34,177,76,1)',
strokeWidth: 5,
name:"green"
});
var yellow = new fabric.Rect({
left: 150,
top: 50,
width: 50,
height: 50,
fill: 'rgba(255,255,255,1)',
stroke: 'rgba(255,255,0,1)',
strokeWidth: 5,
name:"yellow"
});
var red = new fabric.Rect({
left: 250,
top: 50,
width: 50,
height: 50,
fill: 'rgba(255,255,255,1)',
stroke: 'rgba(255,0,0,1)',
strokeWidth: 5,
name:"red"
});
canvas.add(green, yellow,red);
li.push(green);
li.push(yellow);
li.push(red);
li.some(v=>{
var btn = document.createElement("BUTTON"); // Create a <button> elem
btn.innerHTML = v.name;
btn.addEventListener('click',function(e){
var name = e.target
if(name.innerText == "green"){
canvas.setActiveObject(li[0]);
}
if(name.innerText == "yellow"){
canvas.setActiveObject(li[1]);
}
if(name.innerText == "red"){
canvas.setActiveObject(li[2]);
}
});// Insert text
document.body.appendChild(btn);
});
console.log(li);
});
Expected Result:(example)
Here's my solution. Using the after:render event, you can perform canvas draw actions over each frame after it is rendered. This approach has the benefit of avoiding having to create and destroy fabric objects as needed which is an expensive operation.
Be sure to call the .setCoords() method during actions like scaling and moving so that objects will update their position information while performing these actions.
var canvas = new fabric.Canvas('c');
canvas.backgroundColor = 'yellow';
var li = [];
canvas.renderAll();
fabric.Image.fromURL('http://fabricjs.com/assets/pug_small.jpg', function(myImg) {
var img1 = myImg.set({
left: 0,
top: 0,
width: 400,
height: 500
});
canvas.add(img1);
var green = new fabric.Rect({
left: 50,
top: 50,
width: 50,
height: 50,
fill: 'rgba(255,255,255,1)',
stroke: 'rgba(34,177,76,1)',
strokeWidth: 5,
name: "green",
hasRotatingPoint: false
});
var yellow = new fabric.Rect({
left: 150,
top: 50,
width: 50,
height: 50,
fill: 'rgba(255,255,255,1)',
stroke: 'rgba(255,255,0,1)',
strokeWidth: 5,
name: "yellow",
hasRotatingPoint: false
});
var red = new fabric.Rect({
left: 250,
top: 50,
width: 50,
height: 50,
fill: 'rgba(255,255,255,1)',
stroke: 'rgba(255,0,0,1)',
strokeWidth: 5,
name: "red",
hasRotatingPoint: false
});
canvas.add(green, yellow, red);
li.push(green);
li.push(yellow);
li.push(red);
li.some(v => {
var btn = document.createElement("BUTTON"); // Create a <button> elem
btn.innerHTML = v.name;
btn.addEventListener('click', function(e) {
var name = e.target
if (name.innerText == "green") {
canvas.setActiveObject(li[0]);
}
if (name.innerText == "yellow") {
canvas.setActiveObject(li[1]);
}
if (name.innerText == "red") {
canvas.setActiveObject(li[2]);
}
}); // Insert text
document.body.appendChild(btn);
});
console.log(li);
});
canvas.on({
'object:moving': function(e) {
//makes objects update their coordinates while being moved
e.target.setCoords();
},
'object:scaling': function(e) {
//makes objects update their coordinates while being scaled
e.target.setCoords();
}
});
//the after:render event allows you to perform a draw function on each frame after it is rendered
canvas.on('after:render', function() {
var ctx = canvas.contextContainer,
obj = canvas.getActiveObject();
if (obj) {
//set the fill color of the overlay
ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
var bound = obj.getBoundingRect();
ctx.beginPath();
//draw rectangle to the left of the selection
ctx.rect(0, 0, bound.left, canvas.height);
//draw rectangle to the right of the selection
ctx.rect(bound.left + bound.width, 0, canvas.width - bound.left - bound.width, canvas.height);
//draw rectangle above the selection
ctx.rect(bound.left, 0, bound.width, bound.top);
//draw rectangle below the selection
ctx.rect(bound.left, bound.top + bound.height, bound.width, canvas.height - bound.top - bound.height)
ctx.fill();
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.5.0/fabric.min.js"></script>
<canvas id="c" width="400" height="400"></canvas>
<div id="bt"></div>
I have Accomplished this creating 4 more rectangles around the actual rectangle. I have grayed out the outer rectangles so it gives the overlay effect. Also, I will delete all the rectangles before creating a new one.
let blankColor = "rgba(0,0,0,0)";
let grayOut = "rgba(0,0,0,0.4)";
let rect = createRect(x, y, width, height, 1, blankColor);
let rect1 = createRect(0, 0, getViewPortDimensions()[0], y, 0, grayOut);
let rect2 = createRect(0, y + height, getViewPortDimensions()[0], getViewPortDimensions()[1] - (y + height), 0, grayOut);
let rect3 = createRect(0, y, x, height, 0, grayOut);
let rect4 = createRect(x + width, y, getViewPortDimensions()[0] - (x + width), height, 0, grayOut);
state.canvas.add(rect, rect1, rect2, rect3, rect4);
Deleting the already drawn rectangle:
state.canvas.forEachObject((o, index) => {
if (index != 0) {
state.canvas.remove(o);
}
})
I'd like to use a polyline to connect two rectangles and stay connected to the same points on the respective rectangles as the rectangles move (specifically, for two rectangles on a page, I'd like the polyline to connect the bottom middle point of one rectangle to the top middle part of another rectangle. The reason I'm using a PolyLine is because I will eventually be adding in elbows down the road as well). I'm having issues with the polyline coordinates updating in response to the moving rectangles though.
This demonstrates some of the issues I am hitting:
var canvas = new fabric.Canvas('c');
rect = null;
line = null;
function addLine(x1, y1, x2, y2) {
var coords = [{x: x1, y: y1}, {x: x2, y: y2}];
this.line = new fabric.Polyline(coords, {
stroke: 'green',
strokeWidth: 5,
fill: 'rgba(0,0,0,0)',
selectable: true,
evented: false
});
this.canvas.add(this.line);
}
function addRect(left, top, width, height, line1, line2, line3, line4) {
this.rect = new fabric.Rect({
left: left,
top: top,
width: width,
height: height,
fill: '#9f9',
originX: 'left',
originY: 'top',
centeredRotation: true
});
this.rect.line1 = line1;
this.rect.line2 = line2;
this.rect.line3 = line3;
this.rect.line4 = line4;
this.canvas.add(this.rect);
}
var r1_left = 10;
var r1_top = 20;
var r1_width = 125;
var r1_height = 150;
var r2_left = 350;
var r2_top = 300;
var r2_width = 125;
var r2_height = 150;
addLine(r1_left + r1_width/2, r1_top + r1_height, r2_left + r2_width/2, r2_top);
addRect(r1_left, r1_top, r1_width, r1_height, null, null, this.line, null);
addRect(r2_left, r2_top, r2_width, r2_height, this.line, null, null, null);
this.canvas.renderAll();
this.canvas.on('object:moving', function(e) {
var p = e.target;
if (p.line1) {
let x_2_new = p.left + p.width/2;
let y_2_new = p.top;
p.line1.set('points', [p.line1.points[0], {'x': x_2_new, 'y': y_2_new}]);
p.line1.set('height', y_2_new - p.line1.points[0]['y']);
p.line1.set('width', x_2_new - p.line1.points[0]['x']);
p.set('oCoords', p.line1.calcCoords());
} else if (p.line2) {
p.line2.set({'points': [{'x': p.left + p.width, 'y': p.top + p.height/2}, p.line2.points[1]]});
} else if (p.line3) {
p.line3.set({'points': [{'x': p.left + p.width/2, 'y': p.top + p.height}, p.line3.points[1]]});
} else if (p.line4) {
p.line4.set({'points': [p.line4.points[0], {'x': p.left, 'y': p.top + p.height/2}]});
}
});
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.js"></script>
</head>
<body>
<canvas id="c" width="700" height="700" style="border:1px solid #ccc"></canvas>
<script>
</script>
</body>
</html>
For the upper rectangle, I have tried simply setting the x and y coordinates as the object moves. With this, I encounter the error that x and y seem to be bound by the oCoords and aCoords of the line.
For the lower rectangle, I have tried setting the coordinates directly. With this, the entire line seems to shift around the page.
Any advice about what I could change here would be great. Thanks!
Here is the code, what you need, jsfiddle
(function() {
var canvas = this.__canvas = new fabric.Canvas('c', { selection: false });
fabric.Object.prototype.originX = fabric.Object.prototype.originY = 'center';
function makeCircle(left, top, line1, line2) {
var c = new fabric.Rect({
top: top,
left: left,
width: 30,
height: 30,
selection: false,
fill: '#ccc'
});
c.hasControls = c.hasBorders = false;
c.line1 = line1;
c.line2 = line2;
return c;
}
function makeLine(coords) {
return new fabric.Line(coords, {
fill: 'red',
stroke: 'red',
strokeWidth: 5,
selectable: false,
evented: false,
});
}
var line = makeLine([ 250, 125, 250, 375 ]),
line2 = makeLine([ 250, 375, 250, 350 ]);
canvas.add(line);
canvas.add(
makeCircle(line.get('x1'), line.get('y1'), null, line),
makeCircle(line.get('x2'), line.get('y2'), line, line2),
);
canvas.on('object:moving', function(e) {
var p = e.target;
p.line1 && p.line1.set({ 'x2': p.left, 'y2': p.top });
p.line2 && p.line2.set({ 'x1': p.left, 'y1': p.top });
canvas.renderAll();
});
})();
<div>
<canvas id="c" width="700" height="575" style="border:1px solid #999"></canvas>
</div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/fabric.js/2.7.0/fabric.min.js"></script>
I wrote an app where I am trying to hide right eye of the dog using fabric.js rectangle.
But if I resize the window the rectangle is moved.
Here's the fiddle and code :
`
var canvas = this.__canvas = new fabric.Canvas('c');
canvas.setWidth(window.innerWidth*0.98);
canvas.setHeight(window.innerHeight*0.98);
canvas.backgroundColor = 'pink';
canvas.selection = false;
var x1=150, y1=150, x2=180, y2=180, img1;
var rect = new fabric.Rect({
stroke: 'blue',
opacity: 1,
strokeWidth: 2,
selectable: true,
fill : 'green',
left : x1,
top : y1,
width : x2,
height : y2
});
fabric.Image.fromURL('http://fabricjs.com/assets/pug_small.jpg', function(myImg) {
//i create an extra var for to change some image properties
img1 = myImg.set({ left: 0, top: 0 ,width:canvas.width,height:canvas.height});
canvas.add(img1);
canvas.add(rect);
});
window.addEventListener("resize", function() {
console.log('resizing');
var dims = {
w : canvas.width,
h : canvas.height
};
canvas.setWidth(window.innerWidth*0.98);
canvas.setHeight(window.innerHeight*0.98);
img1.width = window.innerWidth*0.98;
img1.height = window.innerHeight*0.98;
rect.left *= dims.w/canvas.width;
rect.top *= dims.h/canvas.height;
rect.width *= dims.w/canvas.width;
rect.height *= dims.h/canvas.height;
});`
Am I missing something?
Here's the code for how to draw circle using Fabricjs : Draw Circle using Mouse.
I want to achieve the same for ellipse . Because drawing circle freely will depend on radius but I want to draw a egg shaped or oval shaped area then , radius is not helping me , hence I am looking to use Ellipse here.
Code for free drawing circle :
`
var canvas = new fabric.Canvas("canvas2");
var circle, isDown, origX, origY;
canvas.on('mouse:down', function(o){
isDown = true;
var pointer = canvas.getPointer(o.e);
origX = pointer.x;
origY = pointer.y;
circle = new fabric.Circle({
left: origX,
top: origY,
originX: 'left',
originY: 'top',
radius: pointer.x-origX,
angle: 0,
fill: '',
stroke:'red',
strokeWidth:3,
});
canvas.add(circle);
});
canvas.on('mouse:move', function(o){
if (!isDown) return;
var pointer = canvas.getPointer(o.e);
var radius = Math.max(Math.abs(origY - pointer.y),Math.abs(origX - pointer.x))/2;
if (radius > circle.strokeWidth) {
radius -= circle.strokeWidth/2;
}
circle.set({ radius: radius});
if(origX>pointer.x){
circle.set({originX: 'right' });
} else {
circle.set({originX: 'left' });
}
if(origY>pointer.y){
circle.set({originY: 'bottom' });
} else {
circle.set({originY: 'top' });
}
canvas.renderAll();
});
canvas.on('mouse:up', function(o){
isDown = false;
});
`
Can anyone do it?
you can do similarly to other shape cases:
How to freedraw Circle in fabricjs using mouse?
var canvas = new fabric.Canvas("canvas2");
var ellipse, isDown, origX, origY;
canvas.on('mouse:down', function(o){
isDown = true;
var pointer = canvas.getPointer(o.e);
origX = pointer.x;
origY = pointer.y;
ellipse = new fabric.Ellipse({
left: origX,
top: origY,
originX: 'left',
originY: 'top',
rx: pointer.x-origX,
ry: pointer.y-origY,
angle: 0,
fill: '',
stroke:'red',
strokeWidth:3,
});
canvas.add(ellipse);
});
canvas.on('mouse:move', function(o){
if (!isDown) return;
var pointer = canvas.getPointer(o.e);
var rx = Math.abs(origX - pointer.x)/2;
var ry = Math.abs(origY - pointer.y)/2;
if (rx > ellipse.strokeWidth) {
rx -= ellipse.strokeWidth/2
}
if (ry > ellipse.strokeWidth) {
ry -= ellipse.strokeWidth/2
}
ellipse.set({ rx: rx, ry: ry});
if(origX>pointer.x){
ellipse.set({originX: 'right' });
} else {
ellipse.set({originX: 'left' });
}
if(origY>pointer.y){
ellipse.set({originY: 'bottom' });
} else {
ellipse.set({originY: 'top' });
}
canvas.renderAll();
});
canvas.on('mouse:up', function(o){
isDown = false;
});
<script type="text/javascript" src="http://www.deltalink.it/andreab/fabric/fabric.js" ></script>
<canvas id="canvas2" width=500 height=500 ></canvas>