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>
Related
I'm attempting to make these balls move along the border of this parent div. The parent div is a pill/discorectangle. I'm sure there must be a way to do this using some trig or equation of a circle. So the straight sides are 200px long and the radius of each semi circle is 100px. So from 0-100px and 300-400px the top style needs to be calculated for each ball. Has anyone done anything similar or have any advice?
HTML:
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="style.css">
</head>
<button id="start_stop">Orbit</button>
<div id="oval">
<div id="ball0" class="ball"></div>
<div id="ball1" class="ball"></div>
<div id="ball2" class="ball"></div>
<div id="ball3" class="ball"></div>
</div>
<script src="orbit.js"></script>
</html>
CSS:
#oval {
position: absolute;
margin-top: 200px;
height: 200px;
width: 400px;
border: 3px solid black;
border-radius: 400px/400px;
right: 50%;
transform: translate(50%, -50%);
}
.ball {
height: 30px;
width: 30px;
border-radius: 30px;
position: absolute;
}
#ball0 {
left:270px;
top:185px;
/*right: 100px;
bottom: -15;*/
background-color: blue;
}
#ball1 {
left:100px;
/* bottom:-15px; */
top: 185px;
background-color: green;
}
#ball2 {
top:-15px;
/* right: 100px; */
left: 270px;
background-color: yellow;
}
#ball3 {
top:-15px;
left: 100px;
background-color: red;
}
JS:
var oval = document.getElementById('oval');
var balls = document.getElementsByClassName('ball');
var run_animation = false;
var req;
document.getElementById("start_stop").addEventListener("click", toggle);
function toggle() {
run_animation = !run_animation
if(run_animation) {
req = window.requestAnimationFrame(orbit);
}
}
console.log(balls.length);
var max_x = oval.clientWidth; // - 100;
var min_x = 0;
var step = 0;
var map = {
"ball0": {"top": 185, "left": 270, forward: true},
"ball1": {"top": 185, "left": 100, forward: true},
"ball2": {"top": -15, "left": 270, forward: false},
"ball3": {"top": -15, "left": 100, forward: false}
}
function get_y(x){
//some math here?
// return 100 * Math.sin(x);
// return Math.sqrt(100**2 + x**2);
}
function orbit() {
if (run_animation){
for(var i=0; i<balls.length; i++){
var curr_left = map["ball"+i].left;
if (curr_left >= max_x) map["ball"+i].forward = false;
if (curr_left <= min_x ) map["ball"+i].forward = true;
if(map["ball"+i].forward){
map["ball"+i].left += 3;
}
else {
forward = false
map["ball"+i].left -= 3;
}
//left edge - curve around semicircle
if(map["ball"+i].left <= 100) {
// map["ball"+i].top = -1 * get_y(map["ball"+i].left);
}
//right edge - curve around semicircle
if(map["ball"+i].left >= 300) {
// map["ball"+i].top = -1 * get_y(map["ball"+i].left);
}
balls[i].style.left = map["ball"+i].left + 'px';
balls[i].style.top = map["ball"+i].top + 'px';
}
req = window.requestAnimationFrame(orbit);
}
else {
console.log("cancel");
window.cancelAnimationFrame(req);
}
}
/* orbit(); */
req = window.requestAnimationFrame(orbit);
https://jsfiddle.net/gzjfxtbu/2/
Well I did it. Not sure if this is the best way to do this or not. I'd still like to figure out any other methods to accomplish this. Eventually I was going to turn the balls into actual divs containing info and images. So I'm not sure if the SVG route is the best?
Hopefully this can help someone.
JS:
var oval = document.getElementById('oval');
var balls = document.getElementsByClassName('ball');
var run_animation = false;
var req;
document.getElementById("start_stop").addEventListener("click", toggle);
function toggle() {
run_animation = !run_animation
if(run_animation) {
req = window.requestAnimationFrame(orbit);
}
}
console.log(balls.length);
var max_x = oval.clientWidth;
var min_x = 0;
var step = 0;
var map = {
"ball0": {"top": 185, "left": 270, forward: false},
"ball1": {"top": 185, "left": 100, forward: false},
"ball2": {"top": -15, "left": 270, forward: true},
"ball3": {"top": -15, "left": 100, forward: true}
}
function get_y(x){
//some math here?
// return 100 * Math.sin(x);
return 1 * (Math.sqrt(100**2 - (100-x)**2));
}
function get_y2(x) {
return 1 * (Math.sqrt(100**2 - (300-x)**2));
}
function orbit() {
if (run_animation){
for(var i=0; i<balls.length; i++){
var curr_left = map["ball"+i].left;
if (curr_left >= max_x) map["ball"+i].forward = false;
if (curr_left <= min_x ) map["ball"+i].forward = true;
if(map["ball"+i].forward){
map["ball"+i].left += 3;
}
else {
map["ball"+i].left -= 3;
}
//left edge - curve around semicircle
if(map["ball"+i].left <= 85 && !map["ball"+i].forward ) {
map["ball"+i].top = 1*get_y(map["ball"+i].left) + 85;
}
else if(map["ball"+i].left <= 85 && map["ball"+i].forward ) {
map["ball"+i].top = -1*get_y(map["ball"+i].left) + 85;
}
//right edge - curve around semicircle
if(map["ball"+i].left >= 315 && map["ball"+i].forward) {
map["ball"+i].top = -1*get_y2(map["ball"+i].left) + 85;
}
else if(map["ball"+i].left >= 315 && !map["ball"+i].forward) {
map["ball"+i].top = get_y2(map["ball"+i].left) + 85;
}
balls[i].style.left = map["ball"+i].left + 'px';
balls[i].style.top = map["ball"+i].top + 'px';
}
req = window.requestAnimationFrame(orbit);
}
else {
console.log("cancel");
window.cancelAnimationFrame(req);
}
}
/* orbit(); */
req = window.requestAnimationFrame(orbit);
HTML:
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="style.css">
</head>
<button id="start_stop">Orbit</button>
<div id="oval">
<div id="ball0" class="ball">0</div>
<div id="ball1" class="ball">1</div>
<div id="ball2" class="ball">2</div>
<div id="ball3" class="ball">3</div>
</div>
<script src="orbit.js"></script>
</html>
CSS:
#oval {
position: absolute;
margin-top: 100px;
height: 200px;
width: 400px;
border: 3px solid black;
border-radius: 400px/400px;
right: 50%;
transform: translate(50%, -50%);
}
.ball {
height: 30px;
width: 30px;
border-radius: 30px;
position: absolute;
}
#ball0 {
left:270px;
top:185px;
/*right: 100px;
bottom: -15;*/
background-color: blue;
}
#ball1 {
left:100px;
/* bottom:-15px; */
top: 185px;
background-color: green;
}
#ball2 {
top:-15px;
/* right: 100px; */
left: 270px;
background-color: yellow;
}
#ball3 {
top:-15px;
left: 100px;
background-color: red;
}
https://jsfiddle.net/nwm3r4he/3/
I have a little app (all code follows) which should replicate c0 to the other canvases and allow for printing; the latter is the only piece of this not working at the moment. What I mean is, it saves an image, but the transferred canvas appear blank when I open it on my computer.
Why is that? Thanks in advance.
var canvas = [],
image;
var mainCanvas;
mainCanvas = new fabric.Canvas('c0');
for (i = 1; i <= 3; i++) {
canvas[i] = new fabric.StaticCanvas('sc' + i);
}
function addText() {
var text = new fabric.IText('Type here...', {
fontSize: 27,
top: 10,
left: 10,
});
mainCanvas.add(text);
}
var rect = new fabric.Rect({
fill: '#ff0000',
width: 100,
height: 100,
id: 1
});
var circle = new fabric.Circle({
fill: '#ffff00',
radius: 50,
left: 150,
top: 150,
originX: 'center',
originY: 'center',
id: 2
});
mainCanvas.on('object:added', onModified);
mainCanvas.on('object:modified', onModified);
mainCanvas.on('object:scaling', onModified);
mainCanvas.on('object:moving', onModified);
mainCanvas.add(rect, circle);
function onModified(option) {
var ob = option.target;
var index = mainCanvas.getObjects().indexOf(ob);
ob.clone(function(obj) {
for (i = 1; i <= 3; i++) {
canvas[i].insertAt(obj, index, true);
}
});
};
$('#update').click(function() {
updateCanvas();
});
function updateCanvas() {
var json = JSON.stringify(mainCanvas);
for (i = 1; i <= 3; i++) {
canvas[i].loadFromJSON(json);
}
}
// Toggling Images
function replaceImage(imgUrl) {
if (!isImageLoaded) return; //return if initial image not loaded
image.setSrc(imgUrl, function() {
mainCanvas.renderAll();
updateCanvas();
});
}
// Default (Blank)
fabric.Image.fromURL('https://i.imgur.com/SamdNdX.png', function(img) {
isImageLoaded = true;
image = img.set({
selectable: false,
evented: false,
});
mainCanvas.add(image);
mainCanvas.sendToBack(image);
updateCanvas();
});
$('#save').click(function() {
html2canvas($('#imagesave'), {
onrendered: function(canvas) {
var a = document.createElement('a');
// toDataURL defaults to png, so we need to request a jpeg, then convert for file download.
a.href = canvas.toDataURL("image/jpeg").replace("image/jpeg", "image/octet-stream");
a.download = 'myfile.jpg';
a.click();
}
});
});
html * {
margin: 0px;
padding: 0px;
}
body {
margin: 0px;
padding: 0px;
}
canvas {
margin: 0px;
display: block;
padding: 0;
}
td,
tr {
margin: 0;
padding: 0;
border: 0;
outline: 0;
vertical-align: baseline;
}
#imagesave {
background-color: white;
height: 637.5px;
width: 825px;
padding-left: 75px;
border: 1px solid black;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/0.4.1/html2canvas.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.22/fabric.js"></script>
<button onclick="addText();" class="dropdown-item">Add Text</button><button id="save">Save</button>
<button onclick="replaceImage('https://i.imgur.com/SamdNdX.png')">Blank</button>
<button onclick="replaceImage('https://i.imgur.com/TIINd6E.png')">Hands Pic</button>
<div id="imagesave">
<table>
<tr>
<td>
<canvas id="c0" width="187.5" height="636"></canvas>
</td>
<td>
<canvas id="sc1" width="187.5" height="636"></canvas>
</td>
<td>
<canvas id="sc2" width="187.5" height="636"></canvas>
</td>
<td>
<canvas id="sc3" width="187.5" height="636"></canvas>
</td>
</tr>
</table>
</div>
Your fabric canvases are tainted by the images you draw on it; html2canvas will thus not draw these since they would also taint the resulting canvas.
Since your images are served with the proper headers, the solution is to request them with the crossOrigin flag.
You'd do it both in the initial fabric.Image call
fabric.Image.fromURL( url, callback, { crossOrigin: true } )
and in the replaceImage function
image.setSrc( imgUrl, callback, { crossOrigin: true } );
var canvas = [],
image;
var mainCanvas;
mainCanvas = new fabric.Canvas('c0');
for (i = 1; i <= 3; i++) {
canvas[i] = new fabric.StaticCanvas('sc' + i);
}
function addText() {
var text = new fabric.IText('Type here...', {
fontSize: 27,
top: 10,
left: 10,
});
mainCanvas.add(text);
}
var rect = new fabric.Rect({
fill: '#ff0000',
width: 100,
height: 100,
id: 1
});
var circle = new fabric.Circle({
fill: '#ffff00',
radius: 50,
left: 150,
top: 150,
originX: 'center',
originY: 'center',
id: 2
});
mainCanvas.on('object:added', onModified);
mainCanvas.on('object:modified', onModified);
mainCanvas.on('object:scaling', onModified);
mainCanvas.on('object:moving', onModified);
mainCanvas.add(rect, circle);
function onModified(option) {
var ob = option.target;
var index = mainCanvas.getObjects().indexOf(ob);
ob.clone(function(obj) {
for (i = 1; i <= 3; i++) {
canvas[i].insertAt(obj, index, true);
}
});
};
$('#update').click(function() {
updateCanvas();
});
function updateCanvas() {
var json = JSON.stringify(mainCanvas);
for (i = 1; i <= 3; i++) {
canvas[i].loadFromJSON(json);
}
}
// Toggling Images
function replaceImage(imgUrl) {
if (!isImageLoaded) return; //return if initial image not loaded
image.setSrc(imgUrl, function() {
mainCanvas.renderAll();
updateCanvas();
},{ crossOrigin: 'anonymous' } );
}
// Default (Blank)
fabric.Image.fromURL('https://i.imgur.com/SamdNdX.png', function(img) {
isImageLoaded = true;
image = img.set({
selectable: false,
evented: false,
});
mainCanvas.add(image);
mainCanvas.sendToBack(image);
updateCanvas();
},{ crossOrigin: 'anonymous' });
$('#save').click(function() {
html2canvas($('#imagesave'), {
onrendered: function(canvas) {
var a = document.createElement('a');
// toDataURL defaults to png, so we need to request a jpeg, then convert for file download.
a.href = canvas.toDataURL("image/jpeg").replace("image/jpeg", "image/octet-stream");
a.download = 'myfile.jpg';
a.click();
}
});
});
html * {
margin: 0px;
padding: 0px;
background: white;
}
body {
margin: 0px;
padding: 0px;
}
canvas {
margin: 0px;
display: block;
padding: 0;
}
td,
tr {
margin: 0;
padding: 0;
border: 0;
outline: 0;
vertical-align: baseline;
}
#imagesave {
background-color: white;
height: 637.5px;
width: 825px;
padding-left: 75px;
border: 1px solid black;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/0.4.1/html2canvas.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.22/fabric.js"></script>
<button onclick="addText();" class="dropdown-item">Add Text</button><button id="save">Save</button>
<button onclick="replaceImage('https://i.imgur.com/SamdNdX.png')">Blank</button>
<button onclick="replaceImage('https://i.imgur.com/TIINd6E.png')">Hands Pic</button>
<div id="imagesave">
<table>
<tr>
<td>
<canvas id="c0" width="187.5" height="636"></canvas>
</td>
<td>
<canvas id="sc1" width="187.5" height="636"></canvas>
</td>
<td>
<canvas id="sc2" width="187.5" height="636"></canvas>
</td>
<td>
<canvas id="sc3" width="187.5" height="636"></canvas>
</td>
</tr>
</table>
</div>
Also note that your version of html2canvas is quite outdated, you may want to use one the latest releases where a lot of bugs have been fixed (not this issue though).
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 will try this one more time.
I am having trouble getting the quote to download as part of the sketch canvas. I am using sketch.js.
Here is the jsfiddle, you can draw like MSpaint but I when pressing the download button only what is drawn is copied. I would like the sketch and the words downloaded.
http://jsfiddle.net/e1ovm9mn/
I should also add that I am quite new to all.
Here is the code
<body>
<nav>
<div id="SketchTools">
<!-- Basic tools -->
<img src="img/black_icon.png" alt="Black"/>
<img src="img/red_icon.png" alt="Red"/>
<img src="img/green_icon.png" alt="Green"/>
<img src="img/blue_icon.png" alt="Blue"/>
<img src="img/yellow_icon.png" alt="Yellow"/>
<img src="img/cyan_icon.png" alt="Cyan"/>
<!-- Advanced colors -->
<img src="img/alizarin_icon.png" alt="Alizarin"/>
<img src="img/pomegrante_icon.png" alt="Pomegrante"/>
<img src="img/emerald_icon.png" alt="Emerald"/>
<img src="img/torquoise_icon.png" alt="Torquoise"/>
<img src="img/peterriver_icon.png" alt="Peter River"/>
<img src="img/amethyst_icon.png" alt="Amethyst"/>
<img src="img/sunflower_icon.png" alt="Sun Flower"/>
<img src="img/orange_icon.png" alt="Orange"/>
<img src="img/clouds_icon.png" alt="Clouds"/>
<img src="img/silver_icon.png" alt="Silver"/>
<img src="img/asbestos_icon.png" alt="Asbestos"/>
<img src="img/wetasphalt_icon.png" alt="Wet Asphalt"/>
</br> <img src="img/eraser_icon.png" alt="Eraser"/>
<!-- Size options -->
<img src="img/pencil_icon.png" alt="Pencil"/>
<img src="img/pen_icon.png" alt="Pen"/>
<img src="img/stick_icon.png" alt="Stick"/>
<img src="img/smallbrush_icon.png" alt="Small brush"/>
<img src="img/mediumbrush_icon.png" alt="Medium brush"/>
<img src="img/bigbrush_icon.png" alt="Big brush"/>
<img src="img/bucket_icon.png" alt="Huge bucket"/>
Download
<br/>
</div>
<div class="links">
<ul>
<li><img src="ficon.png" alt="Facebook"></li>
<li><img src="igramicon.png" alt="Instagram"></li>
<li><img src="picon.png" alt="Pinterest"></li>
<li><img src="mcicon.png" alt="Mixcloud"></li>
<li><img src="twicon.png" alt="Twitter"></li>
</ul>
</div>
<div class="message">
<div id="quote"></div>
<script>
(function() {
var quotes = [
{ text: "Snuggletooth likes pancakes"},
{ text: "Would you like Snuggletooth to tuck you in?"},
{ text: " Snuggletooth loves you"},
{ text: "Snuggletooth is here for you"},
{ text: "Did you know that Snuggletooth </br>can be in 2 places at once?"},
{ text: "Heyyyy!<br> I was just thinking about you </br>Love Snuggletooth" },
{ text: "Wanna Sandwich??</br>xSnuggletooth"},
{ text: "Want some breakfast???</br> ;) Snuggletooth"},
{ text: "Snuggletooth-a-riffic!!!"},
{ text: "Snuggletooth makes great popcorn!"},
{ text: "Come over to Snuggletooth's! He makes a great guacamole!"},
{ text: "Snuggletooth likes his bubblebaths to smell like bubblegum"},
{ text: "Snuggletooth wants to know what are you up to later?"},
{ text: "Snuggletooth-a-licious!!!"},
];
var quote = quotes[Math.floor(Math.random() * quotes.length)];
document.getElementById("quote").innerHTML =
'<p>' + quote.text + '</p>' +
'' + '';
})();
</script>
</div>
</nav>
<canvas id="SketchPad" width="1125" height="600">
</canvas>
</div>
<script type="text/javascript">
$(function() {
$('#SketchPad').sketch();
});
</script>
</body>
And here is the sketch.js
var __slice = Array.prototype.slice;
(function($) {
var Sketch;
$.fn.sketch = function() {
var args, key, sketch;
key = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
if (this.length > 1) {
$.error('Sketch.js can only be called on one element at a time.');
}
sketch = this.data('sketch');
if (typeof key === 'string' && sketch) {
if (sketch[key]) {
if (typeof sketch[key] === 'function') {
return sketch[key].apply(sketch, args);
} else if (args.length === 0) {
return sketch[key];
} else if (args.length === 1) {
return sketch[key] = args[0];
}
} else {
return $.error('Sketch.js did not recognize the given command.');
}
} else if (sketch) {
return sketch;
} else {
this.data('sketch', new Sketch(this.get(0), key));
return this;
}
};
Sketch = (function() {
function Sketch(el, opts) {
this.el = el;
this.canvas = $(el);
this.context = el.getContext('2d');
this.options = $.extend({
toolLinks: true,
defaultTool: 'marker',
defaultColor: 'black',
defaultSize: 5
}, opts);
this.painting = false;
this.color = this.options.defaultColor;
this.size = this.options.defaultSize;
this.tool = this.options.defaultTool;
this.actions = [];
this.action = [];
this.canvas.bind('click mousedown mouseup mousemove mouseleave mouseout touchstart touchmove touchend touchcancel', this.onEvent);
if (this.options.toolLinks) {
$('body').delegate("a[href=\"#" + (this.canvas.attr('id')) + "\"]", 'click', function(e) {
var $canvas, $this, key, sketch, _i, _len, _ref;
$this = $(this);
$canvas = $($this.attr('href'));
sketch = $canvas.data('sketch');
_ref = ['color', 'size', 'tool'];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
key = _ref[_i];
if ($this.attr("data-" + key)) {
sketch.set(key, $(this).attr("data-" + key));
}
}
if ($(this).attr('data-download')) {
sketch.download($(this).attr('data-download'));
}
return false;
});
}
}
Sketch.prototype.download = function(format) {
var mime;
format || (format = "png");
if (format === "jpg") {
format = "jpeg";
}
mime = "image/" + format;
return window.open(this.el.toDataURL(mime));
};
Sketch.prototype.set = function(key, value) {
this[key] = value;
return this.canvas.trigger("sketch.change" + key, value);
};
Sketch.prototype.startPainting = function() {
this.painting = true;
return this.action = {
tool: this.tool,
color: this.color,
size: parseFloat(this.size),
events: []
};
};
Sketch.prototype.stopPainting = function() {
if (this.action) {
this.actions.push(this.action);
}
this.painting = false;
this.action = null;
return this.redraw();
};
Sketch.prototype.onEvent = function(e) {
if (e.originalEvent && e.originalEvent.targetTouches) {
e.pageX = e.originalEvent.targetTouches[0].pageX;
e.pageY = e.originalEvent.targetTouches[0].pageY;
}
$.sketch.tools[$(this).data('sketch').tool].onEvent.call($(this).data('sketch'), e);
e.preventDefault();
return false;
};
Sketch.prototype.redraw = function() {
var sketch;
this.el.width = this.canvas.width();
this.context = this.el.getContext('2d');
sketch = this;
$.each(this.actions, function() {
if (this.tool) {
return $.sketch.tools[this.tool].draw.call(sketch, this);
}
});
if (this.painting && this.action) {
return $.sketch.tools[this.action.tool].draw.call(sketch, this.action);
}
};
return Sketch;
})();
$.sketch = {
tools: {}
};
$.sketch.tools.marker = {
onEvent: function(e) {
switch (e.type) {
case 'mousedown':
case 'touchstart':
this.startPainting();
break;
case 'mouseup':
case 'mouseout':
case 'mouseleave':
case 'touchend':
case 'touchcancel':
this.stopPainting();
}
if (this.painting) {
this.action.events.push({
x: e.pageX - this.canvas.offset().left,
y: e.pageY - this.canvas.offset().top,
event: e.type
});
return this.redraw();
}
},
draw: function(action) {
var event, previous, _i, _len, _ref;
this.context.lineJoin = "round";
this.context.lineCap = "round";
this.context.beginPath();
this.context.moveTo(action.events[0].x, action.events[0].y);
_ref = action.events;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
event = _ref[_i];
this.context.lineTo(event.x, event.y);
previous = event;
}
this.context.strokeStyle = action.color;
this.context.lineWidth = action.size;
return this.context.stroke();
}
};
return $.sketch.tools.eraser = {
onEvent: function(e) {
return $.sketch.tools.marker.onEvent.call(this, e);
},
draw: function(action) {
var oldcomposite;
oldcomposite = this.context.globalCompositeOperation;
this.context.globalCompositeOperation = "copy";
action.color = "rgba(0,0,0,0)";
$.sketch.tools.marker.draw.call(this, action);
return this.context.globalCompositeOperation = oldcomposite;
}
};
})(jQuery);
And just in case here is the css
#import url(http://fonts.googleapis.com/css?family=Codystar);
#import url(http://fonts.googleapis.com/css?family=Lobster);
img {
width: 25px;
height: 25px;
}
li{
}
ul li {
list-style-type: none;
display: inline;
}
.message {
margin-left: 20%;
margin-top: 20%;
z-index: 1;
position: absolute;
font-family: Lobster, Codystar, arial;
font-size: 3.5em;
color:white;
text-align: center;
text-shadow: 2px 2px #ff0000;
}
.links {
float: right;
padding-right: 1em;
}
#SketchPad {
border-color: black;
border-width: 3px;
border-style: solid;
position: fixed;
}
#DownloadPng {
padding: 4px 2px;
font-size: 1em;
line-height: 100%;
text-shadow: 0 1px rgba(0, 0, 0, 0.5);
color: #fff;
display:inline-block;
/*vertical-align: middle;
text-align: center;*/
cursor: pointer;
font-weight: bold;
transition: background 0.1s ease-in-out;
-webkit-transition: background 0.1s ease-in-out;
-moz-transition: background 0.1s ease-in-out;
-ms-transition: background 0.1s ease-in-out;
-o-transition: background 0.1s ease-in-out;
text-shadow: 0 1px rgba(0, 0, 0, 0.3);
color: #fff;
font-family: Arial, sans-serif;
background-color: #2ecc71;
box-shadow: 0px 7px 0px 0px #27ae60;
text-decoration: none;
margin-top: 10px;
margin-bottom: 15px;
}
#DownloadPng:hover {
background-color: #27ae60;
border-radius: 7px;
}
#DownloadPng:active {
box-shadow: 0px 1px 0px 0px #27ae60;
border-radius: 7px;
}
#SketchTools {
width: 65%;
height: 5%;
position: fixed;
float: left;
z-index: 1;
padding: .2em;
}
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/