I'm using Fabric.js to create a group of two rectangles that are lying next to each other. I want the left one to change it's color when I'm moving my mouse over it. Therefore I check if the position of the mouse-cursor is within the the boundary of the rectangle or not.
This works fine until I scale the group...
I made a few tests and found out that the properties of the group members don't change. So while the rectangles become larger they still show their old sizes.
This is my code:
farbicCanvas = new fabric.Canvas('c');
farbicCanvas.on('object:scaling', function(e)
{
//Show the sizes
console.log("group width: " + e.target.getWidth());
console.log("rect1 width: " + e.target.item(0).getWidth());
console.log("rect2 width: " + e.target.item(1).getWidth());
});
farbicCanvas.on('mouse:move', function(e)
{
if(e.target !== null)
{
point = new getMouseCoords(farbicCanvas, event);
//Check if cursor is within the boundary of rect2
if(point.posX >= e.target.item(1).getLeft() + e.target.getLeft() + e.target.getWidth()/2
&& point.posX <= e.target.item(1).getLeft() + e.target.getLeft() + e.target.getWidth()/2 + e.target.item(1).getWidth()
&& point.posY >= e.target.getTop()
&& point.posY <= e.target.getTop() + e.target.item(1).getHeight())
{
e.target.item(1).set({ fill: 'rgb(0,0,255)' });
farbicCanvas.renderAll();
}
else
{
farbicCanvas.getObjects()[0].item(1).set({ fill: 'rgb(0,255,0)' });
farbicCanvas.renderAll();
}
}
else
{
farbicCanvas.getObjects()[0].item(1).set({ fill: 'rgb(0,255,0)' });
farbicCanvas.renderAll();
}
});
var rect1 = new fabric.Rect(
{
left: 100,
top: 100,
width: 100,
height: 75,
fill: 'rgb(255,0,0)',
opacity: 0.5
});
var rect2 = new fabric.Rect(
{
left: rect1.getLeft() + rect1.getWidth(),
top: rect1.getTop(),
width: 100,
height: 75,
fill: 'rgb(0,255,0)',
opacity: 0.5
});
group = new fabric.Group([rect1, rect2]);
farbicCanvas.add(group);
function getMouseCoords(canvas, event)
{
var pointer = canvas.getPointer(event.e);
this.posX = pointer.x;
this.posY = pointer.y;
}
I've also made a Fiddle: https://jsfiddle.net/werschon/0uduqfpe/3/
If I make the group larger, my 'mouse-detection' doesn't work anymore, because the properties like left, top or width are not updating.
What do I need to do, to update the properties? Or is there another way of detecting if the mouse-cursor is above the rectangle?
Thanks.
I found a solution (Thanks to John Morgan): I have to calculate the properties of the group members by myself. With getScaleX() and getScaleY() I can get the scale-factors of the group. Then I need to multiply the properties of the group members with the appropriate scale-factor. As result I get the new values of the group member. In my example I only needed getScaleX() to calculate the width of rect2 so it looks like this: e.target.item(1).getWidth() * e.target.getScaleX().
This is the new code:
farbicCanvas = new fabric.Canvas('c');
farbicCanvas.on('object:scaling', function(e)
{
console.log("group width: " + e.target.getWidth());
console.log("rect1 width: " + e.target.item(0).getWidth() * e.target.getScaleX());
console.log("rect2 width: " + e.target.item(1).getWidth() * e.target.getScaleX());
});
farbicCanvas.on('mouse:move', function(e)
{
if(e.target !== null)
{
point = new getMouseCoords(farbicCanvas, event);
if(point.posX >= e.target.getLeft() + e.target.getWidth() - e.target.item(1).getWidth() * e.target.getScaleX()
&& point.posX <= e.target.getLeft() + e.target.getWidth()
&& point.posY >= e.target.getTop()
&& point.posY <= e.target.getTop() + e.target.getHeight())
{
e.target.item(1).set({ fill: 'rgb(0,0,255)' });
farbicCanvas.renderAll();
}
else
{
e.target.item(1).set({ fill: 'rgb(0,255,0)' });
farbicCanvas.renderAll();
}
}
else
{
farbicCanvas.getObjects()[0].item(1).set({ fill: 'rgb(0,255,0)' });
farbicCanvas.renderAll();
}
});
farbicCanvas.on('mouse:out', function(e)
{
farbicCanvas.getObjects()[0].item(1).set({ fill: 'rgb(0,255,0)' });
farbicCanvas.renderAll();
});
var rect1 = new fabric.Rect(
{
left: 100,
top: 100,
width: 100,
height: 75,
fill: 'rgb(255,0,0)',
opacity: 0.5
});
var rect2 = new fabric.Rect(
{
left: rect1.getLeft() + rect1.getWidth(),
top: rect1.getTop(),
width: 100,
height: 75,
fill: 'rgb(0,255,0)',
opacity: 0.5
});
group = new fabric.Group([rect1, rect2]);
farbicCanvas.add(group);
function getMouseCoords(canvas, event)
{
var pointer = canvas.getPointer(event.e);
this.posX = pointer.x;
this.posY = pointer.y;
}
I've also created a new Fiddle. So now my mouse-detection works even after scaling
Related
I'm trying to check for a collision between 2 text objects and using the intersectsWithObject. It's working but it's taking the bouding rect into account. Is it possible to check on pixel level?
Current behaviour:
Wanted behaviour:
const canvas = new fabric.Canvas('canvas');
canvas.setWidth(document.body.clientWidth);
canvas.setHeight(document.body.clientHeight);
const text = new fabric.Textbox('some text', {
width: 300,
fontSize: 70,
top: 120,
left: 100
});
const text2 = new fabric.Textbox('some more text', {
width: 350,
fontSize: 50,
top: 200,
left: 20,
})
if (text.intersectsWithObject(text2, true, true)) {
text.set('fill', 'red');
}
else {
text.set('fill', 'black');
}
canvas.on('after:render', function() {
canvas.contextContainer.strokeStyle = '#555';
canvas.forEachObject(function(obj) {
var bound = obj.getBoundingRect();
canvas.contextContainer.strokeRect(
bound.left + 0.5,
bound.top + 0.5,
bound.width,
bound.height
);
})
});
canvas.add(text);
canvas.add(text2);
https://jsbin.com/menadejato/edit?js,console,output
I'm using fabricjs to make a ruler tool. When the user click on the canvas it will draw a small rectangle and a line (with length of 0) as the user starts to move the mouse the length of line increases. At the top of the line There's a text which will show the length of the line. The length of the line is updating But it won't display the updated length. It displays the updated length once I click again to finish drawing.
Here's my code
let oneSideDrawn = false;
canvas.on("mouse:down", (event) => {
function rulerDown() {
const mouse = canvas.getPointer(event.e);
const { x, y } = mouse;
const side = new fabric.Rect({
width: 3,
height: 16,
left: x,
top: y,
fill: "#222",
});
canvas.add(side);
updateCanvas();
if (oneSideDrawn) {
oneSideDrawn = false;
const objs = canvas.getObjects();
const length = objs.length;
const line = objs[length - 3];
const side2 = objs[length - 4];
side.set({ top: side2.top });
const txt = canvas.getById("myId");
const rulerGroup = new fabric.Group([side2, line, side, txt], {
basicStyle,
lockScalingX: true,
lockScalingY: true,
});
canvas.add(rulerGroup);
canvas.remove(side2);
canvas.remove(line);
canvas.remove(side);
canvas.remove(txt);
canvas.setActiveObject(rulerGroup);
updateCanvas();
updateMode("");
} else {
const line = new fabric.Rect({
width: 0,
height: 3,
left: x,
top: y + 6,
fill: "#222",
});
const txt = new fabric.Text("0 m", {
fontSize: 10,
left: line.left + line.width / 2,
top: side.top - 5,
id: "myId",
});
txt.set({ left: txt.left - txt.width / 2 });
canvas.add(line);
canvas.add(txt);
updateCanvas();
canvas.setActiveObject(line);
oneSideDrawn = true;
}
}
}
canvas.on("mouse:move", (event) => {
function rulerMove() {
const line = canvas.getActiveObject();
const lineTxt = canvas.getById("myId");
const mouse = canvas.getPointer(event.e);
const { x } = mouse;
const width = Math.abs(x - line.left);
if (!width) {
return false;
}
line.set({ width });
lineTxt.set({
text: parseFloat(width).toFixed(2) + " m",
left: line.left + width / 2 - 15,
});
updateCanvas();
}
}
Here's a screen shot
As we can see the text is updating in the console.log But the value is still "0 m" on the canvas
Update fabricjs!
I was facing this issue when I was using fabricjs 4.3.0.
In fabricjs 4.4.0 they solved this issue
I'm making a 3D box with Zdog and I want to let the height of the box grow.
Here is a codepen: https://codepen.io/anon/pen/qzBMgp.
This is my code for the box:
let progressBox = new Zdog.Box({
addTo: progress,
width: 200,
height: boxHeight,
depth: 200,
stroke: 1,
})
This is the code I use to increase the height of the box. If the box is shorter than 400, the box will increase its height with 0.1.
function animate() {
if (boxHeight < 400) {
moveUp = 'up';
} else if (boxHeight > 400) {
moveUp = 'false';
}
boxHeight += moveUp == 'up' ? 0.1 : 0;
}
The problem is that the box stays at a height of 0 (the value I gave to boxHeight), but when I console.log(boxHeight) the boxHeight will grow.
First of all, let me point out that you cannot change the value of a constant . On your code, the boxHeight is declared as const.
Second, you will need to use Zdog's copy() method. Here is your code modified accordingly.
Zdog.Anchor.prototype.renderGraphSvg = function (svg) {
if (!svg) {
throw new Error('svg is ' + svg + '. ' +
'SVG required for render. Check .renderGraphSvg( svg ).');
}
this.flatGraph.forEach(function (item) {
item.render(svg, Zdog.SvgRenderer);
});
};
const TAU = Zdog.TAU;
const light = '#EAE2B7';
const yellow1 = '#FCBF49';
const orange1 = '#F77F00';
const red1 = '#d62828';
const purple1 = '#003049';
const white1 = '#ffffff';
const isSpinning = true;
var boxHeight = 0;
let progress = new Zdog.Illustration({
element: '.progress',
dragRotate: true,
translate: {
y: 25
},
rotate: {
x: -0.4, y: 0.75
}
});
let progressBox = new Zdog.Box({
addTo: progress,
width: 200,
depth: 200,
height: boxHeight,
stroke: 1,
color: purple1, // default face color
leftFace: yellow1,
rightFace: orange1,
topFace: red1,
bottomFace: light,
translate: {
x: 0,
y: 300
},
});
function animate() {
if (boxHeight <= 400) {
boxHeight++; // 1
progressBox = progressBox.copy({
height: boxHeight, // overwrite height
translate: {
y: progressBox.translate.y - 1 // overwrite vertical position to put box in place while height is growing.
}
});
}
progress.updateRenderGraph();
requestAnimationFrame(animate);
}
animate();
I forked your pen and updated it with the code above. See Zdog - progress box
Note that it seems to be expensive doing the copy() method on every animation frame. I am also new to this library and this is currently the fix I know of.
JSFiddle: https://jsfiddle.net/bd7kc24a/20/
A coworker and I have hit a snag with our attempt to use canvas and we are looking to have a Rect on the canvas to be the actual work area. We are trying to add a grid and/or dot background to that Rect, but we need that section to not scale when zooming occurs. If there is a simple way to do this, we'd love to know about it, but this is where we are currently.
The example jsfiddle above kinda does what we need it to do. But we are having issues:
Persisting that to the database as well as the redux store.
Making sure the grid stays the same pixel distance instead of zooming with objects
So I'm trying to create a custom subclass to re-apply that static canvas background when serialization occurs, however once I've added this base class it will not longer do the loadFromJSON call or hit the callback.
Any help would be appreciated! Especially if there is a simple way to do this that we simply missed in the documentation.
JSFiddle: https://jsfiddle.net/bd7kc24a/20/
'use strict';
debugger;
fabric.CanvasMap = fabric.util.createClass(fabric.Rect, {
type: 'canvasMap',
initialize: function(element, options) {
this.callSuper('initialize', element, options);
options && this.set('name', options.name);
options && this.set('imageRef', options.imageRef);
this.rescaleBackground(10);
},
rescaleBackground: function(scale) {
const padding = 0;
this.imageRef.scaleToWidth(scale);
var patternSourceCanvas = new fabric.StaticCanvas();
patternSourceCanvas.add(this.imageRef);
patternSourceCanvas.setDimensions({
width: this.imageRef.getWidth() + padding,
height: this.imageRef.getHeight() + padding
});
let pattern = new fabric.Pattern({
source: patternSourceCanvas.getElement(),
repeat: 'repeat'
});
this.setPatternFill(pattern);
},
toObject: function() {
return fabric.util.object.extend(this.callSuper('toObject'), {
name: this.name
});
}
});
fabric.CanvasMap.fromObject = function (object, callback) {
var _enlivenedObjects;
object.imageRef = squareImg;
fabric.util.enlivenObjects(object.objects, function (enlivenedObjects) {
delete object.objects;
_enlivenedObjects = enlivenedObjects;
});
const newCanvasMap = new fabric.CanvasMap(_enlivenedObjects, object);
return newCanvasMap;
};
let floorPlan = new fabric.Canvas('floorPlan', {
hoverCursor: 'pointer',
backgroundColor: '#cccccc'
});
function reloadFromJSON() {
let jsonMap = floorPlan.toJSON();
let jsonMapString = JSON.stringify(jsonMap);
floorPlan.loadFromJSON(jsonMapString, function() {
floorPlan.renderAll();
});
}
//let mapBackground = new fabric.Canvas();
function addShape() {
let coordinates = floorPlan.getVpCenter();
const circle = new fabric.Circle({
name: 'some circle',
radius: 20,
fill: 'blue',
left: coordinates.x - 20,
top: coordinates.y - 20
});
floorPlan.add(circle);
}
let defaultScale = 10;
let squareSVG = '<svg width="10" height="10" xmlns="http://www.w3.org/2000/svg"><g> <title>background</title><rect fill="#fff" id="canvas_background" height="12" width="12" y="-1" x="-1"/></g><g> <title>Layer 1</title> <rect id="svg_1" height="9.87496" width="9.99996" y="0.062519" x="0.031269" stroke-width="1.5" stroke="#000000" fill="none"/> </g></svg>'
let squareImg;
fabric.loadSVGFromString(squareSVG, function(objects, options) {
squareImg = fabric.util.groupSVGElements(objects, options);
const map = new fabric.CanvasMap({
imageRef: squareImg,
height: 300, width: 300,
left: 20, top: 20,
strokeWidth: 2,
stroke: 'red',
selectable: false,
evented: false, hasBorders: true, hasControls: false
});
floorPlan.add(map);
floorPlan.renderAll();
});
function setBackground(scale) {
floorPlan._objects[0].rescaleBackground(scale);
}
//logic for binding objects to MAP
floorPlan.observe('object:moving', function(e) {
let obj = e.target;
if (obj.getHeight() > map.height || obj.getWidth() > map.width) {
obj.setScaleY(obj.originalState.scaleY);
obj.setScaleX(obj.originalState.scaleX);
}
obj.setCoords();
if (obj.top - (obj.cornerSize / 2) < map.top ||
obj.left - (obj.cornerSize / 2) < map.left) {
console.log(map.top, map.left);
obj.top = Math.max(obj.top, obj.top - obj.top + (obj.cornerSize / 2) + map.top);
obj.left = Math.max(obj.left, obj.left - obj.left + (obj.cornerSize / 2) + map.left);
}
if (obj.top + obj.height + obj.cornerSize > map.height ||
obj.left + obj.width + obj.cornerSize > map.width) {
obj.top = Math.min(obj.top, (map.height + map.top) - obj.height + obj.top - obj.top - obj.cornerSize / 2);
obj.left = Math.min(obj.left, (map.width + map.left) - obj.width + obj.left - obj.left - obj.cornerSize / 2);
}
});
//zoom
let clicks = 0;
floorPlan.on('mouse:down', function(event) {
clicks += 1;
setTimeout(function() {
if (clicks > 1) {
console.log(event.e.clientX);
floorPlan.zoomToPoint(new fabric.Point(event.e.clientX, event.e.clientY), floorPlan.getZoom() + 1);
}
clicks = 0;
}, 300);
});
function zoomIn() {
floorPlan.setZoom(floorPlan.getZoom() + 0.5);
console.log(floorPlan.getZoom(), defaultScale * floorPlan.getZoom());
setBackground(defaultScale / floorPlan.getZoom());
floorPlan.renderAll();
}
function zoomOut() {
floorPlan.setZoom(floorPlan.getZoom() - 0.5); ;
console.log(floorPlan.getZoom(), defaultScale * floorPlan.getZoom());
setBackground(defaultScale / floorPlan.getZoom());
floorPlan.renderAll();
}
function renderAll() {
floorPlan.renderAll();
}
//pan
function pan(event) {
event.e.preventDefault();
let panAmount = {
x: event.e.deltaX,
y: event.e.deltaY
};
//logic for binding the map to the screen during pan
/*
* We need to use the following points and check for negative or overflow values
* canvas.oCoords.tl - top left
* canvas.oCoords.bl - bottom left
* canvas.oCoords.tr - top right
* canvas.oCoords.br - bottom right
*/
//pan left
if (panAmount.x > 0) {
if (map.oCoords.tr.x < floorPlan.width - 40) {
panAmount.x = 0;
}
}
//pan right
if (panAmount.x < 0) {
if (map.oCoords.tl.x > 40) {
panAmount.x = 0;
}
}
//pan up
if (panAmount.y > 0) {
if (map.oCoords.bl.y < floorPlan.height - 40) {
panAmount.y = 0;
}
}
//pan down
if (panAmount.y < 0) {
if (map.oCoords.tl.y > 40) {
panAmount.y = 0;
}
}
// assuming the logic above allows the scroll, get the relative scroll values and create a new fabric point and then scroll to it
const delta = new fabric.Point(-panAmount.x, -panAmount.y);
floorPlan.relativePan(delta);
}
floorPlan.on('mouse:wheel', pan);
Say I have a HTML5 canvas (In this case using fabric.js), and I want to change the cursor over the canvas to represent whatever brush size and colour has been selected. I'm thinking there should be a way to do this by changing an SVG's properties (size & colour) dynamically with JS so we don't have to use multiple images. Any ideas on if this is possible?
var canvas = new fabric.Canvas(c, {
isDrawingMode: true,
freeDrawingCursor: 'url("img/cursor.svg"), auto'
});
I think freeDrawingCursor is just looking for normal css property names. Here is an example of how to have a fabric object represent the cursor size and color:
var canvas = new fabric.Canvas('c', {
isDrawingMode: true,
freeDrawingCursor: 'none'
});
canvas.freeDrawingBrush.width = 10;
canvas.freeDrawingBrush.color = '#9f9';
var mousecursor = new fabric.Circle({
left: 0,
top: 0,
radius: canvas.freeDrawingBrush.width / 2,
fill: canvas.freeDrawingBrush.color,
originX: 'right',
originY: 'bottom',
})
canvas.add(mousecursor);
canvas.on('mouse:move', function(obj) {
mousecursor.top = obj.e.y - mousecursor.radius;
mousecursor.left = obj.e.x - mousecursor.radius;
canvas.renderAll()
})
canvas.on('mouse:out', function(obj) {
// put circle off screen
mousecursor.top = -100;
mousecursor.left = -100;
canvas.renderAll()
})
canvas {
border: 1px solid #ccc;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.3/fabric.js"></script>
<canvas id="c" width="600" height="600"></canvas>
Existing answers didn't work for me in 2020. Try the following:
this.canvas.isDrawingMode = true;
this.canvas.freeDrawingCursor = 'none';
const mousecursor = new fabric.Circle({
left: 0,
top: 0,
radius: this.canvas.freeDrawingBrush.width / 2,
fill: this.canvas.freeDrawingBrush.color,
originX: 'right',
originY: 'bottom',
});
this.canvas.add(mousecursor);
// Cursor in canvas
this.canvas.on('mouse:move', event => {
mousecursor.top = event.e.layerY + mousecursor.radius;
mousecursor.left = event.e.layerX + mousecursor.radius;
this.canvas.renderAll();
});
// Cursor out of canvas
this.canvas.on('mouse:out', event => {
mousecursor.top = -100;
mousecursor.left = -100;
this.canvas.renderAll();
});
Worked fine with Angular 7.
I was looking for the same and found my way to this question. Unfortunately the solution by STHayden is not working for me. So I've modified it a bit and came up with the code below. It uses two canvas layers, the bottom for drawing, the top for the "cursor". Works pretty well for me now, maybe someone will find it helpful :)
//drawing layer
var canvas = new fabric.Canvas("draw", {
isDrawingMode: true,
freeDrawingCursor: 'none'
});
//mouse cursor layer
var cursor = new fabric.StaticCanvas("cursor");
canvas.freeDrawingBrush.width = 20;
canvas.freeDrawingBrush.color = '#ff0000';
var cursorOpacity = .5;
//create cursor and place it off screen
var mousecursor = new fabric.Circle({
left: -100,
top: -100,
radius: canvas.freeDrawingBrush.width / 2,
fill: "rgba(255,0,0," + cursorOpacity + ")",
stroke: "black",
originX: 'center',
originY: 'center'
});
cursor.add(mousecursor);
//redraw cursor on new mouse position when moved
canvas.on('mouse:move', function (evt) {
var mouse = this.getPointer(evt.e);
mousecursor
.set({
top: mouse.y,
left: mouse.x
})
.setCoords()
.canvas.renderAll();
});
//put cursor off screen again when mouse is leaving
canvas.on('mouse:out', function () {
mousecursor
.set({
top: mousecursor.originalState.top,
left: mousecursor.originalState.left
})
.setCoords()
.canvas.renderAll();
});
//while brush size is changed show cursor in center of canvas
document.getElementById("size").oninput = function () {
var size = parseInt(this.value, 10);
mousecursor
.center()
.set({
radius: size/2
})
.setCoords()
.canvas.renderAll();
};
//after brush size has been changed move offscreen, update brush size
document.getElementById("size").onchange = function () {
var size = parseInt(this.value, 10);
canvas.freeDrawingBrush.width = size;
mousecursor
.set({
left: mousecursor.originalState.left,
top: mousecursor.originalState.top,
radius: size/2
})
.setCoords()
.canvas.renderAll();
};
//change mousecursor opacity
document.getElementById("opacity").onchange = function () {
cursorOpacity = this.value;
var fill = mousecursor.fill.split(",");
fill[fill.length-1] = cursorOpacity + ")";
mousecursor.fill = fill.join(",");
};
//change drawing color
document.getElementById("color").onchange = function () {
canvas.freeDrawingBrush.color = this.value;
var bigint = parseInt(this.value.replace("#", ""), 16);
var r = (bigint >> 16) & 255;
var g = (bigint >> 8) & 255;
var b = bigint & 255;
mousecursor.fill = "rgba(" + [r,g,b,cursorOpacity].join(",") + ")";
};
#cont {
position: relative;
width: 500px;
height: 500px;
}
canvas {
border: 1px solid;
}
#cont canvas, .canvas-container {
position: absolute!important;
left: 0!important;
top: 0!important;
width: 100%!important;
height: 100%!important;
}
#cursor {
pointer-events: none!important;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.4/fabric.min.js"></script>
Color: <input id="color" type="color" value="#ff0000"><br/>
Brush size: <input id="size" type="range" min="1" max="100" step="1" value="20"><br/>
Brush opacity: <input id="opacity" type="number" min="0" max="1" step="0.1" value="0.5"><br/>
<div id="cont">
<canvas id="draw" width="500" height="500"></canvas>
<canvas id="cursor" width="500" height="500"></canvas>
</div>