I have created a simple UI where I would like to change the font of my added text.
I use fabricjs.
Find below my minimum viable example:
<!DOCTYPE html>
<html lang="en">
<head>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor" crossorigin="anonymous" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.9.0/font/bootstrap-icons.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css">
<style>
html,
body {
height: 100%;
width: 100%;
padding: 0;
margin: 0;
}
body {
touch-action: none;
background-image: linear-gradient(to bottom left, rgb(214, 240, 201) 10%, rgba(255, 231, 191, 1) 80%);
-webkit-user-select: none;
-moz-user-select: -moz-none;
-ms-user-select: none;
user-select: none;
}
.input-group {
padding: 4px;
}
.canvas-container {
margin: 0 auto;
width: 100%;
overflow: hidden;
background: url(./transparent.png);
background-size: 15px 15px;
box-shadow: rgba(60, 64, 67, 0.3) 0px 1px 2px 0px, rgba(60, 64, 67, 0.15) 0px 1px 3px 1px;
}
.actived {
background: #fff9a8;
}
#sortable {
max-height: 200px;
overflow: scroll;
}
.list-group {
line-height: 35px;
}
.svg-icon {
width: 1em;
height: 1em;
}
.svg-icon path,
.svg-icon polygon,
.svg-icon rect {
fill: #4691f6;
}
.svg-icon circle {
stroke: #4691f6;
stroke-width: 1;
}
#bkboxXX {
background: url(transparent.png);
border: 1px solid rgba(0, 0, 0, 0.3);
display: inline-block;
height: 19px;
width: 20px;
}
.bk-btn {
background: url(/transparent.png);
}
.fl {
float: left;
}
.fr {
float: right;
}
.input-group-text {
background-color: #f4f4f4;
}
.list-group-item {
padding: 2px 10px;
;
cursor: pointer;
}
.list-group {
border-radius: 0;
text-align: left;
}
</style>
</head>
<body>
<div class="container-fluid h-100">
<div class="row h-100">
<div class="col-sm-3 border-end text-center h-100 overflow-scroll bg-light py-3">
<div class="form-floating w-100 mb-3 tour5">
<h6 class="mb-3">Add Element</h6>
<button class="btn btn-outline-primary" onclick="addText();" data-toggle="tooltip" data-placement="top" title="" data-bs-original-title="Add Text" aria-label="Add Text"><i
class="fa fa-font"></i></button>
</div>
<hr>
<div class="w-100 tour7 Xd-none Xd-sm-block">
<h6 class="mb-3">Elements</h6>
<ul class="list-group" id="sortable"></ul>
</div>
</div>
<div class="col-sm-6 my-auto py-4 overflow-hidden">
<div class="canvas-container" style="width: 329px; height: 329px; position: relative; user-select: none;"><canvas id="c" class="lower-canvas" width="329" height="329" style="position: absolute; width: 329px; height: 329px; left: 0px; top: 0px; touch-action: none; user-select: none;"></canvas>
</div>
</div>
<div class="col-sm-3 border-start py-3 h-100 overflow-scroll bg-light text-center">
<form class="form-inline pt-4" id="f" onsubmit="return false;" style="display: none;">
<div class="input-group input-group-sm mb-2 w-100 tour9 dropdown">
<span class="input-group-text" id="inputGroup-sizing-sm">Font Family</span>
<input id="font-search" class="font-search form-control form-control-sm dropdown-toggle show" type="text" value="" data-provide="typeahead" title="Search for a Google Font" name="fontFamily" autocomplete="off" data-bs-toggle="dropdown" aria-expanded="true"
hidden>
<select id="allFonts" class="form-select" aria-label="Select Font" onchange="runFont(this.value)">
<option selected>Open this select menu</option>
</select>
</div>
</form>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-pprn3073KE6tl6bjs2QrFaJGz5/SUsLqktiwsUTF55Jfv3qYSDhgCecCxMW52nD2" crossorigin="anonymous"></script>
<script src="https://code.jquery.com/jquery-3.6.0.js"></script>
<script src="https://code.jquery.com/ui/1.13.1/jquery-ui.js"></script>
<script src="https://unpkg.com/fabric@5.2.1/dist/fabric.min.js"></script>
<script src="https://rawcdn.githack.com/lyzerk/fabric-history/8c223cbdc8305307b4a8f8710f97da54d9146ffa/src/index.js"></script>
<script src="https://rawgit.com/fabricjs/fabric.js/master/lib/centering_guidelines.js"></script>
<script src="https://rawgit.com/fabricjs/fabric.js/master/lib/aligning_guidelines.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/webfont/1.5.10/webfont.js"></script>
<script>
const rgba2hex = (rgba) => `#${rgba.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+\.{0,1}\d*))?\)$/).slice(1).map((n, i) => (i === 3 ? Math.round(parseFloat(n) * 255) : parseFloat(n)).toString(16).padStart(2, '0').replace('NaN', '')).join('')}`
function cn2h(cn) {
if (!cn) {
return 'transparent';
}
d = document.createElement("span");
d.style.display = "none";
d.style.color = cn
document.body.appendChild(d)
return rgba2hex(window.getComputedStyle(d).color);
}
</script>
<script>
var canvas;
var apiUrl;
var startedLoadingFamilies = false;
var to;
var scale;
fabric.Text.prototype.set({
_getNonTransformedDimensions() { // Object dimensions
return new fabric.Point(this.width, this.height).scalarAdd(this.padding);
},
_calculateCurrentDimensions() { // Controls dimensions
return fabric.util.transformPoint(this._getTransformedDimensions(), this.getViewportTransform(), true);
}
});
function fitCanvas(w, h) {
var scaleW = (document.querySelectorAll(".col-sm-6")[0].offsetWidth - 100) / w;
var scaleH = (document.querySelectorAll("body")[0].offsetHeight - 100) / h;
scale = Math.min(scaleH, scaleW);
if (scale > 1) {
scale = 1;
}
canvas.setZoom(scale);
canvas.setWidth(w * scale);
canvas.setHeight(h * scale);
initAligningGuidelines(canvas);
initCenteringGuidelines(canvas);
canvas.renderAll();
console.log("finished!!");
}
function runFont(value) {
if (typeof canvas.getActiveObject() !== "undefined") {
canvas.getActiveObject().fontFamily = value.replace(/\s/g, '+');
fabric.util.clearFabricFontCache();
canvas.renderAll();
}
}
(function() {
canvas = new fabric.Canvas('c', {
allowTouchScrolling: true
});
WebFont.load({
google: {
families: ["Inter:200"]
},
active: function() {
canvas.loadFromJSON({
"height": "500",
"width": "500",
"edit": "yes",
"objects": [],
"backgroundImage": {
"crossOrigin": "anonymous"
}
}, function() {
// document.querySelectorAll('.bruzu-loader')[0].style.display = 'none';
fitCanvas(500, 500);
})
}
});
canvas.on('before:render', function(opt) {
canvas.getObjects().map(function(o, i) {
if (o.type.toLowerCase() == "textbox") {
var maxH = (o.maxHeight) ? (o.maxHeight) : (canvas.height);
o.set({
'maxHeight': maxH
});
if (o.fontSize > 0 && (maxH > o.fontSize)) {
while (o.height > maxH) {
o.set({
'fontSize': o.fontSize - 1
});
}
}
}
if (o.circleFrame) {
o.set({
clipPath: new fabric.Circle({
radius: o.width / 2,
originX: 'center',
originY: 'center'
})
});
}
if ((o.rx || o.ry) && o.type == "image") {
let rx = (o.rx || 0);
let ry = (o.ry || 0);
o.set({
clipPath: new fabric.Rect({
rx: rx,
ry: ry,
height: o.height,
width: o.width,
originX: 'center',
originY: 'center'
})
});
}
});
});
})();
</script>
<script>
fabric.Object.prototype.objectCaching = false;
function setColor(clr, what = 'fill') {
//console.log("set color",what, clr);
canvas.getActiveObject().set(what, clr);
canvas.renderAll();
}
function loadFont() {
$.getJSON("https://www.googleapis.com/webfonts/v1/webfonts?key=AIzaSyAu0Djqnn98eyXTWHI-9OaiESODz2tfKBI", function(fonts) {
for (var i = 0; i < fonts.items.length; i++) {
$('#allFonts')
.append($("<option></option>")
.attr("value", fonts.items[i].family)
.text(fonts.items[i].family));
}
});
fabric.util.clearFabricFontCache();
canvas.renderAll();
}
(function() {
loadFont()
fabric.Canvas.prototype.getItemByAttr = function(attr, name) {
var object = null,
objects = this.getObjects();
for (var i = 0, len = this.size(); i < len; i++) {
if (objects[i][attr] && objects[i][attr] == name) {
object = objects[i];
break;
}
}
return object;
};
$("form#f").hide();
var activeObject;
$("#f input, #f select").on("input", function() {
if (["height", "width", "top", "left", "strokeWidth", "charSpacing"].includes(this.name)) {
canvas.getActiveObject().set(this.name, parseFloat(this.value)).setCoords();
} else {
canvas.getActiveObject().set(this.name, this.value).setCoords();
}
canvas.renderAll();
});
canvas.preserveObjectStacking = true;
fabric.Object.prototype.toObject = (function(toObject) {
return function() {
return fabric.util.object.extend(toObject.call(this), {
name: this.name,
text: this.text,
textAlign: this.textAlign,
fontSize: this.fontSize,
charSpacing: this.charSpacing,
lineHeight: this.lineHeight,
fontWeight: this.fontWeight,
fontFamily: this.fontFamily,
fontStyle: this.fontStyle,
textBackgroundColor: this.textBackgroundColor,
originX: this.originX,
originY: this.originY,
maxHeight: this.maxHeight,
height: this.height,
width: this.width,
radius: this.radius,
rx: this.rx,
ry: this.ry,
stroke: this.stroke,
padding: this.padding,
circleFrame: this.circleFrame
});
};
})(fabric.Object.prototype.toObject);
canvas.on('object:scaling', function(e) {
if (e.target.toObject().type != "image" && e.target.toObject().type != "circle") {
//console.log(e.target.toObject().type);
e.target.set({
width: e.target.width * e.target.scaleX,
height: e.target.height * e.target.scaleY,
scaleX: 1,
scaleY: 1
})
}
});
canvas.on('object:modified', function(opt) {
document.body.style.cursor = 'progress';
});
canvas.on("after:render", function() {
$("#sortable").empty();
canvas.includeDefaultValues = false;
canvas.toObject().objects.forEach(function(layer, id) {
if (typeof layer.name !== 'undefined') {
canvas.getItemByAttr(`name`, layer.name).set({
"name": layer.name.replaceAll(' ', '_')
})
var actived = '';
if (canvas.getActiveObject()) {
actived = (canvas.getActiveObject().name == layer.name) ? " actived" : "";
}
}
});
document.body.style.cursor = 'default'
});
canvas.on("selection:created", function(obj) {
if ("image" == obj.selected[0].type) {
canvas.getActiveObject().setControlsVisibility({
mb: false,
ml: false,
mt: false,
mr: false
});
}
$("form#f input[type!='hidden'], #f select").parent().hide();
$("form#f").hide();
canvas.renderAll();
showForm();
});
canvas.on("selection:updated", function(obj) {
if ("image" == obj.selected[0].type) {
canvas.getActiveObject().setControlsVisibility({
mb: false,
ml: false,
mt: false,
mr: false
});
}
canvas.renderAll();
$("form#f").hide();
$("form#f input[type!='hidden'] , #f select, #f textarea").parent().hide();
showForm();
});
canvas.on("selection:cleared", function() {
canvas.renderAll();
$("form#f").hide();
$("form#f input[type!='hidden'], #f select, #f textarea").parent().hide();
});
canvas.hoverCursor = 'default';
canvas.on('mouse:over', function(e) {
if (e.target) {
e.target._renderControls(canvas.contextTop, {
hasControls: false
})
}
});
canvas.on('mouse:out', function(e) {
canvas.clearContext(canvas.contextTop);
});
canvas.on('mouse:down', function(e) {
canvas.clearContext(canvas.contextTop);
});
function showForm() {
$("form#f").show();
activeObject = canvas.getActiveObject();
var v;
for (i in activeObject) {
v = activeObject[i];
if (typeof v != "undefined") {
//$("textarea[name='" + i + "']").val(v).parent().show();
$("input[name='" + i + "']").val(v).parent().show();
$("select[name='" + i + "']").val(v).parent().show();
}
}
}
addText = function() {
var text = new fabric.Textbox("Edit this Text", {
name: genNextName(),
left: canvas.getWidth() / canvas.getZoom() / 2,
top: canvas.getHeight() / canvas.getZoom() / 2,
width: (canvas.getWidth() / canvas.getZoom()) / 2,
fill: "#000000",
originX: "center",
originY: "center",
fontFamily: "Inter",
fontWeight: 400,
fontSize: 60,
padding: 20
});
text.setControlsVisibility({
mt: false, // middle top
mb: false, // midle bottom
ml: true, // middle left
mr: true, // middle right
tl: true, //top left
tr: true, //top right
bl: true, //bottom left
br: true //bottom right
});
canvas.add(text);
canvas.setActiveObject(text);
canvas.renderAll();
};
})();
function hide_mh_box() {
canvas.remove(canvas.getItemByAttr("isBB", true));
}
function show_mh_box() {
var h = parseInt($("[name=maxHeight]").val());
var n = new fabric.Rect({
top: canvas.getActiveObject().get('top'),
left: canvas.getActiveObject().get('left'),
width: canvas.getActiveObject().get('width'),
height: h,
originX: canvas.getActiveObject().get('originX'),
originY: canvas.getActiveObject().get('originY'),
angle: canvas.getActiveObject().get('angle'),
opacity: 1,
strokeWidth: 2,
stroke: "#FF00FF",
fill: "rgba(0,0,0,0)",
evented: !1,
isBB: true
});
canvas.add(n);
canvas.renderAll();
}
var visited = [];
$(document).ready(function() {
$("input[type=color]").on("input", function() {
$(this).parent().parent().find('input').first().val(this.value);
});
$("textarea, input").attr("autocomplete", "off");
if (window !== window.parent) {
const url = new URL(document.referrer);
$(".isIframe").removeClass('d-none');
$(".notIframe").hide();
}
});
</script>
<script>
function genNextName() {
canvas.renderAll();
var total = canvas.getObjects().length;
//console.log(total);
return String.fromCharCode(65 + total).toLowerCase()
}
function updateImg() {
const file = document.getElementById("imageUploader").files[0]
let src = null
// Get the real path of the picture file
// Due to browser security policy , Now you need to do this
if (window.createObjcectURL != undefined) {
src = window.createOjcectURL(file);
} else if (window.URL != undefined) {
src = window.URL.createObjectURL(file);
} else if (window.webkitURL != undefined) {
src = window.webkitURL.createObjectURL(file);
}
let ao = canvas.getActiveObject();
fabric.Image.fromURL(src, function(img) {
//console.log(img._element);
if (img.width) {
var imgElm = img.set({
name: ao.name,
originX: ao.originX,
originY: ao.originY,
left: ao.left,
top: ao.top,
scaleX: ao.scaleX,
scaleY: ao.scaleY,
src: src,
lockUniScaling: !0
});
ao.setElement(imgElm.getElement());
canvas.renderAll();
}
});
}
// customized control
fabric.Object.prototype.transparentCorners = false;
fabric.Object.prototype.borderColor = '#5cdce4';
fabric.Object.prototype.cornerColor = 'white';
fabric.Object.prototype.cornerStrokeColor = 'blue';
fabric.Object.prototype.cornerStyle = 'circle';
fabric.Object.prototype.cornerSize = 10;
fabric.Object.prototype.borderScaleFactor = 2;
</script>
</body>
</html>
When trying to change the font I can change it once, but it won't change again.
Any suggestions what I am doing wrong?
I appreciate your replies!
i could suggest you using https://fontfaceobserver.com/ script
you also need load fonts, for my own projects i use jquery.fontpicker.js who handle nicely the job
so, if you mix this two piece of code, load fonts not loaded and wait the effective load using FontFaceObserver it will work :
let fontsLoaded = {} ;
function runFont(font) {
if(!fontsLoaded[font]){
fontsLoaded[font] = true ;
const url = 'https://fonts.googleapis.com/css?family=' + font.replace(/ /g,'+') + ':' + '&display=swap';
console.log('Loading Google font ' + font + ' from ' + url);
$('head').append($('<link>', {href:url, rel:'stylesheet', type:'text/css'}));
runFont( font );
return ;
}
var f = new FontFaceObserver( font );
f.load().then(function () {
console.log( "apply", font )
canvas.getActiveObject().set("fontFamily", font );
}, function () {
console.log('Font',font,'is not available');
});
}
edit > minimal implementation of the jquery font picker with fabricjs :
let $textStyleIcons = $();
let $fontPicker = $("<input/>").addClass("fonts");
$textStyleIcons.on("update", function(){
let o = canvas.getActiveObject();
if(!o) return;
$fontPicker.val( o.fontFamily ).trigger("change");
});
$textStyleIcons = $textStyleIcons.add( $fontPicker );
let $textStyleIconsUpdate = $textStyleIcons.triggerHandler.bind($textStyleIcons, "update") ;
function styleBlocsUpdate(){
let o = canvas.getActiveObject();
const type = o ? o.get("type") : null ;
switch( type ){
case "i-text":
$textStyleIconsUpdate();
setFontPickerText( canvas.getActiveObject().get("text") );
break;
}
}
$body.append( $textStyleIcons );
function setFont(font) {
let f = font.split(':')[0];
canvas.getActiveObject().set("fontFamily", f);
styleBlocsUpdate();
}
const $fp = $fontPicker
.fontpicker({
lang:'fr',
variants:false
})
.on('change', function( ) {
let v = this.value ;
let o = canvas.getActiveObject();
if( !o || o.get("type") !== "i-text" ) return ;
if( o.get("fontFamily") === v ) return ;
if( typeof FontFaceObserver !== 'function' ){
setFont(v);
return;
}
var font = new FontFaceObserver( v );
font.load().then(function () {
setFont(v);
}, function () {
console.log('Font',v,'is not available');
});
});
function setFontPickerText(t){
if( !t || !t.length ) return ;
$fp.$sample.text(t);
}
canvas.on("selection:created",styleBlocsUpdate);
canvas.on("selection:updated",styleBlocsUpdate);
canvas.on("selection:cleared",styleBlocsUpdate);
canvas.on("text:selection:changed",$textStyleIconsUpdate);
canvas.on("text:editing:entered",$textStyleIconsUpdate);
canvas.on("text:editing:exited",$textStyleIconsUpdate);
styleBlocsUpdate();