An example of binding AngularJS to a FabricJS canvas by using a service as a way of mimicking $scope for FabricJS.
A Pen by Michael Calkins on CodePen.
An example of binding AngularJS to a FabricJS canvas by using a service as a way of mimicking $scope for FabricJS.
A Pen by Michael Calkins on CodePen.
<a class='btn btn-info' target='_blank' href='https://github.com/clouddueling/angular-fabric'> | |
<i class='fa fa-github'></i> | |
clouddueling/angular-fabric | |
</a> | |
<div ng-app='example' ng-controller="ExampleCtrl"> | |
<div class='image-builder-container'> | |
<div ng-if="fabric.isLoading()" class="image-loading"> | |
<div class="loading-indicator"></div> | |
</div> | |
<div class="row"> | |
<div class="col-sm-12"> | |
<small><em class='text-muted'>Image and shape do not work due to codepen cross-origin.</em></small> | |
<br /> | |
<div class='btn-group pull-left mrm'> | |
<button ng-disabled="!fabric.selectedObject" ng-click="fabric.deleteActiveObject(); fabric.setDirty(true)" class='btn btn-danger'> | |
<i class='fa fa-trash-o'></i> | |
<div class='clearfix'></div> | |
Delete | |
</button> | |
<button ng-click='addImage()' class='btn btn-default'> | |
<i class='fa fa-image'></i> | |
<div class='clearfix'></div> | |
Image | |
</button> | |
<button ng-click="fabric.addText()" class='btn btn-default'> | |
<i class='fa fa-font'></i> | |
<div class='clearfix'></div> | |
Text | |
</button> | |
<button ng-click='addShape()' class='btn btn-default'> | |
<i class='fa fa-star'></i> | |
<div class='clearfix'></div> | |
Shapes | |
</button> | |
<button ng-click="fabric.clearCanvas(); fabric.setDirty(true)" class='btn btn-default'> | |
<i class='fa fa-refresh'></i> | |
<div class='clearfix'></div> | |
Restart | |
</button> | |
</div> | |
<div class='pull-left' style='margin: 15px 0 0 10px;'> | |
<div class='row'> | |
<div class='col-xs-1 text-center'> | |
<i class='fa fa-search-minus pull-left'></i> | |
</div> | |
<div class='col-xs-6'> | |
<input type='range' class='pull-left' min='.1' max='3' step='.1' ng-change='fabric.setZoom()' ng-model='fabric.canvasScale' /> | |
</div> | |
<div class='col-xs-1 text-center'> | |
<i class='fa fa-search-plus'></i> | |
</div> | |
<div class='col-xs-3'> | |
<button class='btn btn-xs' ng-click="fabric.resetZoom()"> | |
Reset | |
</button> | |
</div> | |
</div> | |
</div> | |
<div class="pull-right"> | |
<button class='btn btn-default' title='Dummy button. This is where you would take the json from the canvas and save it to your database.'> | |
<i class='fa fa-save'></i> | |
<div class='clearfix'></div> | |
Save <span ng-show='fabric.isDirty()' class='text-danger'>*</span> | |
</button> | |
<button ng-click="fabric.download('myCanvas')" class='btn btn-success'> | |
<i class='fa fa-download'></i> | |
<div class='clearfix'></div> | |
Download | |
</button> | |
</div> | |
</div> | |
</div> | |
<br /> | |
<div class="row"> | |
<div class="col-xs-4"> | |
<label>Background Color</label> | |
<input type="text" class='form-control' ng-change='fabric.setDirty(true); fabric.stopContinuousRendering()' ng-model="fabric.canvasBackgroundColor" /> | |
</div> | |
<div class='col-xs-4'> | |
<label>Canvas Size</label> | |
<br /> | |
<button ng-hide='canvasCopy' class='btn btn-default btn-block mrm' ng-click='selectCanvas()'> | |
{{ fabric.canvasOriginalWidth }} x {{ fabric.canvasOriginalHeight }} | |
</button> | |
<div class='row' ng-show='canvasCopy'> | |
<div class='col-xs-12'> | |
<form ng-submit='setCanvasSize()'> | |
<div class='form-group'> | |
<label>Width</label> | |
<input type='text' ng-model='canvasCopy.width' class='form-control' /> | |
</div> | |
<div class='form-group'> | |
<label>Height</label> | |
<input type='text' ng-model='canvasCopy.height' class='form-control' /> | |
</div> | |
<button type='submit' class='btn btn-success'> | |
Submit | |
</button> | |
</form> | |
</div> | |
</div> | |
</div> | |
<div class='col-xs-4'> | |
<label> </label> | |
<br /> | |
<div class="btn-group col-xs-12"> | |
<a class="btn btn-block btn-default dropdown-toggle" data-toggle="dropdown" href="#"> | |
Preset Sizes | |
<span class="caret"></span> | |
</a> | |
<ul class="dropdown-menu pull-right"> | |
<li ng-click='fabric.setCanvasSize(size.width, size.height); fabric.setDirty(true)' ng-repeat='size in FabricConstants.presetSizes'> | |
<a>{{ size.name }}</a> | |
</li> | |
</ul> | |
</div> | |
</div> | |
</div> | |
<br /> | |
<div class='row'> | |
<div class='col-xs-3'> | |
<div ng-if='fabric.selectedObject'> | |
<div ng-switch='fabric.selectedObject.type'> | |
<div ng-switch-when='text'> | |
<p> | |
<textarea style="text-align: {{ fabric.selectedObject.textAlign }}" class="form-control" rows="6" ng-model="fabric.selectedObject.text"></textarea> | |
</p> | |
<p title="Font size"> | |
<i class="fa fa-text-height"></i> | |
<input type='number' class="form-control" ng-model="fabric.selectedObject.fontSize" /> | |
</p> | |
<p title="Line height"> | |
<i class="fa fa-align-left"></i> | |
<input type='number' class="form-control" ng-model="fabric.selectedObject.lineHeight" step=".1" /> | |
</p> | |
<div class='btn-group'> | |
<button ng-class="{ active: fabric.selectedObject.textAlign == 'left' }" ng-click="fabric.selectedObject.textAlign = 'left'" class='btn btn-small'> | |
<i class='fa fa-align-left'></i> | |
</button> | |
<button ng-class="{ active: fabric.selectedObject.textAlign == 'center' }" ng-click="fabric.selectedObject.textAlign = 'center'" class='btn btn-small'> | |
<i class='fa fa-align-center'></i> | |
</button> | |
<button ng-class="{ active: fabric.selectedObject.textAlign == 'right' }" ng-click="fabric.selectedObject.textAlign = 'right'" class='btn btn-small'> | |
<i class='fa fa-align-right'></i> | |
</button> | |
</div> | |
<br /> | |
<div class='btn-group'> | |
<button ng-class="{ active: fabric.isBold() }" ng-click="fabric.toggleBold()" class='btn btn-small'> | |
<i class='fa fa-bold'></i> | |
</button> | |
<button ng-class="{ active: fabric.isItalic() }" ng-click="fabric.toggleItalic()" class='btn btn-small'> | |
<i class='fa fa-italic'></i> | |
</button> | |
<button ng-class="{ active: fabric.isUnderline() }" ng-click="fabric.toggleUnderline()" class='btn btn-small'> | |
<i class='fa fa-underline'></i> | |
</button> | |
<button ng-class="{ active: fabric.isLinethrough() }" ng-click="fabric.toggleLinethrough()" class='btn btn-small'> | |
<i class='fa fa-strikethrough'></i> | |
</button> | |
</div> | |
<br /> | |
<div class="row"> | |
<div class="btn-group col-sm-12"> | |
<a class="btn btn-default btn-block dropdown-toggle" data-toggle="dropdown" href="#"> | |
<span class='object-font-family-preview' style='font-family: "{{ fabric.selectedObject.fontFamily }}";'> | |
{{ fabric.selectedObject.fontFamily }} | |
</span> | |
<span class="caret"></span> | |
</a> | |
<ul class="dropdown-menu"> | |
<li ng-repeat='font in FabricConstants.fonts' ng-click='fabric.selectedObject.fontFamily = font.name' style='font-family: "{{ font.name }}";'> | |
<a>{{ font.name }}</a> | |
</li> | |
</ul> | |
</div> | |
</div> | |
</div> | |
<div ng-switch-when="image"> | |
<div class="input-prepend"> | |
<button ng-class="{ active: fabric.isTinted() }" ng-click="fabric.toggleTint()" class='btn'> | |
<i class='fa fa-tint'></i> | |
</button> | |
<input type="text" class='input-small' ng-model='fabric.selectedObject.tint' /> | |
</div> | |
</div> | |
</div> | |
<div class="row"> | |
<div class="col-sm-12"> | |
<input class='col-sm-12' title="Transparency" type='range' | |
min="0" | |
max="1" | |
step=".01" | |
ng-model="fabric.selectedObject.opacity" /> | |
</div> | |
</div> | |
<div class="row mbm"> | |
<div class="col-sm-12"> | |
<button class='btn btn-small btn-block' ng-class="{ active: fabric.getFlipX() }" ng-click="{ active: fabric.toggleFlipX() }"> | |
<i class='fa fa-exchange'></i> Flip | |
</button> | |
</div> | |
</div> | |
<div class="row" ng-hide="fabric.selectedObject.type == 'image'"> | |
<div class="col-sm-12"> | |
<input type="text" class='form-control' ng-model="fabric.selectedObject.fill" /> | |
</div> | |
</div> | |
<div class="row"> | |
<div class="btn-group col-xs-12 btn-group-vertical"> | |
<button ng-click='fabric.center()' class='btn btn-small btn-block'> | |
Center | |
</button> | |
<button ng-click='fabric.centerH()' class='btn btn-small btn-block'> | |
Center Horizontally | |
</button> | |
<button ng-click='fabric.centerV()' class='btn btn-small btn-block'> | |
Center Vertically | |
</button> | |
</div> | |
</div> | |
<br /> | |
<div class="row"> | |
<div class="btn-group col-xs-12 btn-group-vertical"> | |
<button ng-click='fabric.bringToFront(); fabric.setDirty(true)' class='btn btn-small btn-block'> | |
Bring to front | |
</button> | |
<button ng-click='fabric.bringForward(); fabric.setDirty(true)' class='btn btn-small btn-block'> | |
Bring forwards | |
</button> | |
<button ng-click='fabric.sendBackwards(); fabric.setDirty(true)' class='btn btn-small btn-block'> | |
Send backwards | |
</button> | |
<button ng-click='fabric.sendToBack(); fabric.setDirty(true)' class='btn btn-small btn-block'> | |
Send to back | |
</button> | |
</div> | |
</div> | |
<br /> | |
<button ng-click='fabric.toggleLockActiveObject(); fabric.setDirty(true)' ng-class="{ active: fabric.selectedObject.lockObject }" class='btn btn-small btn-block'> | |
Lock | |
</button> | |
</div> | |
</div> | |
<div class='col-xs-9'> | |
<div class='image-builder' parent-click="fabric.deactivateAll()"> | |
<div class='fabric-container'> | |
<canvas fabric='fabric'></canvas> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> |
angular.module('example', [ | |
'common.fabric', | |
'common.fabric.utilities', | |
'common.fabric.constants' | |
]) | |
.controller('ExampleCtrl', ['$scope', 'Fabric', 'FabricConstants', 'Keypress', function($scope, Fabric, FabricConstants, Keypress) { | |
$scope.fabric = {}; | |
$scope.FabricConstants = FabricConstants; | |
// | |
// Creating Canvas Objects | |
// ================================================================ | |
$scope.addShape = function(path) { | |
$scope.fabric.addShape('http://fabricjs.com/assets/15.svg'); | |
}; | |
$scope.addImage = function(image) { | |
$scope.fabric.addImage('http://stargate-sg1-solutions.com/blog/wp-content/uploads/2007/08/daniel-season-nine.jpg'); | |
}; | |
$scope.addImageUpload = function(data) { | |
var obj = angular.fromJson(data); | |
$scope.addImage(obj.filename); | |
}; | |
// | |
// Editing Canvas Size | |
// ================================================================ | |
$scope.selectCanvas = function() { | |
$scope.canvasCopy = { | |
width: $scope.fabric.canvasOriginalWidth, | |
height: $scope.fabric.canvasOriginalHeight | |
}; | |
}; | |
$scope.setCanvasSize = function() { | |
$scope.fabric.setCanvasSize($scope.canvasCopy.width, $scope.canvasCopy.height); | |
$scope.fabric.setDirty(true); | |
delete $scope.canvasCopy; | |
}; | |
// | |
// Init | |
// ================================================================ | |
$scope.init = function() { | |
$scope.fabric = new Fabric({ | |
JSONExportProperties: FabricConstants.JSONExportProperties, | |
textDefaults: FabricConstants.textDefaults, | |
shapeDefaults: FabricConstants.shapeDefaults, | |
json: {} | |
}); | |
}; | |
$scope.$on('canvas:created', $scope.init); | |
Keypress.onSave(function() { | |
$scope.updatePage(); | |
}); | |
}]); |
body { | |
padding: 10px; | |
} | |
.image-builder-container { | |
position: relative; | |
.image-builder { | |
background: #cacaca; | |
box-sizing: border-box; | |
border: 1px solid #cacaca; | |
min-height: 460px; | |
text-align: center; | |
position: relative; | |
overflow-x: scroll; | |
overflow-y: hidden; | |
width: auto; | |
.image-cover { | |
left: 0; | |
right: 0; | |
bottom: 0; | |
top: 0; | |
position: absolute; | |
z-index: 2; | |
} | |
.fabric-container { | |
background: white; | |
border-radius: 1px; | |
box-shadow: 0 1px 4px -1px rgba(0, 0, 0, .4); | |
display: inline-block; | |
margin: 50px; | |
position: relative; | |
vertical-align: middle; | |
z-index: 0; | |
} | |
} | |
.image-loading { | |
background: rgba(255, 255, 255, .4); | |
position: absolute; | |
top: 0; | |
bottom: 0; | |
left: 0; | |
right: 0; | |
z-index: 1; | |
.loading-indicator { | |
background: white; | |
background-image: url(/images/squareLoader.gif); | |
background-repeat: no-repeat; | |
background-position: center; | |
border-radius: 10px; | |
box-shadow: 0 1px 3px rgba(0, 0, 0, .2); | |
margin: 300px auto 0; | |
height: 160px; | |
width: 160px; | |
} | |
} | |
.object-controls-container { | |
position: relative; | |
.object-controls { | |
position: absolute; | |
z-index: 1; | |
background: white; | |
left: -250px; | |
top: 0px; | |
padding: 5px; | |
width: 240px; | |
textarea { | |
font-size: 12px; | |
} | |
} | |
} | |
} | |
.object-font-family-preview { | |
text-transform: capitalize; | |
} |