Created
January 22, 2021 03:17
-
-
Save restureese/25c75890d20db0b603a692d931e348b4 to your computer and use it in GitHub Desktop.
Create Polygon with fabric js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!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> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
It has issues with panning, if we pan image by pressing alt key and drag then points rendered at random locations