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();
You need to update the active object's font family after a new font was selected and loaded:
Just append this code at the end of your current script.
// load selected font and apply it to active fabric element
let allFontsSelect = document.getElementById('allFonts');
allFontsSelect.addEventListener('change', function(e) {
let currentFont = e.currentTarget.value;
WebFont.load({
google: {
families: [currentFont]
},
active: function() {
if (typeof canvas.getActiveObject() !== "undefined") {
canvas.getActiveObject().fontFamily = currentFont;
fabric.util.clearFabricFontCache();
canvas.renderAll();
}
}
});
});
It's similar to your runFont(value) function but resolves some issues:
canvas.getActiveObject().fontFamily = value.replace(/\s/g, '+');
Would result in invalid font family names like 'Source+Sans+Pro'.
Working example:
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();
}
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'
})
});
}
});
});
})();
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();
}
});
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;
// load selected font and apply it to active fabric element
let allFontsSelect = document.getElementById('allFonts');
allFontsSelect.addEventListener('change', function(e) {
let currentFont = e.currentTarget.value;
//console.log(currentFont)
WebFont.load({
google: {
families: [currentFont]
},
active: function() {
if (typeof canvas.getActiveObject() !== "undefined") {
canvas.getActiveObject().fontFamily = currentFont;
canvas.renderAll();
}
}
});
});
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;
}
<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">
<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>
Here is my code. https://www.jsfiddle.net/tpov743w/
I'm trying to accomplish multiple circular progress bars. The idea is to have them work dynamically, by adding additional progressCircle_# objects with different percentage values whenever needed. As you can see the progress bars load the data and executes the animation, but when I inspect the element in the browser I notice countless "ReferenceError: start is not defined". I need help in overcoming this problem. Thanks for any suggestions.
var progressCircle_1 = {
procent: 89,
startFrom: 0,
incrementBy: 1,
canvasId: 'canvas',
procentId: 'procent',
funct: function() {
var start = setInterval(function() {
draw.call(progressCircle_1)
}, 50);
}
}
var progressCircle_2 = {
procent: 59,
startFrom: 0,
incrementBy: 1,
canvasId: 'canvas1',
procentId: 'procent1',
funct: function() {
var start = setInterval(function() {
draw.call(progressCircle_2)
}, 50);
}
}
progressCircle_1.funct();
progressCircle_2.funct();
function draw() {
(this.startFrom < this.procent) ? this.startFrom++: clearInterval(start);
var getCanvas = document.getElementById(this.canvasId).getContext('2d');
var getNumber = document.getElementById(this.procentId).innerHTML = this.incrementBy++;
getCanvas.beginPath();
getCanvas.arc(250, 250, 100, 0, 0.06283185307179587 * this.startFrom);
getCanvas.lineWidth = '15';
getCanvas.strokeStyle = "white";
getCanvas.lineCap = "round";
getCanvas.stroke();
};
#canvas {
border: 1px solid red;
transform: rotate(0deg);
}
#procent {
font-size: 65px;
color: white;
position: absolute;
top: 160px;
left: 200px;
}
#procent::after {
content: '%';
}
.container {
background-color: lightblue;
height: 500px;
width: 500px;
}
#canvas1 {
border: 1px solid red;
transform: rotate(0deg);
}
#procent1 {
font-size: 65px;
color: black;
position: absolute;
top: 660px;
left: 200px;
}
#procent1::after {
content: '%';
}
.container1 {
background-color: lightgrey;
height: 500px;
width: 500px;
}
#canvasProgressBar {
position: relative;
top: 100px;
left: 10px;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title></title>
<meta name="description" content="Interaktywny poradnik szybkiego startu dla Brackets.">
<link rel="stylesheet" href="main.css">
</head>
<body>
<div class="container">
<canvas id="canvas" width="500" height="500">
</div>
<p id="procent"></p>
<div class="container1">
<canvas id="canvas1" width="500" height="500">
</div>
<p id="procent1"></p>
<script src="main.js"></script>
</body>
</html>
Just add var start at the top instead of inside the funct function
Complete JS Code:
var start
var progressCircle_1 = {
procent:89,
startFrom:0,
incrementBy:1,
canvasId:'canvas',
procentId:'procent',
funct: function(){
start = setInterval(function(){draw.call(progressCircle_1)},50);
}
}
var progressCircle_2 = {
procent:59,
startFrom:0,
incrementBy:1,
canvasId:'canvas1',
procentId:'procent1',
funct: function(){
start = setInterval(function(){draw.call(progressCircle_2)},50);
}
}
progressCircle_1.funct();
progressCircle_2.funct();
function draw(){
(this.startFrom<this.procent)?this.startFrom++:clearInterval(start);
var getCanvas = document.getElementById(this.canvasId).getContext('2d');
var getNumber = document.getElementById(this.procentId).innerHTML=this.incrementBy++;
getCanvas.beginPath();
getCanvas.arc(250,250,100,0,0.06283185307179587*this.startFrom);
getCanvas.lineWidth='15';
getCanvas.strokeStyle="white";
getCanvas.lineCap="round";
getCanvas.stroke();
};
You need to store the interval id as well: https://jsfiddle.net/x8Lypm2j/
var progressCircle_1 = {
procent:89,
startFrom:0,
incrementBy:1,
canvasId:'canvas',
procentId:'procent',
intervalId: 0,
funct: function(){
this.intervalId = setInterval(function(){draw.call(progressCircle_1)},50);
}
}
var progressCircle_2 = {
procent:59,
startFrom:0,
incrementBy:1,
canvasId:'canvas1',
procentId:'procent1',
intervalId: 0,
funct: function(){
this.intervalId = setInterval(function(){draw.call(progressCircle_2)},50);
}
}
progressCircle_1.funct();
progressCircle_2.funct();
function draw(){
(this.startFrom<this.procent)?this.startFrom++:clearInterval(this.intervalId);
var getCanvas = document.getElementById(this.canvasId).getContext('2d');
var getNumber = document.getElementById(this.procentId).innerHTML=this.incrementBy++;
getCanvas.beginPath();
getCanvas.arc(250,250,100,0,0.06283185307179587*this.startFrom);
getCanvas.lineWidth='15';
getCanvas.strokeStyle="white";
getCanvas.lineCap="round";
getCanvas.stroke();
};
Pass start to the calling function, you specify that the function's this is progressCircle_1 but it doesn't have a start attribute, so you need to pass the timeoutId(start) to the calling function.
var progressCircle_1 = {
procent:89,
startFrom:0,
incrementBy:1,
canvasId:'canvas',
procentId:'procent',
funct: function(){
var start = setInterval(function(){draw.call(progressCircle_1,start)},50);
}
}
var progressCircle_2 = {
procent:59,
startFrom:0,
incrementBy:1,
canvasId:'canvas1',
procentId:'procent1',
funct: function(){
var start = setInterval(function(){draw.call(progressCircle_2,start)},50);
}
}
progressCircle_1.funct();
progressCircle_2.funct();
function draw(start){
(this.startFrom<this.procent)?this.startFrom++:clearInterval(start);
var getCanvas = document.getElementById(this.canvasId).getContext('2d');
var getNumber = document.getElementById(this.procentId).innerHTML=this.incrementBy++;
getCanvas.beginPath();
getCanvas.arc(250,250,100,0,0.06283185307179587*this.startFrom);
getCanvas.lineWidth='15';
getCanvas.strokeStyle="white";
getCanvas.lineCap="round";
getCanvas.stroke();
};
I have a set of letters. A word is built form these letters. I loop through the data ids to build the word.
However I cannot seem to group them together.
If I manually select the letters and try
canvas.getActiveObject().toGroup();
canvas.requestRenderAll();
It will group them together.
But I need to do this programatically and when I add
canvas.add(img).setActiveObject();
it will only select the last object added to the canvas.
Here is my function to build the word
function buildImage(i) {
var wordo = jQuery('*[data-id="' + wordArray[i] + '"]').attr('src');
fabric.Image.fromURL(wordo, function(img, letterWidth) {
// fabric.Image.fromURL(tiles[i], function (img) {
img.scale(scaleO).set({
left: offset[i],
top: wordPositionY,
selectable: true,
cornerStyle: 'circle',
cornerColor: '#f6ff00',
cornerStrokeColor: '#f6ff00',
transparentCorners: false,
borderColor: '#f9f60b',
cornerSize: 22,
hasRotatingPoint: true,
id: 'letter'
});
img.setControlsVisibility({
mt: false,
mr: false,
mb: false,
ml: false
});
canvas.add(img);
});
}
and here is the loop that calls it
for (var i = 0; i < letters; i++) {
buildImage(i);
}
I want to add these letters to the canvas as a group which can be un grouped if needed as well. Is there a way to do this?
Here is my JSfiddle
As fabric.Image.fromURL is asynchronous, so you have to wait till all the images loaded, after loading all images create a group of all images.
$(document).ready(function() {
canvasWidth = 448;
canvasHeight = 390;
canvas = new fabric.Canvas('c', {
width: canvasWidth,
height: canvasHeight,
containerClassName: 'c',
selection: true,
preserveObjectStacking: true,
});
var word = 'abc';
let letters = word.length;
let imageLoaded = 0;
var letterWidth = canvas.width / letters;
var numArr = [];
var wordArray = word.split("");
var offset = [
0,
letterWidth,
letterWidth * 2,
letterWidth * 3,
letterWidth * 4,
letterWidth * 5,
letterWidth * 6,
letterWidth * 7,
letterWidth * 8,
letterWidth * 9,
letterWidth * 10,
letterWidth * 11,
letterWidth * 12,
letterWidth * 13,
];
for (var i = 0; i < letters; i++) {
buildImage(i);
}
let imageData = [];
var scaleO = letterWidth / 448;
var wordPositionY = (canvasHeight - letterWidth) / 2
// creates a new scope for the passed in value of i when called:
function buildImage(i) {
var wordo = jQuery('*[data-id="' + wordArray[i] + '"]').attr('src');
fabric.Image.fromURL(wordo, function(img, letterWidth) {
// fabric.Image.fromURL(tiles[i], function (img) {
img.scale(scaleO).set({
left: offset[i],
top: wordPositionY,
id: 'letter'
});
img.setControlsVisibility({
mt: false,
mr: false,
mb: false,
ml: false
});
imageData.push(img);
if (letters == ++imageLoaded) {
setGroup();
}
});
}
function setGroup() {
let group = new fabric.Group(imageData, {
top: 100,
selectable: true,
cornerStyle: 'circle',
cornerColor: '#f6ff00',
cornerStrokeColor: '#f6ff00',
transparentCorners: false,
borderColor: '#f9f60b',
cornerSize: 22,
})
canvas.add(group)
imageData = [];
}
});
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#banner-message {
background: #fff;
border-radius: 4px;
padding: 20px;
font-size: 25px;
text-align: center;
transition: all 0.2s;
margin: 0 auto;
width: 450px;
}
button {
background: #0084ff;
border: none;
border-radius: 5px;
padding: 8px 14px;
font-size: 15px;
color: #fff;
}
#banner-message.alt {
background: #0084ff;
color: #fff;
margin-top: 40px;
}
#banner-message.alt button {
background: #fff;
color: #000;
}
#c {
background: #e6e6e6;
}
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="banner-message">
<p>My Canvas</p>
<canvas id="c"></canvas>
</div>
<div class="letters">
<img src="https://i.imgur.com/7lXiKkD.png" height="32" id="theme_img" data-id="a" alt="">
<img src="https://i.imgur.com/gMqYdMB.png" height="32" id="theme_img" data-id="b" alt="">
<img src="https://i.imgur.com/1CMBCqv.png" height="32" id="theme_img" data-id="c" alt="">
</div>
I need to add action onclick will change it colour (to gray). And then it will stay gray until it will be clicked to another tab.
I want to show tab in selected state (by having gray colour). If you click on another tab it color changes to gray and previously click tab color change to white.
This is part of my code:
<link rel="stylesheet" href="demo.css" type="text/css" media="screen">
<link rel="stylesheet" href="demo-print.css" type="text/css" media="print">
<style type="text/css" media="screen">
#canvas {
height: 300px;
left: 50%;
margin: -150px 0 0 -300px;
position: absolute;
top: 50%;
width: 600px;
}
#paper {
height: 300px;
left: 0;
position: absolute;
top: 0;
width: 300px;
}
#nsw, #vic, #wa, #sa, #nt, #qld, #tas {
display: none;
height: 400px;
overflow: auto;
position: absolute;
right: 0;
top: 0;
width: 300px;
}
h2 {
text-align: center;
}
</style>
<script src="raphael.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript" charset="utf-8">
window.onload = function () {
var R = Raphael("paper", 700, 700);
var attr = {
fill: "#333",
stroke: "#666",
"stroke-width": 1,
"stroke-linejoin": "round"
};
var aus = {};
var outsideRectX1=30, outsideRectY1=30,outsideRectX2=220, outsideRectY2=480, outsideRectR=10;
aus.nsw = R.rect(outsideRectX1+40, outsideRectY1+70, 120, 40,0);
aus.vic = R.rect(outsideRectX1+160, outsideRectY1+70, 120, 40,0);
aus.wa = R.rect(outsideRectX1+280, outsideRectY1+70, 120, 40,0);
aus.sa = R.rect(outsideRectX1+400, outsideRectY1+70, 120, 40,0);
var text3=R.text(outsideRectX1+75, outsideRectY1+85,"tab1").attr({fill: '#000000', 'font-family':'Vardana', 'font-size':'14px', 'font-weight': 'bold'});
var text4=R.text(outsideRectX1+195, outsideRectY1+85,"tab2").attr({fill: '#000000', 'font-family':'Vardana', 'font-size':'14px', 'font-weight': 'bold'});
var text5=R.text(outsideRectX1+315, outsideRectY1+85,"tab3").attr({fill: '#000000', 'font-family':'Vardana', 'font-size':'14px', 'font-weight': 'bold'});
var text6=R.text(outsideRectX1+435, outsideRectY1+85,"tab4").attr({fill: '#000000', 'font-family':'Vardana', 'font-size':'14px', 'font-weight': 'bold'});
var current = null;
for (var state in aus) {
aus[state].color = Raphael.getColor();
(function (st, state) {
st[0].style.cursor = "pointer";
st[0].click = function () {
current && aus[current].animate({fill: "#333", stroke: "#666"}, 500) && (document.getElementById(current).style.display = "");
st.animate({fill: st.color, stroke: "#ccc"}, 500);
st.toFront();
R.safari();
document.getElementById(state).style.display = "block";
current = state;
};
})(aus[state], state);
}
};
</script>
</head>
<body>
<div id="canvas">
<div id="paper"></div>
<div id="nsw"> </div>
<div id="vic"> </div>
<div id="wa"></div>
<div id="sa">
</div>
</div>
</body>
</html>
Here is a distilled version of your code, I have removed the irrelevant parts to simplify the work:
var R = Raphael("paper", 700, 700);
var aus = {};
aus.nsw = R.set()
aus.vic = R.set()
aus.wa = R.set()
aus.sa = R.set()
var boxattrs = {'cursor': 'pointer', 'fill' : "#fff"}
var attrs = {fill: '#000000', 'font-family':'Verdana', 'font-size':'14px', 'font-weight': 'bold', 'cursor': 'pointer'}
var outsideRectX1=30.5, outsideRectY1=30.5,outsideRectX2=220.5, outsideRectY2=480.5, outsideRectR=10.5;
var i = 0;
for (state in aus) {
aus[state].push(R.rect((outsideRectX1+90)*i+80, outsideRectY1+70, 120, 40,0).attr(boxattrs));
aus[state].push(R.text((outsideRectX1+90)*i+115, outsideRectY1+85,"tab" + (i+1)).attr(attrs));
aus[state].click(highlight)
i++;
}
function highlight() {
for (state in aus) {
if(aus[state][0].attr("fill") == "#CCCCCC") {
aus[state][0].animate({fill: "#FFFFFF", "stroke": "#000"}, 300)
}
}
node = (this.type == "rect") ? this : this.prev
node.animate({fill: "#CCCCCC", "stroke": "#CCC"}, 300)
}
You can view it live here http://jsfiddle.net/xYfXE/