Adding objects to a rotated Group - javascript

When I add an object to a group that is rotated in FabricJS it doesn't respect the angle of the other objects.
Steps to reproduce:
Select an object
Click add to group
Select group
Click Rotate group
Click add to group
The group now contains objects that have different angles.
var canvas = new fabric.Canvas("canvas", {
width: 400,
height: 400
});
canvas.add(new fabric.Triangle({
top: 100,
left: 100,
width: 50,
height: 50,
fill: '#f55'
}));
$("#rotate").on("click", function() {
var currentObject = canvas.getActiveObject();
var currentAngle = currentObject.angle;
var adjustedAngle = currentAngle + 90;
if (adjustedAngle >= 360) {
adjustedAngle = 0;
}
currentObject.setAngle(adjustedAngle);
canvas.renderAll();
});
$("#add").on('click', function() {
if (canvas.getActiveObject()) {
var activeObject = canvas.getActiveObject();
var object;
console.log(activeObject.type);
if (activeObject.type == "triangle") {
object = fabric.util.object.clone(activeObject);
object.top = activeObject.top;
object.left = activeObject.left + 25;
object.fill = "#0000FF";
var tempAngle = activeObject.angle;
activeObject.angle = 0;
object.angle = 0;
var group = new fabric.Group([activeObject, object], {
angle: tempAngle
});
canvas.add(group);
canvas.setActiveObject(group);
canvas.remove(activeObject);
} else {
var activeGroup = activeObject;
var lastAddedObject = activeGroup.getObjects()[activeGroup.size() - 1];
object = fabric.util.object.clone(lastAddedObject);
object.set('top', lastAddedObject.top + activeGroup.top + activeGroup.height / 2);
object.set('left', lastAddedObject.left + activeGroup.left + activeGroup.width / 2 + 25);
console.log(lastAddedObject.angle);
activeGroup.addWithUpdate(object);
canvas.renderAll();
}
}
});
canvas {
border: 1px solid red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.0-rc.1/fabric.js"></script>
<button id="rotate">Rotate</button>
<button id="add">Add to Group</button>
<canvas id="canvas" width=400 height=400></canvas>

I linked the latest version where adding items to rotated group works.
I do not really understand what effect you are trying to achieve.
If group has angle of 30, when you add an object with angle of 0, it will not be rotated.
if you want an object to jump in a rotated group and be rotated as the other, you have to add some calculations. In those calculations you have to consider that everytime you use addWithUpdate the group is destroyed and created again and that reset the angle to 0.
var canvas = new fabric.Canvas("canvas", {
width: 400,
height: 400
});
canvas.add(new fabric.Triangle({
top: 100,
left: 100,
width: 50,
height: 50,
fill: '#f55'
}));
$("#rotate").on("click", function() {
var currentObject = canvas.getActiveObject();
var currentAngle = currentObject.angle;
var adjustedAngle = currentAngle + 90;
if (adjustedAngle >= 360) {
adjustedAngle = 0;
}
currentObject.setAngle(adjustedAngle);
canvas.renderAll();
});
$("#add").on('click', function() {
if (canvas.getActiveObject()) {
var activeObject = canvas.getActiveObject();
var object;
console.log(activeObject.type);
if (activeObject.type == "triangle") {
object = fabric.util.object.clone(activeObject);
object.top = activeObject.top;
object.left = activeObject.left + 25;
object.fill = "#0000FF";
var tempAngle = activeObject.angle;
activeObject.angle = 0;
object.angle = 0;
var group = new fabric.Group([activeObject, object], {
});
canvas.add(group);
canvas.setActiveObject(group);
canvas.remove(activeObject);
} else {
var activeGroup = activeObject;
var lastAddedObject = activeGroup.getObjects()[activeGroup.size() - 1];
object = fabric.util.object.clone(lastAddedObject);
object.set('top', lastAddedObject.top + activeGroup.top + activeGroup.height / 2);
object.set('left', lastAddedObject.left + activeGroup.left + activeGroup.width / 2 + 25);
//object.set('angle', activeObject.angle);
activeGroup.addWithUpdate(object);
canvas.renderAll();
}
}
});
canvas {
border: 1px solid red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<script src="http://www.deltalink.it/andreab/fabric/fabric.js"></script>
<button id="rotate">Rotate</button>
<button id="add">Add to Group</button>
<canvas id="canvas" width=400 height=400></canvas>

Related

How to position image on canvas same as overlaped div of same dimension

I have two div of same dimension which overlap each other using z-index.
Ist div contain image and 2nd div contain a canvas
I am trying to draw image on canvas same place as Ist div. I am using hammerjs library to zoomin/out , reposition and rotate image. I found these code somewhere else
function getMeta(url){
img = new Image();
var remoteImage = {}
img.src = url;
remoteImage.width = img.naturalWidth
remoteImage.height = img.naturalHeight
remoteImage.src = url;
return remoteImage
}
var urlImage = getMeta('https://www.penghu-nsa.gov.tw/FileDownload/Album/Big/20161012162551758864338.jpg')
var text = document.querySelector("#text");
var oImg=document.querySelector("#img_scan");
oImg.style['transition-duration'] = '100ms';
var timeInMs = 0;
var preAngle = 0;
var rotateAngle = 0;
var preRotation = 0;
var originalSize = {
width : oImg.offsetWidth,
height : oImg.offsetHeight,
}
var current = {
x : 0,
y : 0,
z : 1,
angle : 0,
width: originalSize.width,
height: originalSize.height,
}
var last = {
x : 0,
y : 0,
z : 1,
}
var oImgRec = oImg.getBoundingClientRect();
var cx = oImgRec.left + oImgRec.width * 0.5;
var cy = oImgRec.top + oImgRec.height * 0.5;
var imageCenter = {
x:cx,
y:cy
}
var pinchImageCenter = {}
var deltaIssue = { x: 0, y: 0 };
var pinchStart = { x: undefined, y: undefined, isPanend:false}
var panendDeltaFix = {x:0,y:0,isPanend:false}
var pinchZoomOrigin = undefined;
var lastEvent = ''
var hammer = new Hammer(oImg);
hammer.get('pinch').set({enable: true});
hammer.get('pan').set({direction: Hammer.DIRECTION_ALL}).recognizeWith(hammer.get('pinch'));
hammer.on("pinchstart", function(e) {
last.x = current.x;
last.y = current.y;
pinchStart.x = e.center.x;
pinchStart.y = e.center.y;
pinchImageCenter = {
x: imageCenter.x + last.x,
y: imageCenter.y + last.y
}
lastEvent = 'pinchstart';
});
hammer.on("pinchmove", function(e) {
if(preAngle == 0){
preAngle = Math.round(e.rotation);
preRotation = Math.round(e.rotation);
}else{
if(Math.abs(Math.round(e.rotation)-preRotation)>=300){
if(e.rotation > 0){
preAngle+=360;
}else if(e.rotation < 0){
preAngle-=360;
}
}
current.angle = rotateAngle + (Math.round(e.rotation)-preAngle);
preRotation = Math.round(e.rotation);
}
var newScale = (last.z * e.scale) >= 0.1 ? (last.z * e.scale) : 0.1;
var d = scaleCal(e.center, pinchImageCenter, last.z, newScale)
current.x = d.x + last.x;
current.y = d.y + last.y;
current.z = d.z + last.z;
update();
lastEvent = 'pinchmove';
});
hammer.on("pinchend", function(e) {
last.x = current.x;
last.y = current.y;
last.z = current.z;
rotateAngle = current.angle;
preAngle = 0;
lastEvent = 'pinchend';
});
hammer.on("panmove", function(e) {
var panDelta = {
x:e.deltaX,
y:e.deltaY
}
if (lastEvent !== 'panmove') {
deltaIssue = {
x: panDelta.x,
y: panDelta.y
}
}
current.x = (last.x+panDelta.x-deltaIssue.x);
current.y = (last.y+panDelta.y-deltaIssue.y);
lastEvent = 'panmove'
update();
});
hammer.on("panend", function(e) {
last.x = current.x;
last.y = current.y;
lastEvent = 'panend';
});
hammer.on('tap', function(e) {
if((Date.now()-timeInMs)<300){
if(last.z > 1){
last.z = 1;
current.z = 1;
update();
}else if(last.z <= 1){
last.z = 2;
current.z = 2;
update();
}
}
timeInMs = Date.now();
lastEvent = 'tap';
});
function scaleCal(eCenter, originCenter, currentScale, newScale) {
var zoomDistance = newScale - currentScale;
var x = (originCenter.x - eCenter.x)*(zoomDistance)/currentScale;
var y = (originCenter.y - eCenter.y)*(zoomDistance)/currentScale;
var output = {
x: x,
y: y,
z: zoomDistance
}
return output
}
function update() {
current.height = originalSize.height * current.z;
current.width = originalSize.width * current.z;
if(current.z < 0.1){
current.z = 0.1;
}
oImg.style.transform = " translate3d(" + current.x + "px, " + current.y + "px, 0)rotate("+current.angle+"deg)scale("+current.z+")"
}
So by above code user can zoomin/zoom out , rotate image . After they set image in their desired position i am putting that image on canvas so i can use dataurl method to save image later.
I tried below code to draw image on canvas same place as div ,same dimension and with same angle but sadly image is not getting exactly same positioned as div
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
$("#btn").click(copyoncanvas);
function copyoncanvas(){
var bound=objimg.getBoundingClientRect();
var objimg=new Image();
objimg.src="https://www.penghu-nsa.gov.tw/FileDownload/Album/Big/20161012162551758864338.jpg";
ctx.drawImage(objimg,bound.left,bound.top,current.width,current.height);
drawRotated(current.angle,bound.left,bound.top);
}
function drawRotated(degrees,l,t){
const objimg=document.getElementById("img_scan");
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.save();
var x=canvas.width/2;
var y=canvas.height/2;
ctx.translate(x,y);
ctx.rotate(degrees * Math.PI/180);
ctx.drawImage(objimg,-l,-t,current.width,current.height);
ctx.restore();
}
I think my problem is with drawrotated function because it work fine without using it but i want to rotate image on canvas too
HTML
<div id="container">
<div class="box"><canvas id="canvas"></canvas></div>
<div id="imgcont">
<img src="https://www.penghu-nsa.gov.tw/FileDownload/Album/Big/20161012162551758864338.jpg" id="img_scan" class="img-custom-img2"/>
</div>
</div>
<button id="btn">Copy on canvas</button>
CSS
#container{
position:relative;margin: 0px;
padding:0px;background:#ff0;
top:0px; overflow:hidden;
width:300px;
border:1px solid #000; height:250}
#img_scan,.box{
width:100%; height:100%;
position: absolute;
top: 0;
left: 0; margin:0; padding:0px
}
.box{z-index:98;height:250}
#canvas{width:100%;margin:0;
padding:0;
width:300;height:250}
#img_scan{width:300px;height:250px}
#imgcont{width:100%;height:250px;z-index:99}

Pointing arrow on precise location on donut chart

I am trying to point to certain parts on a donut chart. The chart is divided into 9 parts.
comArray is the main array which contain all parts. angleArray is the angles in degrees for each part. useArray contains the parts that should be located through the pointer.
My code is working for sometime but then it starts locating on some different point. For example, if pointer needs to point to 3 it locates properly but after 1 hour it will start locating on the 6 part for 3 part.
What I am doing wrong? You can check the working example here: https://bishttech.com/newtest/
$(document).ready(function() {
var comArray = [1, 2, 3, 4, 5, 6, 7, 8, 9]; // main array of all parts
var angleArray = [20, 60, 100, 140, 180, 220, 260, 300, 340]; // angle in degrees for all parts
var useArray = [3, 6, 8, 2]; // pointer should stop on these parts
//console.log(areacount);
var canvas = document.getElementById("chart");
var ctx = canvas.getContext("2d");
function drawMultiRadiantCircle(xc, yc, r, radientColors) {
var partLength = (2 * Math.PI) / radientColors.length;
var start = 0;
var gradient = null;
var startColor = null,
endColor = null;
var startAngle = 0;
for (var i = 0; i < radientColors.length; i++) {
startColor = radientColors[i];
// endColor = radientColors[(i + 1) % radientColors.length];
endColor = adjust(startColor, -30);
// x start / end of the next arc to draw
var xStart = xc + Math.cos(start) * r;
var xEnd = xc + Math.cos(start + partLength) * r;
// y start / end of the next arc to draw
var yStart = yc + Math.sin(start) * r;
var yEnd = yc + Math.sin(start + partLength) * r;
ctx.beginPath();
gradient = ctx.createLinearGradient(xStart, yStart, xEnd, yEnd);
gradient.addColorStop(0, endColor);
gradient.addColorStop(0.5, startColor);
gradient.addColorStop(1.0, endColor);
ctx.strokeStyle = gradient;
ctx.arc(xc, yc, r, start, start + partLength);
ctx.lineWidth = 30;
ctx.stroke();
ctx.closePath();
start += partLength;
startAngle = partLength;
}
}
var someColors = ["#fb4168", "#4efb7f", "#fbf672", "#63e0fb", "#5b5bfb", "#f079fb", "#e0dffb", "#1aa3fb", "#1aa3fb"];
drawMultiRadiantCircle(300, 300, 200, someColors);
// get location of value
function getCurLoc(value) {
for (var i = 0; i < comArray.length; i++) {
if (value == comArray[i]) {
return i;
break;
}
}
}
var startingPnt = 0;
var curangleDegrees = 0;
var prevangleDegrees = 0;
var prevLoc = 0;
var curLoc = 0;
var rotateNum = 0;
var diffLoc = 0;
//function for displaying data on chart
function displayData() {
var maxPnt = useArray.length - 1;
var currentValue = useArray[startingPnt];
var getLoc = getCurLoc(currentValue); // getting location of value in comArray
if (rotateNum == 0) {
curangleDegrees = angleArray[getLoc];
curLoc = getLoc;
} else {
curLoc = getLoc;
diffLoc = curLoc - prevLoc;
curangleDegrees = (diffLoc * 40);
}
$(".img").rotate(curangleDegrees, {
easing: 'easeInExpo'
});
console.log(getLoc);
if (startingPnt == maxPnt) {
startingPnt = 0;
} else {
startingPnt++;
}
prevLoc = curLoc;
prevangleDegrees = curangleDegrees;
rotateNum++;
}
setInterval(function() {
displayData();
}, 10000);
function adjust(color, amount) {
return '#' + color.replace(/^#/, '').replace(/../g, color => ('0' + Math.min(255, Math.max(0, parseInt(color, 16) + amount)).toString(16)).substr(-2));
}
});
.img {
margin: 48px 52px;
position: absolute;
z-index: 1;
}
#container {
width: 50%;
display: block;
margin: 0 auto;
position: relative;
}
#chart {
position: relative;
z-index: 20;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<div id="container">
<img class='img' src="images/arrowcircle.png" alt="" />
<canvas id="chart" width="800" height="800"></canvas>
</div>

how to add random circles with no overlap inside a fabricjs canvas

I am trying to come up with an algorithm of adding random circles with no overlap inside fabricj canvas;
The bellow snippet is what i have tried.
const canvas = new fabric.Canvas('gameCanvas', {selection: false});
fabric.Object.prototype.originX = fabric.Object.prototype.originY = 'center';
let rectangle;
document.addEventListener('DOMContentLoaded', function(){
rectangle = new fabric.Rect({
width: 250,
height: 100,
fill: 'blue',
opacity: 0.2,
left: 200,
top: 140
});
canvas.add(rectangle);
});
document.getElementById('addCirclesBtn').addEventListener('click', function(){
drawCircles();
});
function drawCircles()
{
for(let i = 1; i <=20; i++)
{
let x= Math.random() * (rectangle.width - rectangle.left) + rectangle.left;
let y = Math.random() * (rectangle.height - rectangle.top) + rectangle.top;
let circle = new fabric.Circle({
left: x,
top: y,
strokeWidth: 2,
radius: 10,
fill: 'white',
stroke: '#666',
selectable: false,
hoverCursor: 'default',
hasControls: false,
hasBorders: false
});
canvas.add(circle);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.0.0/fabric.min.js"></script>
<canvas id="gameCanvas" width="500" height="300" style="border: 2px solid green;"></canvas>
<button id="addCirclesBtn">ADD CIRCLES</button>
This is what i came up with and the main logic is in the function drawCircles() which checks is circles are overlapping before adding inside the rectangle. It is to be noted that the program add 100 circles inside the rectangle
const canvas = new fabric.Canvas('gameCanvas', {selection: false});
fabric.Object.prototype.originX = fabric.Object.prototype.originY = 'center';
let rectangle;
const radius = 10;
let min_x;
let min_y;
let max_x;
let max_y;
let circles;
document.addEventListener('DOMContentLoaded', function(){
drawRectangle();
min_x = rectangle.left - rectangle.width / 2 ;
//min_x = 250 - 200/2;
max_x = rectangle.left + rectangle.width / 2;
//max_x = 250 + 200/2;
min_y = rectangle.top - rectangle.height / 2 ;
//min_y = 150 - 100/2;
max_y = rectangle.top + rectangle.height / 2;
//max_y = 150 + 100/2;
});
function drawRectangle()
{
rectangle = new fabric.Rect({
width: 200,
height: 100,
fill: 'blue',
opacity: 0.2,
left: 250,
top: 150
});
canvas.add(rectangle);
}
document.getElementById('addCirclesBtn').addEventListener('click', function(){
canvas.clear();
circles = [];
drawRectangle();
drawCircles();
});
function drawCircles()
{
let anti_crash_count = 0
while(circles.length < 100)
{
let circle = new fabric.Circle({
left: getRandomNumber(min_x + radius, max_x - radius),
top: getRandomNumber(min_y + radius, max_y - radius),
strokeWidth: 2,
radius: radius,
fill: 'white',
stroke: '#666',
selectable: false,
hoverCursor: 'default',
hasControls: false,
hasBorders: false,
originX:'center',
originY:'center'
});
let isOverlapping = false;
for(let j = 0; j < circles.length; j++)
{
let previous_circle = circles[j];
let distance = Math.hypot(circle.left - previous_circle.left, circle.top - previous_circle.top);
if(distance < (circle.radius + previous_circle.radius))
{
isOverlapping = true;
break;
}
}
if(!isOverlapping)
{
canvas.add(circle);
circles.push(circle);
}
anti_crash_count ++;
if(anti_crash_count > 1000)
{
break;
}
}
}
function getRandomNumber(min, max)
{
return Math.floor((Math.random() * (max - min)) + min);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.0.0/fabric.min.js"></script>
<canvas id="gameCanvas" width="500" height="300" style="border: 2px solid green;"></canvas>
<button id="addCirclesBtn">ADD CIRCLES</button>
I would appreciate any idea on how to improve it if necessary.

Add google map in html using canvas tag

I have a simple drawing canvas that can allow a user to draw, edit, clear and save that drawing. whenever user refreshes his page that saved drawing will appear on the screen until and unless user deletes that image I have this code which is perfectly fine.
var canvas, ctx,
brush = {
x: 0,
y: 0,
color: '#000000',
size: 10,
down: false,
},
strokes = [],
currentStroke = null;
function redraw () {
ctx.clearRect(0, 0, canvas.width(), canvas.height());
ctx.lineCap = 'round';
for (var i = 0; i < strokes.length; i++) {
var s =strokes[i];
ctx.strokeStyle = s.color;
ctx.lineWidth = s.size;
ctx.beginPath();
ctx.moveTo(s.points[0].x, s.points[0].y);
for (var j = 0; j < s.points.length; j++){
var p = s.points[j];
ctx.lineTo(p.x, p.y);
}
ctx.stroke();
}
}
function init () {
canvas = $('#draw');
canvas.attr({
width: window.innerWidth,
height: window.innerHeight,
});
ctx = canvas[0].getContext('2d');
function mouseEvent (e){
brush.x = e.pageX;
brush.y = e.pageY;
currentStroke.points.push({
x: brush.x,
y: brush.y,
});
redraw();
}
canvas.mousedown(function (e){
brush.down = true;
currentStroke = {
color: brush.color,
size: brush.size,
points: [],
};
strokes.push(currentStroke);
mouseEvent(e);
}) .mouseup(function (e) {
brush.down = false;
mouseEvent(e);
currentStroke = null;
}) .mousemove(function (e) {
if (brush.down)
mouseEvent(e);
});
// check if localstorage has an array of strokes saved
if(localStorage.getItem('canvas_strokes')) {
strokes = JSON.parse(localStorage.getItem('canvas_strokes'));
redraw();
}
$('#save-to-local-storage').click(function () {
localStorage.setItem('canvas_strokes', JSON.stringify(strokes));
});
$('#save-btn').click(function () {
window.open(canvas[0].toDataURL());
});
$('#undo-btn').click(function (){
strokes.pop();
redraw();
});
$('#clear-btn').click(function (){
strokes = [];
redraw();
});
$('#color-picker').on('input', function () {
brush.color = this.value;
});
$('#brush-size').on('input', function () {
brush.size = this.value;
});
}
$(init);
<script src="https://code.jquery.com/jquery-3.2.1.js"></script>
<canvas id="draw"></canvas>
<button id="save-to-local-storage">
Save
</button>
<button id="clear-btn">
clear
</button>
<button id="undo-btn">
undo
</button>
I just want to add google Maps on canvas so that the user can draw on Google Maps rather than blank canvas. all other functionalities remain same.
here is the code
The easiest way for this to work is if you just make your canvas transparent and lay it over the top of a div element that contains a google static map. The map I'm using does not have markers on it, but you could make one that does. The map in this snippet won't show because you will need to add your API key.
Also, I disabled your localStorage because it gave me a CORS error in my console.
var canvas, ctx,
brush = {
x: 0,
y: 0,
color: '#000000',
size: 10,
down: false,
},
strokes = [],
currentStroke = null;
function redraw() {
ctx.clearRect(0, 0, canvas.width(), canvas.height());
ctx.lineCap = 'round';
for (var i = 0; i < strokes.length; i++) {
var s = strokes[i];
ctx.strokeStyle = s.color;
ctx.lineWidth = s.size;
ctx.beginPath();
ctx.moveTo(s.points[0].x, s.points[0].y);
for (var j = 0; j < s.points.length; j++) {
var p = s.points[j];
ctx.lineTo(p.x, p.y);
}
ctx.stroke();
}
}
function init() {
canvas = $('#draw');
canvas.attr({
width: window.innerWidth,
height: window.innerHeight,
});
ctx = canvas[0].getContext('2d');
function mouseEvent(e) {
brush.x = e.pageX;
brush.y = e.pageY;
currentStroke.points.push({
x: brush.x,
y: brush.y,
});
redraw();
}
canvas.mousedown(function(e) {
brush.down = true;
currentStroke = {
color: brush.color,
size: brush.size,
points: [],
};
strokes.push(currentStroke);
mouseEvent(e);
}).mouseup(function(e) {
brush.down = false;
mouseEvent(e);
currentStroke = null;
}).mousemove(function(e) {
if (brush.down)
mouseEvent(e);
});
// check if localstorage has an array of strokes saved
//if (localStorage.getItem('canvas_strokes')) {
//strokes = JSON.parse(localStorage.getItem('canvas_strokes'));
//redraw();
//}
$('#save-to-local-storage').click(function() {
localStorage.setItem('canvas_strokes', JSON.stringify(strokes));
});
$('#save-btn').click(function() {
window.open(canvas[0].toDataURL());
});
$('#undo-btn').click(function() {
strokes.pop();
redraw();
});
$('#clear-btn').click(function() {
strokes = [];
redraw();
});
$('#color-picker').on('input', function() {
brush.color = this.value;
});
$('#brush-size').on('input', function() {
brush.size = this.value;
});
}
$(init);
html,
body {
height: 100%;
}
#draw {
background-color: transparent;
position: absolute;
top: 0px;
height: 100%;
width: 100%;
z-index: 2;
}
#map {
background-image: url("https://maps.googleapis.com/maps/api/staticmap?center=Fargo,ND&zoom=12&size=400x400&key=YOUR_API_KEY");
position: absolute;
top: 0px;
height: 100%;
width: 100%;
z-index: 1;
}
<script src="https://code.jquery.com/jquery-3.2.1.js"></script>
<div style="position:relative;height:100%;width:100%">
<div id="map"></div>
<canvas id="draw"></canvas>
</div>
<button id="save-to-local-storage">
Save
</button>
<button id="clear-btn">
clear
</button>
<button id="undo-btn">
undo
</button>

Adding grid over Fabric.js canvas

I just started using Fabric.js (I have to say, I'm impressed).
I want to add a grid over the fabric objects. In the following code, I am putting my grid canvas right over on the Fabric canvas. The problem here is that, I now cannot move my fabric objects!
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<script type='text/javascript' src='http://code.jquery.com/jquery-1.7.1.js'></script>
<script type='text/javascript' src='http://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.2.0/fabric.all.min.js'></script>
</head>
<body>
<div style="height:480px;width:640px;border:1px solid #ccc;position:relative;font:16px/26px Georgia, Garamond, Serif;overflow:auto;">
<canvas id="rubber" width="800" height="800"
style="position: absolute; left: 0; top: 0; z-index: 0;"></canvas>
<canvas id="myCanvas" width="800" height="800"
style="position: absolute; left: 0; top: 0; z-index: 1;"></canvas>
</div>
<script>
//<![CDATA[
$(window).load(function(){
$(document).ready(function () {
function renderGrid(x_size,y_size, color)
{
var canvas = $("#myCanvas").get(0);
var context = canvas.getContext("2d");
context.save();
context.lineWidth = 0.5;
context.strokeStyle = color;
// horizontal grid lines
for(var i = 0; i <= canvas.height; i = i + x_size)
{
context.beginPath();
context.moveTo(0, i);
context.lineTo(canvas.width, i);
context.closePath();
context.stroke();
}
// vertical grid lines
for(var j = 0; j <= canvas.width; j = j + y_size)
{
context.beginPath();
context.moveTo(j, 0);
context.lineTo(j, canvas.height);
context.closePath();
context.stroke();
}
context.restore();
}
renderGrid(10,15, "gray");
});
});//]]>
var canvas = new fabric.Canvas('rubber');
canvas.add(new fabric.Circle({ radius: 30, fill: '#f55', top: 100, left: 100 }));
canvas.selectionColor = 'rgba(0,255,0,0.3)';
canvas.selectionBorderColor = 'red';
canvas.selectionLineWidth = 5;
</script>
</body>
</html>
I am hoping that there is a way to do this in Fabric itself.
Any help would be awesome, thanks!
This two lines of code will work:
var gridsize = 5;
for(var x=1;x<(canvas.width/gridsize);x++)
{
canvas.add(new fabric.Line([100*x, 0, 100*x, 600],{ stroke: "#000000", strokeWidth: 1, selectable:false, strokeDashArray: [5, 5]}));
canvas.add(new fabric.Line([0, 100*x, 600, 100*x],{ stroke: "#000000", strokeWidth: 1, selectable:false, strokeDashArray: [5, 5]}));
}
A shorter version and more generic for copy/paste :
var oCanvas; // must be your canvas object
var gridWidth; // <= you must define this with final grid width
var gridHeight; // <= you must define this with final grid height
// to manipulate grid after creation
var oGridGroup = new fabric.Group([], {left: 0, top: 0});
var gridSize = 20; // define grid size
// define presentation option of grid
var lineOption = {stroke: 'rgba(0,0,0,.4)', strokeWidth: 1, selectable:false, strokeDashArray: [3, 3]};
// do in two steps to limit the calculations
// first loop for vertical line
for(var i = Math.ceil(gridWidth/gridSize); i--;){
oGridGroup.add( new fabric.Line([gridSize*i, 0, gridSize*i, gridHeight], lineOption) );
}
// second loop for horizontal line
for(var i = Math.ceil(gridHeight/gridSize); i--;){
oGridGroup.add( new fabric.Line([0, gridSize*i, gridWidth, gridSize*i], lineOption) );
}
// Group add to canvas
oCanvas.add(oGridGroup);
I hope this will help you----
function draw_grid(grid_size) {
grid_size || (grid_size = 25);
currentCanvasWidth = canvas.getWidth();
currentcanvasHeight = canvas.getHeight();
// Drawing vertical lines
var x;
for (x = 0; x <= currentCanvasWidth; x += grid_size) {
this.grid_context.moveTo(x + 0.5, 0);
this.grid_context.lineTo(x + 0.5, currentCanvasHeight);
}
// Drawing horizontal lines
var y;
for (y = 0; y <= currentCanvasHeight; y += grid_size) {
this.grid_context.moveTo(0, y + 0.5);
this.grid_context.lineTo(currentCanvasWidth, y + 0.5);
}
grid_size = grid_size;
this.grid_context.strokeStyle = "black";
this.grid_context.stroke();
}
My solution is -
var width = canvas.width;
var height = canvas.height;
var j = 0;
var line = null;
var rect = [];
var size = 20;
console.log(width + ":" + height);
for (var i = 0; i < Math.ceil(width / 20); ++i) {
rect[0] = i * size;
rect[1] = 0;
rect[2] = i * size;
rect[3] = height;
line = null;
line = new fabric.Line(rect, {
stroke: '#999',
opacity: 0.5,
});
line.selectable = false;
canvas.add(line);
line.sendToBack();
}
for (i = 0; i < Math.ceil(height / 20); ++i) {
rect[0] = 0;
rect[1] = i * size;
rect[2] = width;
rect[3] = i * size;
line = null;
line = new fabric.Line(rect, {
stroke: '#999',
opacity: 0.5,
});
line.selectable = false;
canvas.add(line);
line.sendToBack();
}
canvas.renderAll();
You have to save all line objects for removing grid, or you can added all line objects to a group, and you can remove the group for removing grid, well I think this is not elegant one, but worked.
If you don't insist on generating your grid dynamically you might want to consider the native overlay image function that fabric.js provides.
var canvas = new fabric.Canvas('rubber');
canvas.setOverlayImage('grid.png', canvas.renderAll.bind(canvas));
It won't hinder interactions with the objects on the canvas at all.
I really liked #Draeli's answer, but it seemed it wasn't working with the latest fabric version. Fixed the old one and added one slight adjustment that was necessary for myself - centring the grid.
Anyhow, maybe someone else finds it useful:
const gridSize = 100;
const width = this.canvas.getWidth();
const height = this.canvas.getHeight();
const left = (width % gridSize) / 2;
const top = (height % gridSize) / 2;
const lines = [];
const lineOption = {stroke: 'rgba(0,0,0,1)', strokeWidth: 1, selectable: false};
for (let i = Math.ceil(width / gridSize); i--;) {
lines.push(new fabric.Line([gridSize * i, -top, gridSize * i, height], lineOption));
}
for (let i = Math.ceil(height / gridSize); i--;) {
lines.push(new fabric.Line([-left, gridSize * i, width, gridSize * i], lineOption));
}
const oGridGroup = new fabric.Group(lines, {left: 0, top: 0});
this.canvas.add(oGridGroup);
this.canvas - this supposedly is the fabric instance.
Here is my solution, works with Fabric Js 4.
at the end there is 2 events listeners that append the grid when object moving and remove the grid when object move end. (improvement of #draeli answer https://stackoverflow.com/a/35936606/1727357 )
(function () {
const gcd = (a, b) => {
if (!b) {
return a;
}
return gcd(b, a % b);
}
const gridWidth = canvas.getWidth();
const gridHeight = canvas.getHeight();
const oGridGroup = [];
console.log(gcd(gridWidth, gridHeight));
const gridRows = gcd(gridWidth, gridHeight);
const gridCols = gcd(gridWidth, gridHeight);
const lineOption = { stroke: 'rgba(0,0,0,.1)', strokeWidth: 1, selectable: false, evented: false };
for (let i = 0; i <= gridWidth; i += gridCols) {
oGridGroup.push(new fabric.Line([i, 0, i, gridHeight], lineOption));
}
for (let i = 0; i <= gridHeight; i += gridRows) {
oGridGroup.push(new fabric.Line([0, i, gridWidth, i], lineOption));
}
const theGorup = new fabric.Group(oGridGroup);
theGorup.set({
selectable: false,
evented: false
})
canvas.on('mouse:down', function (event) {
if (event.target) {
canvas.add(theGorup);
}
});
canvas.on('mouse:up', function (event) {
canvas.remove(theGorup);
});
}())
Updated:
Answer is based on the code posted by rafi:
I have updated the missing grid_context.
Kindly replace "<your canvas Id>" with your canvas Id in string. For e.g. "myCanvas".
Usually, any operation you do on the canvas will clear out grid on the canvas, register the after:render event on fabric.js canvas to redraw it. So you can always see it.
var canvas = new fabric.Canvas(<your canvas Id>);
canvas.on('after:render',function(ctx){
draw_grid(25);
});
function draw_grid(grid_size) {
grid_size || (grid_size = 25);
var currentCanvas = document.getElementById(<your canvas Id>);
var grid_context = currentCanvas.getContext("2d");
var currentCanvasWidth = canvas.getWidth();
var currentCanvasHeight = canvas.getHeight();
// Drawing vertical lines
var x;
for (x = 0; x <= currentCanvasWidth; x += grid_size) {
grid_context.moveTo(x + 0.5, 0);
grid_context.lineTo(x + 0.5, currentCanvasHeight);
}
// Drawing horizontal lines
var y;
for (y = 0; y <= currentCanvasHeight; y += grid_size) {
grid_context.moveTo(0, y + 0.5);
grid_context.lineTo(currentCanvasWidth, y + 0.5);
}
grid_context.strokeStyle = "#0000002b";
grid_context.stroke();
}

Categories

Resources