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
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 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.
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
I want to make and dynamic crop area and found this snippet. It works perfect in normal usage, but when you scaled the original object before making the crop area, the crop zone seems not in the right position. Can you look into this pen for some help ?
var canvas = new fabric.CanvasEx('canvas');
var el;
var object, lastActive, object1, object2;
var cntObj = 0;
var selection_object_left = 0;
var selection_object_top = 0;
var src = "http://fabricjs.com/lib/pug.jpg";
fabric.Image.fromURL('https://omicron.aeon.co/images/08e7f2bb-f2ce-4058-a955-1c8d594468a2/card_SIZED-Aleksandr-Zykov-4975950437_b84f9f9ef8_o.jpg', function (oImg) {
oImg.top = canvas.getHeight()/2 - oImg.getHeight()/2;
oImg.left = canvas.getWidth()/2 - oImg.getWidth()/2;
canvas.add(oImg);
bindCropEvent(oImg);
});
canvas.renderAll();
function bindCropEvent(obj){
obj.on('object:dblclick', function(){
CropMode();
});
};
function CropMode() {
canvas.remove(el);
if (canvas.getActiveObject()) {
object = canvas.getActiveObject();
if (lastActive !== object) {
console.log('different object');
} else {
console.log('same object');
}
if (lastActive && lastActive !== object) {
//lastActive.clipTo = null; results in clip loss
}
el = new fabric.Rect({
fill: 'rgba(0,0,0,0.6)',
originX: 'left',
originY: 'top',
stroke: '#ccc',
strokeDashArray: [2, 2],
opacity: 1,
width: 1,
height: 1,
borderColor: 'red',
cornerColor: 'red',
hasRotatingPoint: false
});
el.left = canvas.getActiveObject().left;
selection_object_left = canvas.getActiveObject().left;
selection_object_top = canvas.getActiveObject().top;
el.top = canvas.getActiveObject().top;
el.width = canvas.getActiveObject().width * canvas.getActiveObject().scaleX;
el.height = canvas.getActiveObject().height * canvas.getActiveObject().scaleY;
//插入
canvas.add(el);
canvas.setActiveObject(el);
el.on('deselected', function(){
console.log('des');
doCrop();
});
} else {
alert("Please select an object or layer");
}
}
function doCrop() {
var eLeft = el.get('left');
var eTop = el.get('top');
var left = eLeft - object.left;
var top = eTop - object.top;
console.log(left, top);
left *= 1;
top *= 1;
console.log(left, top);
var eWidth = el.get('width');
var eHeight = el.get('height');
var eScaleX = el.get('scaleX');
var eScaleY = el.get('scaleY');
var width = eWidth * 1;
var height = eHeight * 1;
object.clipTo = function (ctx) {
ctx.rect(-(eWidth / 2) + left, -(eHeight / 2) + top, parseInt(width * eScaleX), parseInt( height * eScaleY));
}
canvas.remove(el);
lastActive = object;
canvas.renderAll();
}
Thanks !
when you create a rect, you can create new image with toDataURL(). What will be cropped image.
cropOptions = {
left: Math.floor(rect.left),
top: Math.floor(rect.top),
width: Math.floor(rect.width),
height: Math.floor(rect.height)
},
cropDataUrl ;
cropDataUrl = image.toDataURL(cropOptions);
new fabric.Image.fromURL(cropDataUrl, function(img) {
canvas.remove(image,rect).add(img); //this is your cropped image
})
I need help only having the anchors for rotating. Right now there is five anchors and I don't know how to get rid of all of them except the rotate one. I would also only like the anchors to show when the user hovers over the image
Here is my code
<html>
<head>
<style>
body {
margin: 0px;
padding: 0px;
}
</style>
</head>
<body>
<body onmousedown="return false;">
<div id="container"></div>
<script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v4.7.4.min.js">
</script>
<script>
function update(activeAnchor) {
var group = activeAnchor.getParent();
var topLeft = group.get('.topLeft')[0];
var topRight = group.get('.topRight')[0];
var bottomRight = group.get('.bottomRight')[0];
var bottomLeft = group.get('.bottomLeft')[0];
var rotateAnchor = group.get('.rotateAnchor')[0];
var image = group.get('Image')[0];
var anchorX = activeAnchor.getX();
var anchorY = activeAnchor.getY();
var imageWidth = image.getWidth();
var imageHeight = image.getHeight();
var offsetX = Math.abs((topLeft.getX() + bottomRight.getX() + 10) / 2);
var offsetY = Math.abs((topLeft.getY() + bottomRight.getY() + 10) / 2);
// update anchor positions
switch (activeAnchor.getName()) {
case 'rotateAnchor':
group.setOffset(offsetX, offsetY);
break;
case 'topLeft':
topRight.setY(anchorY);
bottomLeft.setX(anchorX);
break;
case 'topRight':
topLeft.setY(anchorY);
bottomRight.setX(anchorX);
break;
case 'bottomRight':
topRight.setX(anchorX);
bottomLeft.setY(anchorY);
break;
case 'bottomLeft':
topLeft.setX(anchorX);
bottomRight.setY(anchorY);
break;
}
rotateAnchor.setX(topRight.getX() + 5);
rotateAnchor.setY(topRight.getY() + 20);
image.setPosition((topLeft.getPosition().x + 20), (topLeft.getPosition().y + 20));
var width = topRight.getX() - topLeft.getX() - 30;
var height = bottomLeft.getY() - topLeft.getY() - 30;
if (width && height) {
image.setSize(width, height);
}
}
function addAnchor(group, x, y, name, dragBound) {
var stage = group.getStage();
var layer = group.getLayer();
var anchor = new Kinetic.Circle({
x: x,
y: y,
stroke: '#666',
fill: '#ddd',
strokeWidth: 2,
radius: 8,
name: name,
draggable: true,
dragOnTop: false
});
if (dragBound == 'rotate') {
anchor.setAttrs({
dragBoundFunc: function (pos) {
return getRotatingAnchorBounds(pos, group);
}
});
}
anchor.on('dragmove', function() {
update(this);
layer.draw();
});
anchor.on('mousedown touchstart', function() {
group.setDraggable(false);
this.moveToTop();
});
anchor.on('dragend', function() {
group.setDraggable(true);
layer.draw();
});
// add hover styling
anchor.on('mouseover', function() {
var layer = this.getLayer();
document.body.style.cursor = 'pointer';
this.setStrokeWidth(4);
layer.draw();
});
anchor.on('mouseout', function() {
var layer = this.getLayer();
document.body.style.cursor = 'default';
this.setStrokeWidth(2);
layer.draw();
});
group.add(anchor);
}
function loadImages(sources, callback) {
var images = {};
var loadedImages = 0;
var numImages = 0;
for(var src in sources) {
numImages++;
}
for(var src in sources) {
images[src] = new Image();
images[src].onload = function() {
if(++loadedImages >= numImages) {
callback(images);
}
};
images[src].src = sources[src];
}
}
function getRotatingAnchorBounds(pos, group) {
var topLeft = group.get('.topLeft')[0];
var bottomRight = group.get('.bottomRight')[0];
var topRight = group.get('.topRight')[0];
var absCenterX = Math.abs((topLeft.getAbsolutePosition().x + 5 + bottomRight.getAbsolutePosition().x + 5) / 2);
var absCenterY = Math.abs((topLeft.getAbsolutePosition().y + 5 + bottomRight.getAbsolutePosition().y + 5) / 2);
var relCenterX = Math.abs((topLeft.getX() + bottomRight.getX()) / 2);
var relCenterY = Math.abs((topLeft.getY() + bottomRight.getY()) / 2);
var radius = distance(relCenterX, relCenterY, topRight.getX() + 5, topRight.getY() + 20);
var scale = radius / distance(pos.x, pos.y, absCenterX, absCenterY);
var realRotation = Math.round(degrees(angle(relCenterX, relCenterY, topRight.getX() + 5, topRight.getY() + 20)));
var rotation = Math.round(degrees(angle(absCenterX, absCenterY, pos.x, pos.y)));
rotation -= realRotation;
group.setRotationDeg(rotation);
return {
y: Math.round((pos.y - absCenterY) * scale + absCenterY),
x: Math.round((pos.x - absCenterX) * scale + absCenterX)
};
}
function radians(degrees) { return degrees * (Math.PI / 180); }
function degrees(radians) { return radians * (180 / Math.PI); }
// Calculate the angle between two points.
function angle(cx, cy, px, py) {
var x = cx - px;
var y = cy - py;
return Math.atan2(-y, -x);
}
// Calculate the distance between two points.
function distance(p1x, p1y, p2x, p2y) {
return Math.sqrt(Math.pow((p2x - p1x), 2) + Math.pow((p2y - p1y), 2));
}
function initStage(images) {
var stage = new Kinetic.Stage({
container: 'container',
width: 578,
height: 400
});
var darthVaderGroup = new Kinetic.Group({
x: 270,
y: 100,
draggable: true
});
var yodaGroup = new Kinetic.Group({
x: 100,
y: 110,
draggable: true
});
var layer = new Kinetic.Layer();
/*
* go ahead and add the groups
* to the layer and the layer to the
* stage so that the groups have knowledge
* of its layer and stage
*/
layer.add(darthVaderGroup);
layer.add(yodaGroup);
stage.add(layer);
// darth vader
var darthVaderImg = new Kinetic.Image({
x: 0,
y: 0,
image: images.darthVader,
width: 200,
height: 138,
name: 'image'
});
darthVaderGroup.add(darthVaderImg);
addAnchor(darthVaderGroup, -20, -20, 'topLeft', 'none');
addAnchor(darthVaderGroup, 220, -20, 'topRight', 'none');
addAnchor(darthVaderGroup, 220, 158, 'bottomRight', 'none');
addAnchor(darthVaderGroup, -20, 158, 'bottomLeft','none');
addAnchor(darthVaderGroup, 225, 0, 'rotateAnchor','rotate');
darthVaderGroup.on('dragstart', function() {
this.moveToTop();
});
stage.draw();
}
var sources = {
darthVader: 'http://www.html5canvastutorials.com/demos/assets/darth-vader.jpg'
};
loadImages(sources, initStage);
</script>
</body>
</html>
You can use each anchors show/hide methods inside the images mouseenter/mouseleave events to display the anchors when the mouse enters the image:
image.on("mouseleave",function(){ anchor1.hide(); }
image.on("mouseenter",function(){ anchor1.show(); layer.draw(); }
Problem is that since your anchors are partly outside your image, so hiding the anchors when the mouse leaves the image might make the anchors "disappear" when the user intends to use them.
The ideal solution would be to listen for mouseenter/mouseleave events on the group which contains the image but also extends to include the outside part of the anchors. Unfortunately, a Kinetic.Group will not respond to mouseenter/mouseleave events.
A workaround is to create a Kinetic.Rect background to the group which includes the images plus the anchors. The rect will listen for mouseenter/mouseleave events and will show/hide the anchors. If you don't want the background rect to be visible, just set it's opacity to .001. The rect will still listen for events, but will be invisible.
groupBackgroundRect.on("mouseleave",function(){ anchor1.hide(); }
groupBackgroundRect.on("mouseenter",function(){ anchor1.show(); layer.draw(); }
A related note:
With KineticJS, combining rotation with resizing is made more difficult than it needs to be because KineticJS uses offsetX/offsetY as both an object's rotation point and as an offset to its position. Your key to making it work will be to re-center the offset point after resizing so that your rotation takes place around the new centerpoint--not the previous centerpoint. (or reset the offset reference point to any other point that you want to rotate around).