I'm creating a Polygon with fabric.js and set its value "perPixelTargetFind" true.
When you click, for example, on the right top end (not inside the polygon, inside its bounding box), the polygon gets selcected, although "perPixelTargetFind" is set ( I though with this value it is only possible to select an object by directly clicking on it).
The polygon should only be selected, if you click directly on it, is this possible?
Here is a link to the issue on jsfiddle: Polygon perPixelTargetFind
And here is my code so far:
var canvas = new fabric.Canvas('canvas');
fabric.Object.prototype.originX = fabric.Object.prototype.originY = 'center';
document.getElementById("canvas").tabIndex = 1000;
var pol = new fabric.Polygon([
{x: 200, y: 0},
{x: 250, y: 50},
{x: 250, y: 100},
{x: 150, y: 100},
{x: 150, y: 50} ], {
left: 250,
top: 150,
fill: 'green',
perPixelTargetFind: true
}
);
canvas.add(pol);
thank you for your help! :)
Related
Say I have an array of 4 x/y co-ordinates
[{x: 10, y: 5}, {x:10, y:15}, {x:20, y:10}, {x:20, y:20}]
Is there a way to construct a HTML element, so that each of the four corners math the co-ordinates in the array?
I know this is possible using canvas, but I'm stuggling to work out how to go about doing this with HTML elements.
The array will always contain 4 sets of coordinates.
The final shape may be rotated or skewed, but will always be a "valid" shape which can be acheived using CSS transformations.
Assuming you got it in form of [topLeft, bottomLeft, topRight, BottomRight] of the original rectangle, you can try recreate it like this:
const obj1 = [{x: 10, y: 5}, {x:10, y:15}, {x:20, y:10}, {x:20, y:20}];
const obj2 = [{x: 40, y: 80}, {x: 10, y: 160}, {x: 120, y: 80}, {x: 90, y: 160}];
const obj3 = [{x: 200, y: 30}, {x: 150, y: 80}, {x: 250, y: 80}, {x: 200, y: 130}];
function render(obj) {
const skewX = obj[1].x - obj[0].x;
const skewY = obj[2].y - obj[0].y;
let translateX = Math.min(...obj.map(t => t.x));
let translateY = Math.min(...obj.map(t => t.y));
if(skewX<0) translateX -= skewX;
if(skewY<0) translateY -= skewY;
const scaleX = Math.abs(obj[0].x - obj[2].x);
const scaleY = Math.abs(obj[0].y - obj[1].y);
const el = document.createElement('div');
el.style.width = '1px';
el.style.height = '1px';
el.style.backgroundColor = 'blue';
el.style.transformOrigin = 'top left';
el.style.transform = `matrix(${scaleX}, ${skewY}, ${skewX}, ${scaleY}, ${translateX}, ${translateY})`;
document.body.appendChild(el);
}
render(obj1);
render(obj2);
render(obj3);
However, I will recommend you to not store the shapes as its vertices but as it's transformation matrix. (if it's possible, of course)
If you're simply trying to draw shapes without the use of canvas, you could maybe draw SVG shapes by translating the coordinates in your object.
If you want to deform a div, best I can think off top of my head is to make use of CSS transform:matrix, but you'd need to figure out how to translate your x/y for each corner coordinates to scale/skew/translate parameters.
If you're not deforming a div, and simply creating a regular rectangular one, then you should be able to translate your x/y coordinates into top; left; width; height; CSS properties.
Well, no. HTML is a tree-like structured DOM. Although, you can have a DOM with position: absolute (absolute to html) and top: y; left: x, but it does not have any advantage doing it this way, from my perspective.
I start with interract.js and try to figure out how to snap my elements to defined sizes in the index targets. It works well when I set the values statically but actually I would like to dynamize the values so that each element can snap to each other.
I didn't find how to do this in the doc, would you have a research track allowing me to make the values evolve according to the displacement of the blocks, so that they become "snappable" ?
ideally I think these values should be able to change dynamically each time you move an element, so using dragend event, but how do you make the change in values take effect...
thank you for your feedback.
// here I define my static values for the snap
var dynamicRestrictions = [
{x: 200, range: 20},
{x: 400, range: 20},
{y: 300, range: 20}
];
interact('.resize-drag')
.draggable({
inertia: true,
modifiers: [
interact.modifiers.snap({
targets: dynamicRestrictions, // <= how to dynamic edit them ?
relativePoints: [
{x: 0, y: 0},
{x: 1, y: 1},
{x: 0, y: 1},
{x: 1, y: 0},
],
offset: 'parent'
}),
],
autoScroll: true,
listeners: {
move: dragMoveListener,
}
})
you can use a function on the onstart event.
.draggable({
//...
onstart: dragMoveStartListener,
//...
})
which will allow you to set your hang values.
function dragMoveStartListener(event) {
var element = $(event.target);
dynamicRestrictions.length = 0;
$('.resize-drag').each(function () {
if (!$(this).is(element)) {
var thisPosition = $(this).position(),
snapX = thisPosition.left,
snapY = thisPosition.top,
snapWidth = $(this).width(),
snapHeight = $(this).height(),
range = 20;
var sumWidth = parseInt(snapX + snapWidth),
sumHeight = parseInt(snapY + snapHeight);
dynamicRestrictions.push({x: snapX, y: snapY, range: 10});
dynamicRestrictions.push({x: snapX, range: range});
dynamicRestrictions.push({x: sumWidth, range: range});
dynamicRestrictions.push({y: snapY, range: range});
dynamicRestrictions.push({y: sumHeight, range: range});
}
});
}
I tested it and it works pretty well, except that I don't know how to snap both vertically and horizontally, each time it's one or the other...
looking for a little bit more, should do the job
I have a polygon (in fact it's a hexagon) painted with Adobe's Snap.svg:
var s = Snap('#test');
var hexagon = s.paper.polygon([
0, 50,
50, 0,
100, 0,
150, 50,
100, 100,
50, 100,
0, 50
]);
hexagon.attr({
stroke: '#fff',
strokeWidth: 1
});
Instead of filling the polygon with some paint, I want to place an image inside of it. The only thing I have found in the docs is the image function, but I don't know how to insert it. Anyone any idea?
======
Final Solution:
var image = s.paper.image('http://placekitten.com/g/200/300', 0, 0, 150, 150);
image = image.pattern(0, 0, 150, 150);
...
hexagon.attr({
stroke: '#fff',
strokeWidth: 1,
fill: image
});
You can't directly fill an element with an image you have to go via a pattern.
What this means is that you need to create a pattern that contains an image and then fill the shape with the pattern.
What I want do is dynamically create a shape on mousedown, and then immediately have it (the shape) follow the mouse cursor until mouseup sets it in place.
Here is what I have so far and it's not working for me.
addNegativeButton.on('mousedown', function(){
var userPos = stage.getUserPosition();
shapesLayer.add(new Kinetic.Image({
image: imageObj,
x: userPos.x,
y: userPos.y,
height: 25,
width: 25,
rotation: 1,
draggable: true,
offset: 12
}));
var last = shapesLayer.getChildren()[shapesLayer.getChildren().length -1];
stage.on("mouseup", function(){
last.setAbsolutePosition(stage.getUserPosition());
stage.off("mouseup");
});
To reiterate:
What I have is an 'addNegativeButton' which when clicked creates a shape.
But I want it to follow the mouse cursor until the click is released.
http://jsfiddle.net/CPrEe/37/
Any suggestions?
Turns out its rather simple ;)
After you add the element/shape, all you have to do is use the simulate function to simulate mouse down and it drags....
var stage = new Kinetic.Stage({
container: 'container',
width: 578,
height: 200
});
var layer = new Kinetic.Layer();
var rect = new Kinetic.Rect({
x: 20,
y: 20,
width: 100,
height: 50,
fill: 'green',
stroke: 'black',
strokeWidth: 4
});
//What I really want here is to start dragging the circle until the
//user releases the mouse, which would then place the circle.
rect.on('mousedown', function(evt) {
var userPos = stage.getUserPosition();
var latestElement = new Kinetic.Circle({
x: userPos.x,
y: userPos.y,
radius: 20,
fill: 'red',
stroke: 'black',
strokeWidth: 4,
draggable: true
})
layer.add(latestElement);
// Here's the bit you want. After adding the circle (with draggable:true) simulate the mousedown event on it which will initiate the drag
latestElement.simulate('mousedown');
layer.draw();
});
layer.add(rect);
stage.add(layer);
http://jsfiddle.net/CPrEe/38/
http://kineticjs.com/docs/symbols/Kinetic.Node.php#simulate
I have KineticJS rect already fill with 'blue' color, I want to show Text on this rect box. But my below code not showing anything. Also I need to assign this label text on button click, the code for the same is also below.
drawShape = new Kinetic.Rect({
id: shapeId,
name: shapeName,
x: 0,
y: 0,
width: 150,
height: 40,
fill: "#00D2FF",
stroke: "black",
strokeWidth: 3,
fillText: "Step" + stepNumber
});
function OnApplyPropertiesClick(){
drawShape.fillText(document.getElementById("txtLabel").value);
}
any help on this?? please.
Thanks
Biru
I don't think you can add text to a rectangle object, only a fill type of:
[color, linear gradient, radial gradient, or pattern]
To solve this i created a group, then added a rectangle and text object to it, when i wanted to create a rectangle with text on it:
var group = new Kinetic.Group({
});
var rectangle = new Kinetic.Rect({
x: 5,
y: 5,
width: 80,
height: 35,
fill: "green",
stroke: "black",
strokeWidth: 2
});
var rectangleText = new Kinetic.Text({
x: 15,
y: 10,
text: 'test',
fontSize: 20,
fontFamily: 'Calibri',
textFill: 'black'
});
group.add(rectangle);
group.add(rectangleText);
layer.add(group);
You should be able to adapt your listener to then apply it to this.
I've done a jsfiddle to show what i mean:
http://jsfiddle.net/B85Ff/
Have you added drawShape to any layer yet? Doesn't appear in the snippet you posted.
Also, you need to draw the layer (like a refresh) at the end of your OnApplyPropertiesClick() function.