Skip to content

Instantly share code, notes, and snippets.

@restureese
Created January 22, 2021 03:17
Show Gist options
  • Select an option

  • Save restureese/25c75890d20db0b603a692d931e348b4 to your computer and use it in GitHub Desktop.

Select an option

Save restureese/25c75890d20db0b603a692d931e348b4 to your computer and use it in GitHub Desktop.
Create Polygon with fabric js
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<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>
<button type="button" onclick="clearPolygon()">Clear</button>
<canvas id="c"></canvas>
<script type="text/javascript">
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(720);
canvas.setWidth(1280);
var imageUrl = "https://images.wallpaperscraft.com/image/road_asphalt_marking_130996_1280x720.jpg";
// Define
canvas.setBackgroundImage(imageUrl, canvas.renderAll.bind(canvas), {
// Optionally add an opacity lvl to the image
backgroundImageOpacity: 0.5,
// should the image be resized to fit the container?
backgroundImageStretch: true
});
//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
console.log(points)
const polygon = new fabric.Polygon(points, {
id: new Date().getTime(),
stroke: '#0084ff',
fill: false,
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();
}
function clearPolygon(){
canvas.remove(...canvas.getObjects());
}
initCanvas();
</script>
</body>
</html>
@DevYunus
Copy link

DevYunus commented Jul 1, 2021

It has issues with panning, if we pan image by pressing alt key and drag then points rendered at random locations

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment