FabricJS Help Zoom? - javascript

I wrote a code. Combines point of click through on the screen.
HTML
<button id="end">End</button>
<button id="zoomIn">Zoom-In</button>
<button id="zoomOut">Zoom-Out</button>
<canvas id="c" width="500" height="500" ></canvas>
JavaScript
var canvas = new fabric.Canvas('c');
var point1;
var line=null;
var canvasScale = 1;
var SCALE_FACTOR = 1.2;
var bool = true;
canvas.on('mouse:down', function (options) {
var x = options.e.clientX - canvas._offset.left;
var y = options.e.clientY - canvas._offset.top;
var circle = new fabric.Circle({
left: x,
top: y,
fill: 'red',
originX: 'center',
originY: 'center',
hasControls: false,
hasBorders: false,
lockMovementX: false,
lockMovementY: false,
radius: 5,
hoverCursor: 'default'
});
if(bool)
canvas.add(circle);
point1 = new fabric.Point(x, y);
if(line){
line = new fabric.Line([line.get('x2'), line.get('y2'), x, y], {
stroke: 'black',
hasControls: false,
hasBorders: false,
lockMovementX: false,
lockMovementY: false,
hoverCursor: 'default'
});
}else{
line = new fabric.Line([x, y, x, y], {
stroke: 'black',
hasControls: false,
hasBorders: false,
lockMovementX: false,
lockMovementY: false,
hoverCursor: 'default'
});
}
if(bool)
canvas.add(line);
$("#end").click(function(){
var lines = canvas.getObjects('line');
var firstLine = lines[1];
var lastLine = lines[lines.length - 1];
line = new fabric.Line([firstLine.get('x2'), firstLine.get('y2'), lastLine.get('x2'), lastLine.get('y2')], {
stroke: 'red',
hasControls: false,
hasBorders: false,
lockMovementX: false,
lockMovementY: false,
hoverCursor: 'default'
});
canvas.add(line);
bool = false;
});
});
$("#zoomIn").click(function() {
canvasScale = canvasScale * SCALE_FACTOR;
canvas.setHeight(canvas.getHeight() * SCALE_FACTOR);
canvas.setWidth(canvas.getWidth() * SCALE_FACTOR);
var objects = canvas.getObjects();
for (var i in objects) {
var scaleX = objects[i].scaleX;
var scaleY = objects[i].scaleY;
var left = objects[i].left;
var top = objects[i].top;
var tempScaleX = scaleX * SCALE_FACTOR;
var tempScaleY = scaleY * SCALE_FACTOR;
var tempLeft = left * SCALE_FACTOR;
var tempTop = top * SCALE_FACTOR;
objects[i].scaleX = tempScaleX;
objects[i].scaleY = tempScaleY;
objects[i].left = tempLeft;
objects[i].top = tempTop;
objects[i].setCoords();
}
canvas.renderAll();
});
$("#zoomOut").click(function() {
canvasScale = canvasScale / SCALE_FACTOR;
canvas.setHeight(canvas.getHeight() * (1 / SCALE_FACTOR));
canvas.setWidth(canvas.getWidth() * (1 / SCALE_FACTOR));
var objects = canvas.getObjects();
for (var i in objects) {
var scaleX = objects[i].scaleX;
var scaleY = objects[i].scaleY;
var left = objects[i].left;
var top = objects[i].top;
var tempScaleX = scaleX * (1 / SCALE_FACTOR);
var tempScaleY = scaleY * (1 / SCALE_FACTOR);
var tempLeft = left * (1 / SCALE_FACTOR);
var tempTop = top * (1 / SCALE_FACTOR);
objects[i].scaleX = tempScaleX;
objects[i].scaleY = tempScaleY;
objects[i].left = tempLeft;
objects[i].top = tempTop;
objects[i].setCoords();
}
canvas.renderAll();
});
But here the constant points.
I want to be like that here:
HTML
<canvas id="c" width="500" height="500" ></canvas>
JavaScript
(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, line3, line4) {
var c = new fabric.Circle({
left: left,
top: top,
strokeWidth: 5,
radius: 12,
fill: '#fff',
stroke: '#666'
});
c.hasControls = c.hasBorders = false;
c.line1 = line1;
c.line2 = line2;
c.line3 = line3;
c.line4 = line4;
return c;
}
function makeLine(coords) {
return new fabric.Line(coords, {
fill: 'red',
stroke: 'red',
strokeWidth: 5,
selectable: false
});
}
var line = makeLine([ 250, 125, 250, 175 ]),
line2 = makeLine([ 250, 175, 250, 250 ]),
line3 = makeLine([ 250, 250, 300, 350]),
line4 = makeLine([ 250, 250, 200, 350]),
line5 = makeLine([ 250, 175, 175, 225 ]),
line6 = makeLine([ 250, 175, 325, 225 ]);
canvas.add(line, line2, line3, line4, line5, line6);
canvas.add(
makeCircle(line.get('x1'), line.get('y1'), null, line),
makeCircle(line.get('x2'), line.get('y2'), line, line2, line5, line6),
makeCircle(line2.get('x2'), line2.get('y2'), line2, line3, line4),
makeCircle(line3.get('x2'), line3.get('y2'), line3),
makeCircle(line4.get('x2'), line4.get('y2'), line4),
makeCircle(line5.get('x2'), line5.get('y2'), line5),
makeCircle(line6.get('x2'), line6.get('y2'), line6)
);
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 });
p.line3 && p.line3.set({ 'x1': p.left, 'y1': p.top });
p.line4 && p.line4.set({ 'x1': p.left, 'y1': p.top });
canvas.renderAll();
});
})();
The point for which I should move be able to add points. So, I add points must act together with lines.
How can I integrate it above the code?
Zoom Problem
jsFiddle
Example If zoomIn or zoomOut becomes, my line is added corrupted

You need to adjust the coordinates of the saved line when you zoom in our out.
When zooming in
if (line) {
line.x2 = line.x2 * SCALE_FACTOR;
line.y2 = line.y2 * SCALE_FACTOR;
}
When zooming out
if (line) {
line.x2 = line.x2 / SCALE_FACTOR;
line.y2 = line.y2 / SCALE_FACTOR;
}
Fiddle - https://jsfiddle.net/65c3dxvv/

Solution this project--->
<button id="end">End</button>
<button id="zoomIn">Zoom-In</button>
<button id="zoomOut">Zoom-Out</button>
<canvas id="c" width="1800" height="910" ></canvas>
var canvas = new fabric.Canvas('c');
var point1;
var line=null;
var canvasScale = 1;
var SCALE_FACTOR = 1.2;
var bool = true;
canvas.on('mouse:down', function (options) {
var x = options.e.clientX - canvas._offset.left;
var y = options.e.clientY - canvas._offset.top;
var circle = new fabric.Circle({
left: x,
top: y,
fill: 'red',
originX: 'center',
originY: 'center',
hasControls: false,
hasBorders: false,
lockMovementX: false,
lockMovementY: false,
radius: 5,
hoverCursor: 'default'
});
if(bool) {
circle.line1 = null;
circle.line2 = null;
canvas.add(circle);
}
point1 = new fabric.Point(x, y);
if(line){
line = new fabric.Line([line.get('x2'), line.get('y2'), x, y], {
fill: 'grey',
stroke: 'grey',
hasControls: false,
hasBorders: false,
lockMovementX: true,
lockMovementY: true,
selectable: false,
hoverCursor: 'default'
});
}else{
line = new fabric.Line([x, y, x, y], {
fill: 'grey',
stroke: 'grey',
hasControls: false,
hasBorders: false,
lockMovementX: true,
lockMovementY: true,
selectable: false,
hoverCursor: 'default'
});
}
if(bool)
canvas.add(line);
});
$("#end").click(function(){
var lines = canvas.getObjects('line');
var firstLine = lines[1];
var lastLine = lines[lines.length - 1];
line = new fabric.Line([lastLine.get('x2'), lastLine.get('y2'),firstLine.get('x1'), firstLine.get('y1')], {
fill: 'grey',
stroke: 'grey',
hasControls: false,
hasBorders: false,
lockMovementX: false,
lockMovementY: false,
selectable: false,
hoverCursor: 'default'
});
canvas.add(line);
var lines = canvas.getObjects('line');
var circles = canvas.getObjects('circle');
for(i = 1; i < circles.length - 1; i++) {
for(j = 0; j < lines.length; j++) {
if(circles[i].getLeft() == lines[j].get('x1') && circles[i].getTop() == lines[j].get('y1')) {
circles[i].line2 = lines[j];
circles[i].line1 = lines[j - 1];
}
}
}
circles[0].line1 = lines[lines.length - 1];
circles[0].line2 = lines[1];
circles[circles.length - 1].line2 = lines[lines.length - 1];
circles[circles.length - 1].line1 = lines[0];
bool = false;
});
$("#zoomIn").click(function() {
canvasScale = canvasScale * SCALE_FACTOR;
canvas.setHeight(canvas.getHeight() * SCALE_FACTOR);
canvas.setWidth(canvas.getWidth() * SCALE_FACTOR);
var circles = canvas.getObjects('circle');
for (var i in circles) {
var scaleX = circles[i].scaleX;
var scaleY = circles[i].scaleY;
var left = circles[i].left;
var top = circles[i].top;
circles[i].scaleX = scaleX * SCALE_FACTOR;
circles[i].scaleY = scaleY * SCALE_FACTOR;
circles[i].left = left * SCALE_FACTOR;
circles[i].top = top * SCALE_FACTOR;
circles[i].setCoords();
var coord = circles[i].getCenterPoint();
circles[i].line1.set({
'x2': coord.x,
'y2': coord.y
});
circles[i].line2.set({
'x1': coord.x,
'y1': coord.y
});
}
canvas.renderAll();
canvas.calcOffset();
});
$("#zoomOut").click(function() {
canvasScale = canvasScale / SCALE_FACTOR;
canvas.setHeight(canvas.getHeight() * (1 / SCALE_FACTOR));
canvas.setWidth(canvas.getWidth() * (1 / SCALE_FACTOR));
var circles = canvas.getObjects('circle');
for (var i in circles) {
var scaleX = circles[i].scaleX;
var scaleY = circles[i].scaleY;
var left = circles[i].left;
var top = circles[i].top;
circles[i].scaleX = scaleX * (1 / SCALE_FACTOR);
circles[i].scaleY = scaleY * (1 / SCALE_FACTOR);
circles[i].left = left * (1 / SCALE_FACTOR);
circles[i].top = top * (1 / SCALE_FACTOR);
circles[i].setCoords();
var coord = circles[i].getCenterPoint();
circles[i].line1.set({
'x2': coord.x,
'y2': coord.y
});
circles[i].line2.set({
'x1': coord.x,
'y1': coord.y
});
}
canvas.renderAll();
canvas.calcOffset();
});
canvas.on('object:moving', function(e) {
var p = e.target;
var coord = p.getCenterPoint();
p.line1.set({ 'x2': coord.x, 'y2': coord.y });
p.line2.set({ 'x1': coord.x, 'y1': coord.y });
canvas.renderAll();
});

Related

FabricJS How can I update the custom control point of a polygon after Zoom or Pan the canvas?

Can someone please suggest me the right way to update the transform points and custom control points of my custom polygon (draw by mouse move + click) after zoom or pan the canvas?
Step to reproduce:
click on Draw Polygon
draw a polygon in canvas
zoom (scroll up or down) or pan (alt + mouse drag)
Here is my code: https://jsfiddle.net/ckitisak/qv6y283p/
/**
* based on:
* 1. https://codepen.io/durga598/pen/gXQjdw?editors=0010
* 2. http://fabricjs.com/custom-controls-polygon
*/
let activeLine;
let activeShape;
let canvas;
let lineArray = [];
let pointArray = [];
let drawMode = false;
function initCanvas() {
canvas = new fabric.Canvas('c');
canvas.backgroundColor = 'rgba(0,0,0,0.3)';
canvas.setHeight(500);
canvas.setWidth(500);
fabric.Object.prototype.originX = 'center';
fabric.Object.prototype.originY = 'center';
canvas.on('mouse:down', onMouseDown);
canvas.on('mouse:up', onMouseUp);
canvas.on('mouse:move', onMouseMove);
canvas.on('object:moving', onObjectMove);
canvas.on('mouse:wheel', onMouseWheel);
}
function onMouseDown(options) {
if (drawMode) {
if (options.target && options.target.id === pointArray[0].id) {
// when click on the first point
generatePolygon(pointArray);
} else {
addPoint(options);
}
}
var evt = options.e;
if (evt.altKey === true) {
this.isDragging = true;
this.selection = false;
this.lastPosX = evt.clientX;
this.lastPosY = evt.clientY;
}
}
function onMouseUp(options) {
this.isDragging = false;
this.selection = true;
}
function onMouseMove(options) {
if (this.isDragging) {
var e = options.e;
this.viewportTransform[4] += e.clientX - this.lastPosX;
this.viewportTransform[5] += e.clientY - this.lastPosY;
this.requestRenderAll();
this.lastPosX = e.clientX;
this.lastPosY = e.clientY;
}
if (drawMode) {
if (activeLine && activeLine.class === 'line') {
const pointer = canvas.getPointer(options.e);
activeLine.set({
x2: pointer.x,
y2: pointer.y
});
const points = activeShape.get('points');
points[pointArray.length] = {
x: pointer.x,
y: pointer.y,
};
activeShape.set({
points
});
}
canvas.renderAll();
}
}
function onMouseWheel(options) {
var delta = options.e.deltaY;
var pointer = canvas.getPointer(options.e);
var zoom = canvas.getZoom();
if (delta > 0) {
zoom += 0.1;
} else {
zoom -= 0.1;
}
if (zoom > 20) zoom = 20;
if (zoom < 0.1) zoom = 0.1;
canvas.zoomToPoint({ x: options.e.offsetX, y: options.e.offsetY }, zoom);
options.e.preventDefault();
options.e.stopPropagation();
}
function onObjectMove(option) {
const object = option.target;
object._calcDimensions();
object.setCoords();
canvas.renderAll();
}
function toggleDrawPolygon(event) {
if (drawMode) {
// stop draw mode
activeLine = null;
activeShape = null;
lineArray = [];
pointArray = [];
canvas.selection = true;
drawMode = false;
} else {
// start draw mode
canvas.selection = false;
drawMode = true;
}
}
function addPoint(options) {
const pointOption = {
id: new Date().getTime(),
radius: 5,
fill: '#ffffff',
stroke: '#333333',
strokeWidth: 0.5,
left: options.e.layerX / canvas.getZoom(),
top: options.e.layerY / canvas.getZoom(),
selectable: false,
hasBorders: false,
hasControls: false,
originX: 'center',
originY: 'center',
objectCaching: false,
};
const point = new fabric.Circle(pointOption);
if (pointArray.length === 0) {
// fill first point with red color
point.set({
fill: 'red'
});
}
const linePoints = [
options.e.layerX / canvas.getZoom(),
options.e.layerY / canvas.getZoom(),
options.e.layerX / canvas.getZoom(),
options.e.layerY / canvas.getZoom(),
];
const lineOption = {
strokeWidth: 2,
fill: '#999999',
stroke: '#999999',
originX: 'center',
originY: 'center',
selectable: false,
hasBorders: false,
hasControls: false,
evented: false,
objectCaching: false,
};
const line = new fabric.Line(linePoints, lineOption);
line.class = 'line';
if (activeShape) {
const pos = canvas.getPointer(options.e);
const points = activeShape.get('points');
points.push({
x: pos.x,
y: pos.y
});
const polygon = new fabric.Polygon(points, {
stroke: '#333333',
strokeWidth: 1,
fill: '#cccccc',
opacity: 0.3,
selectable: false,
hasBorders: false,
hasControls: false,
evented: false,
objectCaching: false,
});
canvas.remove(activeShape);
canvas.add(polygon);
activeShape = polygon;
canvas.renderAll();
} else {
const polyPoint = [{
x: options.e.layerX / canvas.getZoom(),
y: options.e.layerY / canvas.getZoom(),
}, ];
const polygon = new fabric.Polygon(polyPoint, {
stroke: '#333333',
strokeWidth: 1,
fill: '#cccccc',
opacity: 0.3,
selectable: false,
hasBorders: false,
hasControls: false,
evented: false,
objectCaching: false,
});
activeShape = polygon;
canvas.add(polygon);
}
activeLine = line;
pointArray.push(point);
lineArray.push(line);
canvas.add(line);
canvas.add(point);
}
function generatePolygon(pointArray) {
const points = [];
// collect points and remove them from canvas
for (const point of pointArray) {
points.push({
x: point.left,
y: point.top,
});
canvas.remove(point);
}
// remove lines from canvas
for (const line of lineArray) {
canvas.remove(line);
}
// remove selected Shape and Line
canvas.remove(activeShape).remove(activeLine);
// create polygon from collected points
const polygon = new fabric.Polygon(points, {
id: new Date().getTime(),
stroke: '#eee',
fill: '#f00',
objectCaching: false,
moveable: false,
//selectable: false
});
canvas.add(polygon);
toggleDrawPolygon();
editPolygon();
}
/**
* define a function that can locate the controls.
* this function will be used both for drawing and for interaction.
*/
function polygonPositionHandler(dim, finalMatrix, fabricObject) {
const transformPoint = {
x: fabricObject.points[this.pointIndex].x - fabricObject.pathOffset.x,
y: fabricObject.points[this.pointIndex].y - fabricObject.pathOffset.y,
};
return fabric.util.transformPoint(transformPoint, fabricObject.calcTransformMatrix());
}
/**
* define a function that will define what the control does
* this function will be called on every mouse move after a control has been
* clicked and is being dragged.
* The function receive as argument the mouse event, the current trasnform object
* and the current position in canvas coordinate
* transform.target is a reference to the current object being transformed,
*/
function actionHandler(eventData, transform, x, y) {
const polygon = transform.target;
const currentControl = polygon.controls[polygon.__corner];
const mouseLocalPosition = polygon.toLocalPoint(new fabric.Point(x, y), 'center', 'center');
const size = polygon._getTransformedDimensions(0, 0);
const finalPointPosition = {
x: (mouseLocalPosition.x * polygon.width) / size.x + polygon.pathOffset.x,
y: (mouseLocalPosition.y * polygon.height) / size.y + polygon.pathOffset.y,
};
polygon.points[currentControl.pointIndex] = finalPointPosition;
return true;
}
/**
* define a function that can keep the polygon in the same position when we change its
* width/height/top/left.
*/
function anchorWrapper(anchorIndex, fn) {
return function(eventData, transform, x, y) {
const fabricObject = transform.target;
const point = {
x: fabricObject.points[anchorIndex].x - fabricObject.pathOffset.x,
y: fabricObject.points[anchorIndex].y - fabricObject.pathOffset.y,
};
// update the transform border
fabricObject._setPositionDimensions({});
// Now newX and newY represent the point position with a range from
// -0.5 to 0.5 for X and Y.
const newX = point.x / fabricObject.width;
const newY = point.y / fabricObject.height;
// Fabric supports numeric origins for objects with a range from 0 to 1.
// This let us use the relative position as an origin to translate the old absolutePoint.
const absolutePoint = fabric.util.transformPoint(point, fabricObject.calcTransformMatrix());
fabricObject.setPositionByOrigin(absolutePoint, newX + 0.5, newY + 0.5);
// action performed
return fn(eventData, transform, x, y);
};
}
function editPolygon() {
let activeObject = canvas.getActiveObject();
if (!activeObject) {
activeObject = canvas.getObjects()[0];
canvas.setActiveObject(activeObject);
}
activeObject.edit = true;
activeObject.objectCaching = false;
const lastControl = activeObject.points.length - 1;
activeObject.cornerStyle = 'circle';
activeObject.controls = activeObject.points.reduce((acc, point, index) => {
acc['p' + index] = new fabric.Control({
positionHandler: polygonPositionHandler,
actionHandler: anchorWrapper(index > 0 ? index - 1 : lastControl, actionHandler),
actionName: 'modifyPolygon',
pointIndex: index,
});
return acc;
}, {});
activeObject.hasBorders = false;
canvas.requestRenderAll();
}
function resizePolygon() {
let activeObject = canvas.getActiveObject();
if (!activeObject) {
activeObject = canvas.getObjects()[0];
canvas.setActiveObject(activeObject);
}
activeObject.edit = false;
activeObject.objectCaching = false;
activeObject.controls = fabric.Object.prototype.controls;
activeObject.cornerStyle = 'rect';
activeObject.hasBorders = true;
canvas.requestRenderAll();
}
initCanvas();
<script src="https://unpkg.com/fabric#4.0.0-beta.12/dist/fabric.js"></script>
<button type="button" onclick="toggleDrawPolygon()">Draw Polygon</button>
<button type="button" onclick="editPolygon()">Edit Polygon</button>
<button type="button" onclick="resizePolygon()">Resize/Move Polygon</button>
<canvas id="c"></canvas>
i modified the code to take in account of the canvas zoom and panning.
The viewport transform needs to be take in account when calculating the position of the controls.
/**
* based on:
* 1. https://codepen.io/durga598/pen/gXQjdw?editors=0010
* 2. http://fabricjs.com/custom-controls-polygon
*/
let activeLine;
let activeShape;
let canvas;
let lineArray = [];
let pointArray = [];
let drawMode = false;
function initCanvas() {
canvas = new fabric.Canvas('c');
canvas.backgroundColor = 'rgba(0,0,0,0.3)';
canvas.setHeight(500);
canvas.setWidth(500);
fabric.Object.prototype.originX = 'center';
fabric.Object.prototype.originY = 'center';
canvas.on('mouse:down', onMouseDown);
canvas.on('mouse:up', onMouseUp);
canvas.on('mouse:move', onMouseMove);
canvas.on('object:moving', onObjectMove);
canvas.on('mouse:wheel', onMouseWheel);
}
function onMouseDown(options) {
if (drawMode) {
if (options.target && options.target.id === pointArray[0].id) {
// when click on the first point
generatePolygon(pointArray);
} else {
addPoint(options);
}
}
var evt = options.e;
if (evt.altKey === true) {
this.isDragging = true;
this.selection = false;
this.lastPosX = evt.clientX;
this.lastPosY = evt.clientY;
}
}
function onMouseUp(options) {
this.isDragging = false;
this.selection = true;
}
function onMouseMove(options) {
if (this.isDragging) {
var e = options.e;
this.viewportTransform[4] += e.clientX - this.lastPosX;
this.viewportTransform[5] += e.clientY - this.lastPosY;
this.requestRenderAll();
this.lastPosX = e.clientX;
this.lastPosY = e.clientY;
}
if (drawMode) {
if (activeLine && activeLine.class === 'line') {
const pointer = canvas.getPointer(options.e);
activeLine.set({
x2: pointer.x,
y2: pointer.y
});
const points = activeShape.get('points');
points[pointArray.length] = {
x: pointer.x,
y: pointer.y,
};
activeShape.set({
points
});
}
canvas.renderAll();
}
}
function onMouseWheel(options) {
var delta = options.e.deltaY;
var pointer = canvas.getPointer(options.e);
var zoom = canvas.getZoom();
if (delta > 0) {
zoom += 0.1;
} else {
zoom -= 0.1;
}
if (zoom > 20) zoom = 20;
if (zoom < 0.1) zoom = 0.1;
canvas.zoomToPoint({ x: options.e.offsetX, y: options.e.offsetY }, zoom);
options.e.preventDefault();
options.e.stopPropagation();
}
function onObjectMove(option) {
const object = option.target;
object._calcDimensions();
object.setCoords();
canvas.renderAll();
}
function toggleDrawPolygon(event) {
if (drawMode) {
// stop draw mode
activeLine = null;
activeShape = null;
lineArray = [];
pointArray = [];
canvas.selection = true;
drawMode = false;
} else {
// start draw mode
canvas.selection = false;
drawMode = true;
}
}
function addPoint(options) {
const pointOption = {
id: new Date().getTime(),
radius: 5,
fill: '#ffffff',
stroke: '#333333',
strokeWidth: 0.5,
left: options.e.layerX / canvas.getZoom(),
top: options.e.layerY / canvas.getZoom(),
selectable: false,
hasBorders: false,
hasControls: false,
originX: 'center',
originY: 'center',
objectCaching: false,
};
const point = new fabric.Circle(pointOption);
if (pointArray.length === 0) {
// fill first point with red color
point.set({
fill: 'red'
});
}
const linePoints = [
options.e.layerX / canvas.getZoom(),
options.e.layerY / canvas.getZoom(),
options.e.layerX / canvas.getZoom(),
options.e.layerY / canvas.getZoom(),
];
const lineOption = {
strokeWidth: 2,
fill: '#999999',
stroke: '#999999',
originX: 'center',
originY: 'center',
selectable: false,
hasBorders: false,
hasControls: false,
evented: false,
objectCaching: false,
};
const line = new fabric.Line(linePoints, lineOption);
line.class = 'line';
if (activeShape) {
const pos = canvas.getPointer(options.e);
const points = activeShape.get('points');
points.push({
x: pos.x,
y: pos.y
});
const polygon = new fabric.Polygon(points, {
stroke: '#333333',
strokeWidth: 1,
fill: '#cccccc',
opacity: 0.3,
selectable: false,
hasBorders: false,
hasControls: false,
evented: false,
objectCaching: false,
});
canvas.remove(activeShape);
canvas.add(polygon);
activeShape = polygon;
canvas.renderAll();
} else {
const polyPoint = [{
x: options.e.layerX / canvas.getZoom(),
y: options.e.layerY / canvas.getZoom(),
}, ];
const polygon = new fabric.Polygon(polyPoint, {
stroke: '#333333',
strokeWidth: 1,
fill: '#cccccc',
opacity: 0.3,
selectable: false,
hasBorders: false,
hasControls: false,
evented: false,
objectCaching: false,
});
activeShape = polygon;
canvas.add(polygon);
}
activeLine = line;
pointArray.push(point);
lineArray.push(line);
canvas.add(line);
canvas.add(point);
}
function generatePolygon(pointArray) {
const points = [];
// collect points and remove them from canvas
for (const point of pointArray) {
points.push({
x: point.left,
y: point.top,
});
canvas.remove(point);
}
// remove lines from canvas
for (const line of lineArray) {
canvas.remove(line);
}
// remove selected Shape and Line
canvas.remove(activeShape).remove(activeLine);
// create polygon from collected points
const polygon = new fabric.Polygon(points, {
id: new Date().getTime(),
stroke: '#eee',
fill: '#f00',
objectCaching: false,
moveable: false,
//selectable: false
});
canvas.add(polygon);
toggleDrawPolygon();
editPolygon();
}
/**
* define a function that can locate the controls.
* this function will be used both for drawing and for interaction.
*/
function polygonPositionHandler(dim, finalMatrix, fabricObject) {
var x = (fabricObject.points[this.pointIndex].x - fabricObject.pathOffset.x),
y = (fabricObject.points[this.pointIndex].y - fabricObject.pathOffset.y);
return fabric.util.transformPoint(
{ x: x, y: y },
fabric.util.multiplyTransformMatrices(
fabricObject.canvas.viewportTransform,
fabricObject.calcTransformMatrix()
)
);
}
/**
* define a function that will define what the control does
* this function will be called on every mouse move after a control has been
* clicked and is being dragged.
* The function receive as argument the mouse event, the current trasnform object
* and the current position in canvas coordinate
* transform.target is a reference to the current object being transformed,
*/
function actionHandler(eventData, transform, x, y) {
var polygon = transform.target,
currentControl = polygon.controls[polygon.__corner],
mouseLocalPosition = polygon.toLocalPoint(new fabric.Point(x, y), 'center', 'center'),
polygonBaseSize = polygon._getNonTransformedDimensions(),
size = polygon._getTransformedDimensions(0, 0),
finalPointPosition = {
x: mouseLocalPosition.x * polygonBaseSize.x / size.x + polygon.pathOffset.x,
y: mouseLocalPosition.y * polygonBaseSize.y / size.y + polygon.pathOffset.y
};
polygon.points[currentControl.pointIndex] = finalPointPosition;
return true;
}
/**
* define a function that can keep the polygon in the same position when we change its
* width/height/top/left.
*/
function anchorWrapper(anchorIndex, fn) {
return function(eventData, transform, x, y) {
var fabricObject = transform.target,
absolutePoint = fabric.util.transformPoint({
x: (fabricObject.points[anchorIndex].x - fabricObject.pathOffset.x),
y: (fabricObject.points[anchorIndex].y - fabricObject.pathOffset.y),
}, fabricObject.calcTransformMatrix()),
actionPerformed = fn(eventData, transform, x, y),
newDim = fabricObject._setPositionDimensions({}),
polygonBaseSize = fabricObject._getNonTransformedDimensions(),
newX = (fabricObject.points[anchorIndex].x - fabricObject.pathOffset.x) / polygonBaseSize.x,
newY = (fabricObject.points[anchorIndex].y - fabricObject.pathOffset.y) / polygonBaseSize.y;
fabricObject.setPositionByOrigin(absolutePoint, newX + 0.5, newY + 0.5);
return actionPerformed;
}
}
function editPolygon() {
let activeObject = canvas.getActiveObject();
if (!activeObject) {
activeObject = canvas.getObjects()[0];
canvas.setActiveObject(activeObject);
}
activeObject.edit = true;
activeObject.objectCaching = false;
const lastControl = activeObject.points.length - 1;
activeObject.cornerStyle = 'circle';
activeObject.controls = activeObject.points.reduce((acc, point, index) => {
acc['p' + index] = new fabric.Control({
positionHandler: polygonPositionHandler,
actionHandler: anchorWrapper(index > 0 ? index - 1 : lastControl, actionHandler),
actionName: 'modifyPolygon',
pointIndex: index,
});
return acc;
}, {});
activeObject.hasBorders = false;
canvas.requestRenderAll();
}
function resizePolygon() {
let activeObject = canvas.getActiveObject();
if (!activeObject) {
activeObject = canvas.getObjects()[0];
canvas.setActiveObject(activeObject);
}
activeObject.edit = false;
activeObject.objectCaching = false;
activeObject.controls = fabric.Object.prototype.controls;
activeObject.cornerStyle = 'rect';
activeObject.hasBorders = true;
canvas.requestRenderAll();
}
initCanvas();
<script src="https://unpkg.com/fabric#4.0.0-beta.12/dist/fabric.js"></script>
<button type="button" onclick="toggleDrawPolygon()">Draw Polygon</button>
<button type="button" onclick="editPolygon()">Edit Polygon</button>
<button type="button" onclick="resizePolygon()">Resize/Move Polygon</button>
<canvas id="c"></canvas>

FabricJS Objects offsetted on mobile

I have created a canvas using fabric.js and encountered a very strange problem on mobile devices. Whenever I add an object i.e a circle it has a bizarre behaviour: Wherever I click on desktop that's where it is added but on mobile its top left and the object can not be selected after being added on canvas.
setupCircle(x0, y0) {
this.circleCoord.x0 = x0;
this.circleCoord.y0 = y0;
this.circleCoord.circObj = new fabric.Circle({
left: x0,
top: y0,
radius: 100,
strokeWidth: 5,
hasControls: true,
borderColor: 'red',
fill: 'rgba(0,0,0,0)',
stroke: this.canvas.freeDrawingBrush.color,
originX: 'center',
originY: 'center'
});
this.canvas.add(this.circleCoord.circObj);
}
This is how I add a shape on mouse move. And above is how I scale the canvas and its objects on different screen resolutions
getCanvasAtResoution(newWidth, newHeight) {
const id = 'c' + this.panelId;
const scaleMultiplier = newWidth / document.getElementsByTagName('canvas')[0].clientWidth;
let objects = this.canvas.getObjects();
for (let i in objects) {
objects[i].scaleX = objects[i].scaleX * scaleMultiplier;
objects[i].scaleY = objects[i].scaleY * scaleMultiplier;
objects[i].left = objects[i].left * scaleMultiplier;
objects[i].top = objects[i].top * scaleMultiplier;
objects[i].setCoords();
}
let obj = this.canvas.backgroundImage;
if (obj) {
obj.scaleX = obj.scaleX * scaleMultiplier;
obj.scaleY = obj.scaleY * scaleMultiplier;
}
this.canvas.discardActiveObject();
this.canvas.setDimensions({
width: newWidth,
height: newHeight
});
this.canvas.renderAll();
this.canvas.calcOffset();
},
var canvas = new fabric.Canvas('canvas');
var result = document.getElementById('result');
canvas.on('mouse:move',function(option){
var pointer = canvas.getPointer(option.e);
result.innerHTML = "x: "+pointer.x+" y: "+pointer.y;
})
canvas{
border:2px solid #000;
}
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.js"></script>
<canvas id='canvas' width=400 height=400></canvas><br>
<span id='result'></span>
Use getPointer to get the mouse position.

Compound Shape Class?

Has anyone ever tried to create a custom shape class for Fabric.js that is a combination of shapes? I want to create something like a ruler, where the left and right ends are vertical lines and is attached to the middle, |---| .
I'd also like to have a text box follow along in the center of this ruler. The ends should also have the ability to reposition.
I've done this without a class, but it won't save or load, since I made my version from multiple shapes.
Anyone have any thoughts if it's possible to create a custom shape like this?
Below is an example of some code that I pulled together from another example I found a while ago. 3 lines are created to make up my ruler. If I export the canvas data I'll get data to recreate as 3 lines, so if I clear the canvas and reload with this data, I'll get my 3 lines, but they will be independent 3 lines. I guess I need to figure out how to save their relationship so they can retain their ruler behavior when reloaded. I was thinking the best approach would be to create a custom shape with a type of "ruler".
window.onload = function() {
var canvas;
var started = false;
var mode = 0;
var lastX = 0;
var lastY = 0;
canvas = new fabric.Canvas('c');
canvas.isDrawingMode = false;
canvas.isTouchSupported = true;
canvas.selection = false;
canvas.on("mouse:down", function(option) {
if (mode === 1) {
var mouse = canvas.getPointer(option.e);
lastX = mouse.x;
lastY = mouse.y;
started = true;
var ln = new fabric.Line([lastX, lastY, lastX, lastY], {
fill: 'red',
stroke: 'red',
strokeWidth: 4,
originX: 'center',
originY: 'center',
lockScalingX: true,
lockScalingY: true,
lockRotation: true,
hasBorders: false,
hasControls: false,
perPixelTargetFind: true
});
var rAnchor = new fabric.Line([lastX - 20, lastY, lastX + 20, lastY], {
fill: 'red',
stroke: 'red',
strokeWidth: 4,
originX: 'center',
originY: 'center',
lockScalingX: true,
lockScalingY: true,
lockRotation: true,
hasBorders: false,
hasControls: false,
perPixelTargetFind: true
});
var tip = new fabric.Line([lastX - 20, lastY, lastX + 20, lastY], {
fill: 'red',
stroke: 'red',
strokeWidth: 4,
originX: 'center',
originY: 'center',
lockScalingX: true,
lockScalingY: true,
lockRotation: true,
hasBorders: false,
hasControls: false,
perPixelTargetFind: true
});
tip.line = ln;
rAnchor.line = ln;
ln.tip = tip;
ln.rAnchor = rAnchor;
canvas.add(ln);
canvas.add(rAnchor);
canvas.add(tip);
canvas.setActiveObject(ln);
canvas.renderAll();
}
});
canvas.on("mouse:move", function(e) {
if (!started) {
return false;
}
var mouse = canvas.getPointer(e.e);
var line = canvas.getActiveObject();
line.set('x2', mouse.x).set('y2', mouse.y);
var tip = line.get('tip');
tip.set('left', mouse.x).set('top', mouse.y);
var rAnchor = line.get('rAnchor');
// Get the angle for pointing the tip at the mouse location.
var rad = Math.atan2((line.y1 - line.y2), (line.x1 - line.x2));
var ang = rad * (180 / Math.PI);
tip.set('angle', (ang - 90));
rAnchor.set('angle', (ang - 90));
line.setCoords();
tip.setCoords();
rAnchor.setCoords();
canvas.renderAll();
});
canvas.on("mouse:up", function() {
if (started) {
started = false;
}
canvas.discardActiveObject();
canvas.renderAll();
});
// Event Handler for Drawn Object move
canvas.on('object:moving', function(e) {
var p = e.target;
// move line
if (p.tip) {
var oldCenterX = (p.x1 + p.x2) / 2;
var oldCenterY = (p.y1 + p.y2) / 2;
var deltaX = p.left - oldCenterX;
var deltaY = p.top - oldCenterY;
p.tip.set({
'left': p.x2 + deltaX,
'top': p.y2 + deltaY
}).setCoords();
p.rAnchor.set({
'left': p.x1 + deltaX,
'top': p.y1 + deltaY
});
p.set({
'x1': p.x1 + deltaX,
'y1': p.y1 + deltaY
});
p.set({
'x2': p.x2 + deltaX,
'y2': p.y2 + deltaY
});
p.set({
'left': (p.x1 + p.x2) / 2,
'top': (p.y1 + p.y2) / 2
});
}
// move tip
if (p.line) {
var tip = p.line.tip;
var rAnchor = p.line.rAnchor;
p.line.set({
'x2': tip.left,
'y2': tip.top,
'x1': rAnchor.left,
'y1': rAnchor.top
});
// Get the angle for pointing the tip at the mouse location.
var rad = Math.atan2((p.line.y1 - p.line.y2), (p.line.x1 - p.line.x2));
var ang = rad * (180 / Math.PI);
tip.set('angle', (ang - 90));
rAnchor.set('angle', (ang - 90));
tip.setCoords();
rAnchor.setCoords();
p.line.setCoords();
}
canvas.renderAll();
});
// Button Event Handlers
document.getElementById("btnEdit").addEventListener("click", function(e) {
mode = 0;
});
document.getElementById("btnArrow").addEventListener("click", function(e) {
mode = 1;
});
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.17/fabric.min.js"></script>
<canvas style="border: 2px solid; " height="500" width="600" id="c"></canvas>
<button id="btnEdit">Edit</button>
<button id="btnArrow">Ruler</button>

FabricJS Draw Line But...?

I want to do something. Example, I will put a point when I click on the screen. After When I click somewhere else to put point there. Finally, automatic line to be drawn between these two points. How do I do this?
Thank you for your help:)
HTML
<canvas id="c" height="200" width="300"></canvas>
JS
var canvas = new fabric.Canvas('c');
var point1;
canvas.on('mouse:down', function (options) {
var x = options.e.clientX - canvas._offset.left;
var y = options.e.clientY - canvas._offset.top;
var circle = new fabric.Circle({
left: x,
top: y,
fill: 'red',
originX: 'center',
originY: 'center',
hasControls: false,
hasBorders: false,
lockMovementX: true,
lockMovementY: true,
radius: 5,
hoverCursor: 'default'
});
canvas.add(circle);
if (point1 === undefined) {
point1 = new fabric.Point(x, y)
} else {
canvas.add(new fabric.Line([point1.x, point1.y, x, y], {
stroke: 'blue',
hasControls: false,
hasBorders: false,
lockMovementX: true,
lockMovementY: true,
hoverCursor: 'default'
}))
point1 = undefined;
}
});
Fiddle - http://jsfiddle.net/zdaax418/
Solution;
HTML
<button id="end">End</button>
<button id="zoomIn">Zoom-In</button>
<button id="zoomOut">Zoom-Out</button>
<canvas id="c" width="1800" height="910" ></canvas>
JavaScript
var canvas = new fabric.Canvas('c');
var point1;
var line=null;
var canvasScale = 1;
var SCALE_FACTOR = 1.2;
var bool = true;
canvas.on('mouse:down', function (options) {
var x = options.e.clientX - canvas._offset.left;
var y = options.e.clientY - canvas._offset.top;
var circle = new fabric.Circle({
left: x,
top: y,
fill: 'red',
originX: 'center',
originY: 'center',
hasControls: false,
hasBorders: false,
lockMovementX: false,
lockMovementY: false,
radius: 5,
hoverCursor: 'default'
});
if(bool) {
circle.line1 = null;
circle.line2 = null;
canvas.add(circle);
}
point1 = new fabric.Point(x, y);
if(line){
line = new fabric.Line([line.get('x2'), line.get('y2'), x, y], {
fill: 'grey',
stroke: 'grey',
hasControls: false,
hasBorders: false,
lockMovementX: true,
lockMovementY: true,
selectable: false,
hoverCursor: 'default'
});
}else{
line = new fabric.Line([x, y, x, y], {
fill: 'grey',
stroke: 'grey',
hasControls: false,
hasBorders: false,
lockMovementX: true,
lockMovementY: true,
selectable: false,
hoverCursor: 'default'
});
}
if(bool)
canvas.add(line);
});
$("#end").click(function(){
var lines = canvas.getObjects('line');
var firstLine = lines[1];
var lastLine = lines[lines.length - 1];
line = new fabric.Line([lastLine.get('x2'), lastLine.get('y2'),firstLine.get('x1'), firstLine.get('y1')], {
fill: 'grey',
stroke: 'grey',
hasControls: false,
hasBorders: false,
lockMovementX: false,
lockMovementY: false,
selectable: false,
hoverCursor: 'default'
});
canvas.add(line);
var lines = canvas.getObjects('line');
var circles = canvas.getObjects('circle');
for(i = 1; i < circles.length - 1; i++) {
for(j = 0; j < lines.length; j++) {
if(circles[i].getLeft() == lines[j].get('x1') && circles[i].getTop() == lines[j].get('y1')) {
circles[i].line2 = lines[j];
circles[i].line1 = lines[j - 1];
}
}
}
circles[0].line1 = lines[lines.length - 1];
circles[0].line2 = lines[1];
circles[circles.length - 1].line2 = lines[lines.length - 1];
circles[circles.length - 1].line1 = lines[0];
bool = false;
});
$("#zoomIn").click(function() {
canvasScale = canvasScale * SCALE_FACTOR;
canvas.setHeight(canvas.getHeight() * SCALE_FACTOR);
canvas.setWidth(canvas.getWidth() * SCALE_FACTOR);
var circles = canvas.getObjects('circle');
for (var i in circles) {
var scaleX = circles[i].scaleX;
var scaleY = circles[i].scaleY;
var left = circles[i].left;
var top = circles[i].top;
circles[i].scaleX = scaleX * SCALE_FACTOR;
circles[i].scaleY = scaleY * SCALE_FACTOR;
circles[i].left = left * SCALE_FACTOR;
circles[i].top = top * SCALE_FACTOR;
circles[i].setCoords();
var coord = circles[i].getCenterPoint();
circles[i].line1.set({
'x2': coord.x,
'y2': coord.y
});
circles[i].line2.set({
'x1': coord.x,
'y1': coord.y
});
}
canvas.renderAll();
canvas.calcOffset();
});
$("#zoomOut").click(function() {
canvasScale = canvasScale / SCALE_FACTOR;
canvas.setHeight(canvas.getHeight() * (1 / SCALE_FACTOR));
canvas.setWidth(canvas.getWidth() * (1 / SCALE_FACTOR));
var circles = canvas.getObjects('circle');
for (var i in circles) {
var scaleX = circles[i].scaleX;
var scaleY = circles[i].scaleY;
var left = circles[i].left;
var top = circles[i].top;
circles[i].scaleX = scaleX * (1 / SCALE_FACTOR);
circles[i].scaleY = scaleY * (1 / SCALE_FACTOR);
circles[i].left = left * (1 / SCALE_FACTOR);
circles[i].top = top * (1 / SCALE_FACTOR);
circles[i].setCoords();
var coord = circles[i].getCenterPoint();
circles[i].line1.set({
'x2': coord.x,
'y2': coord.y
});
circles[i].line2.set({
'x1': coord.x,
'y1': coord.y
});
}
canvas.renderAll();
canvas.calcOffset();
});
canvas.on('object:moving', function(e) {
var p = e.target;
var coord = p.getCenterPoint();
p.line1.set({ 'x2': coord.x, 'y2': coord.y });
p.line2.set({ 'x1': coord.x, 'y1': coord.y });
canvas.renderAll();
});

Define x,y of line (start/end) connector for two shapes KineticJS/HTML5

I'm working in a small project where I'll need create a resizable parent shape and a resizable child shape with a connection line. I did​​ in KinecticJS.
But, I have a problem for calculate the x1, x2 (start connector) and x2,y2 (end connector) when the shape is resizing.
This calculate is in the function addConnection:
var x1 = parentNode.getX() + rectParent.getWidth()/2;
var y1 = parentNode.getY() + rectParent.getHeight()/2;
var x2 = childNode.getX() + rectChild.getWidth()/2;
var y2 = childNode.getY() + rectChild.getHeight()/2;
I put my working code in http://jsfiddle.net/geremora/nxDNH/
My Javascript code:
var stage = new Kinetic.Stage({
container: 'container',
width: 400,
height: 400
});
var groupRoot = new Kinetic.Group({
x: 100,
y: 50,
draggable: true,
});
var layer = new Kinetic.Layer();
layer.add(groupRoot);
stage.add(layer);
newRect(groupRoot);
var groupChild = new Kinetic.Group({
x: 270,
y: 100,
draggable: true
});
layer.add(groupChild);
newRect(groupChild);
var con = addConnection(groupRoot, groupChild);
layer.add(con);
con.moveToBottom();
stage.draw();
function newRect(group){
var rect = new Kinetic.Rect({
x: 0,
y: 0,
width: 50,
height: 50,
fill: 'blue',
stroke: 'black',
strokeWidth: 1,
name:'rect'
});
group.add(rect);
addAnchor(group, rect.x, rect.y, 'topLeft');
addAnchor(group, rect.getWidth(), rect.y, 'topRight');
addAnchor(group, rect.getWidth(), rect.getHeight(), 'bottomRight');
addAnchor(group, rect.x, rect.getHeight(), 'bottomLeft');
group.on('dragstart', function() {
this.moveToTop();
});
stage.draw();
}
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 rect = group.get('.rect')[0];
var anchorX = activeAnchor.getX();
var anchorY = activeAnchor.getY();
switch (activeAnchor.getName()) {
case 'topLeft':
topRight.setY(anchorY);
bottomLeft.setX(anchorX);
break;
case 'topRight':
topLeft.setY(anchorY);
bottomRight.setX(anchorX);
break;
case 'bottomRight':
bottomLeft.setY(anchorY);
topRight.setX(anchorX);
break;
case 'bottomLeft':
bottomRight.setY(anchorY);
topLeft.setX(anchorX);
break;
}
rect.setPosition(topLeft.getPosition());
var width = topRight.getX() - topLeft.getX();
var height = bottomLeft.getY() - topLeft.getY();
if(width && height) {
rect.setSize(width, height);
}
}
function addAnchor(group, x, y, name) {
var stage = group.getStage();
var layer = group.getLayer();
var anchor = new Kinetic.Circle({
x: x,
y: y,
stroke: '#666',
fill: '#ddd',
strokeWidth: 1,
radius: 5,
name: name,
draggable: true,
dragOnTop: false
});
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();
});
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 addConnection(parentNode, childNode){
var connector = new Kinetic.Line({
drawFunc: function (canvas) {
var rectParent = parentNode.get('.rect')[0];
var rectChild = childNode.get('.rect')[0];
var ctx = canvas.getContext();
var x1 = parentNode.getX() + rectParent.getWidth()/2;
var y1 = parentNode.getY() + rectParent.getHeight()/2;
var x2 = childNode.getX() + rectChild.getWidth()/2;
var y2 = childNode.getY() + rectChild.getHeight()/2;
ctx.save();
ctx.strokeStyle = "red";
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
ctx.restore();
},
points: [1, 1, 1, 3],
stroke: "red",
strokeWidth: 2,
lineCap: 'round',
lineJoin: 'round',
opacity: 1,
draggable: false
});
return connector;
}
Problem: when you are moving, for example, topLeft anchor, you are changing X position of rectange. But X position of group is not changing. So solution - add rect position when are are calculating position of connector:
var x1 = parentNode.getX() + rectParent.getX()+ rectParent.getWidth()/2;
var y1 = parentNode.getY() + rectParent.getY() + rectParent.getHeight()/2;
var x2 = childNode.getX() + rectChild.getX()+ rectChild.getWidth()/2;
var y2 = childNode.getY() + rectChild.getY() + rectChild.getHeight()/2;
http://jsfiddle.net/lavrton/pAQKx/

Categories