i am working on online print media application like 'vistaprint.in' where user can modify premade designs on fabric js canvas. in fabric.js object there is no unique identifier for objects...all the text objects create a input for each them so that user can directly change desired text by just changing in input...you can see in screenshot in right side of canvas there are input for each text object...what i am doing now is when input changes i loop through all text objects and match input text and text object text if it matches then i update new text to that object..this works fine for first time but not after that...i tried but unable to find what to do to solve this.
This is how canvas and input looks like now
Screenshot of app
and this is the function which i created now for changing text of canvas
$(document).on('input', '#cardalltexthex input', function(){
v = $(this).attr('text');
newtext = $(this).val();
objs = canvas.getObjects();
objs.forEach(function(e) {
if (e && e.type === 'i-text') {
console.log(e.text);
console.log(newtext);
if (e.text == v) {
e.setText(newtext);
canvas.renderAll();
}
}
});
});
You need to specify id property to fabric object and check that property with your input as below sample
No need to do if (e.text == v) just set all fabric object with some unique id and compare that with your input.
var text;
var canvas;
canvas = new fabric.Canvas('c');
text1 = new fabric.Text("Text", {
id: "cardalltexthex1",
fontSize: 70,
selectable: false,
left: 100,
top: 0,
text: "text1",
fill: '#f00'
});
canvas.add(text1);
text2 = new fabric.Text("Text", {
id: "cardalltexthex2",
fontSize: 70,
selectable: false,
left: 100,
top: 100,
text: "text2",
fill: '#f00'
});
canvas.add(text2);
$('.input').on('keyup', function() {
id = $(this).attr('id');
val = $(this).attr('data-text');
newtext = $(this).val();
input = $(this);
objs = canvas.getObjects();
objs.forEach(function(obj) {
if (obj && obj.text == val) {
obj.setText(newtext);
input.attr("data-text", newtext);
canvas.renderAll();
}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.17/fabric.min.js"></script>
<canvas id="c" width=500 height=250></canvas>
<input class="input" data-text="text1" value="text1" id="cardalltexthex1" />
<input class="input" data-text="text2" value="text2" id="cardalltexthex2" />
Related
How can I move the popper.js element to follow specific coords?
I was able to get (I think) the caret position in a textarea but now I need to make Popper.js follow it.
I tried, update and onUpdate on the root and in the modifiers. I do not understand at all the documentation.
I created a codepen to show what I was able to achieve so far:
https://codepen.io/anon/pen/gzGvvG
const refEl = document.getElementById('ref');
const popEl = document.getElementById('pop');
new Popper(refEl, popEl, {
placement: 'auto',
modifiers: {
offset: {
enabled: true,
offset: '0,10'
},
flip: {
behavior: ['left', 'bottom', 'top']
},
preventOverflow: {
enabled: true,
padding: 10,
escapeWithReference: false,
}
},
});
document.getElementById("ref").onkeyup = function() {
var xy = getCursorXY(refEl, refEl.selectionEnd)
document.getElementById("log").innerText = `X: ${xy.x}, Y: ${xy.y}`;
}
The getcursorXY function I got from Medium: https://medium.com/#jh3y/how-to-where-s-the-caret-getting-the-xy-position-of-the-caret-a24ba372990a
It's not a complete answer to your question ... as it needs a few tweaks to get it to work like how I think you are trying to get it to work. But, here's javascript that i modified from your CodePen which is believe is a step in the right direction for what you are looking to achieve.
const refEl = document.getElementById('ref');
const popEl = document.getElementById('pop');
function update_popper(x, y) {
new Popper(refEl, popEl, {
placement: 'auto',
modifiers: {
offset: {
enabled: true,
offset: (x - 150) + ',' + (-1 * (y - 140))
},
flip: {
behavior: ['left', 'bottom', 'top']
},
preventOverflow: {
enabled: true,
padding: 10,
escapeWithReference: false,
}
},
});
}
document.getElementById("ref").onkeyup = function () {
var xy = getCursorXY(refEl, refEl.selectionEnd)
document.getElementById("log").innerText = `X: ${xy.x}, Y: ${xy.y}`;
update_popper(xy.x, xy.y);
}
const getCursorXY = (input, selectionPoint) => {
const {
offsetLeft: inputX,
offsetTop: inputY,
} = input
// create a dummy element that will be a clone of our input
const div = document.createElement('div')
// get the computed style of the input and clone it onto the dummy element
const copyStyle = getComputedStyle(input)
for (const prop of copyStyle) {
div.style[prop] = copyStyle[prop]
}
// we need a character that will replace whitespace when filling our dummy element if it's a single line <input/>
const swap = '.'
const inputValue = input.tagName === 'INPUT' ? input.value.replace(/ /g, swap) : input.value
// set the div content to that of the textarea up until selection
const textContent = inputValue.substr(0, selectionPoint)
// set the text content of the dummy element div
div.textContent = textContent
if (input.tagName === 'TEXTAREA') div.style.height = 'auto'
// if a single line input then the div needs to be single line and not break out like a text area
if (input.tagName === 'INPUT') div.style.width = 'auto'
// create a marker element to obtain caret position
const span = document.createElement('span')
// give the span the textContent of remaining content so that the recreated dummy element is as close as possible
span.textContent = inputValue.substr(selectionPoint) || '.'
// append the span marker to the div
div.appendChild(span)
// append the dummy element to the body
document.body.appendChild(div)
// get the marker position, this is the caret position top and left relative to the input
const { offsetLeft: spanX, offsetTop: spanY } = span
// lastly, remove that dummy element
// NOTE:: can comment this out for debugging purposes if you want to see where that span is rendered
document.body.removeChild(div)
// return an object with the x and y of the caret. account for input positioning so that you don't need to wrap the input
return {
x: inputX + spanX,
y: inputY + spanY,
}
}
what I have done here is to move your Popper() into a function called update popper, which will rebuild the popper new each time with a new x,y offset. You don't have to worry much about creating the new Popper() over and over, as it is mostly all just math, with very little weight.
Anyways, i think that should help get you closer to your goal .. good luck!
I'm working on project for Canvas editor.I have this issue :User can add text and edit it in canvas but when delete first text and try to put again text,new text can't be edit.I use fabricjs.
For add text i use this code.
$("#addText").on("click", function addText() {
var oText = new fabric.IText('Tap and Type', {
left: 100,
top: 100,
fontSize: 26,
});
oText.set({ fill: $(".addText-options .sp-preview-inner").css("background-color") });
oText.id = 'userDesign';
canvas.add(oText).renderAll().setActiveObject(oText);
activeObject = canvas.getActiveObject();
});
I found the solution.The problem was in version of fabricjs i used i just change version.
I have a simple fabric js based application where I will let users add shapes connect them and animate them.
Following is my JS
var canvas;
window.newAnimation = function(){
canvas = new fabric.Canvas('canvas');
}
window.addRect = function(){
var rect = new fabric.Rect({
left: 100,
top: 100,
fill: 'red',
width: 20,
height: 20,
});
canvas.add(rect);
}
window.addCircle = function(){
var circle = new fabric.Circle({
radius: 20, fill: 'green', left: 100, top: 100
});
canvas.add(circle);
}
This is my Fiddle. You can click on new animation and then add objects as of now.
I want the user to select some object and then also be able to delete it I am not sure how. I found this Delete multiple Objects at once on a fabric.js canvas in html5 But i was not able to implement it successfully. I basically want users to be able to select an object and delete it.
Since new version of fabric.js was released - you should use:
canvas.remove(canvas.getActiveObject());
Edit: This is for older versions now.
You can use the remove() method, eg.
window.deleteObject = function() {
canvas.getActiveObject().remove();
}
jsfiddle
Delete all selected objects:
canvas.getActiveObjects().forEach((obj) => {
canvas.remove(obj)
});
canvas.discardActiveObject().renderAll()
I am using Fabric JS 2.3.6.
I wanted to allow the user to select one or more objects on the canvas and delete them by clicking the delete button.
Removed methods from old versions
The following methods are no longer available since the introduction of ActiveSelection:
setActiveGroup(group);
getActiveGroup();
deactivateAll();
discardActiveGroup();
deactivateAllWithDispatch();
Here is my code that works great for me and hopefully you as well.
$('html').keyup(function(e){
if(e.keyCode == 46) {
deleteSelectedObjectsFromCanvas();
}
});
function deleteSelectedObjectsFromCanvas(){
var selection = canvas.getActiveObject();
if (selection.type === 'activeSelection') {
selection.forEachObject(function(element) {
console.log(element);
canvas.remove(element);
});
}
else{
canvas.remove(selection);
}
canvas.discardActiveObject();
canvas.requestRenderAll();
}
It's pretty simple actually.
Just use Fabric's event handling to manage the object selection, and then fire the delete function for whatever object is selected.
I'm using the canvas selection events to cover all the objects of the canvas. The idea is to add a delete button, keeping it hidden unless needed, and then handling its display on canvas selection events.
Deletion is made easy with the help of remove property of the Fabric library, which obviously triggers when the delete button is clicked.
Here is some sample code to demo what I said above.
// Grab the required elements
var canvas = new fabric.Canvas('c'),
delBtn = document.getElementById('delete')
// Hide the delete button until needed
delBtn.style.display = 'none'
// Initialize a rectangle object
var rect = new fabric.Rect({
left: 100,
top: 100,
fill: 'red',
width: 100,
height: 50
})
// Initialize a circle object
var circle = new fabric.Circle({
left: 250,
top: 100,
radius: 20,
fill: 'purple'
})
// Add objects to the canvas
canvas.add(rect)
canvas.add(circle)
// When a selection is being made
canvas.on({
'selection:created': () => {
delBtn.style.display = 'inline-block'
}
})
// When a selection is cleared
canvas.on({
'selection:cleared': () => {
delBtn.style.display = 'none'
}
})
// Rmove the active object on clicking the delete button
delBtn.addEventListener('click', e => {
canvas.remove(canvas.getActiveObject())
})
body {
background-color: #eee;
color: #333;
}
#c {
background-color: #fff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.4.0/fabric.min.js"></script>
<h4>Select an object</h4>
<canvas id="c" width="600" height="200"></canvas>
<input type="button" id="delete" value="Delete selection">
Easy, wasn't it? Cheers!
On further improving #Rahul answer, we can also support deletion of selected object using key events like 'Delete', 'Backscape'.
// Grab the required elements
var canvas = new fabric.Canvas('c'),
delBtn = document.getElementById('delete'),
wrapper= document.getElementById('canvasWrapper')
// Hide the delete button until needed
delBtn.style.display = 'none'
// Initialize a rectangle object
var rect = new fabric.Rect({
left: 100,
top: 100,
fill: 'red',
width: 100,
height: 50
})
// Initialize a circle object
var circle = new fabric.Circle({
left: 250,
top: 100,
radius: 20,
fill: 'purple'
})
// Add objects to the canvas
canvas.add(rect)
canvas.add(circle)
// When a selection is being made
canvas.on({
'selection:created': () => {
delBtn.style.display = 'inline-block'
}
})
// When a selection is cleared
canvas.on({
'selection:cleared': () => {
delBtn.style.display = 'none'
}
})
// Rmove the active object on clicking the delete button
delBtn.addEventListener('click', e => {
canvas.remove(canvas.getActiveObject())
})
//Remove using keyboard events
wrapper.addEventListener('keyup', e => {
if (
e.keyCode == 46 ||
e.key == 'Delete' ||
e.code == 'Delete' ||
e.key == 'Backspace'
) {
if (canvas.getActiveObject()) {
if (canvas.getActiveObject().isEditing) {
return
}
canvas.remove(canvas.getActiveObject())
}
}
})
body {
background-color: #eee;
color: #333;
}
#c {
background-color: #fff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.4.0/fabric.min.js"></script>
<h4>Select an object</h4>
<div tabIndex="1000"id="canvasWrapper" #keyup="checkDelete($event)" >
<canvas id="c" width="600" height="200"></canvas>
</div>
<input type="button" id="delete" value="Delete selection">
Delete object in Fabricjs. This works completely fine.
this.canvas.getActiveObjects().forEach((o) => {
this.canvas.remove(o);
});
you can delete active object by using backspace key
$(document).keydown(function(event){
if (event.which == 8) {
if (canvas.getActiveObject()) {
canvas.getActiveObject().remove();
}
}
});
I want to edit the highlighted characters in a text in canvas using fabric.js, like change its color, font, style etc.
just like this http://fabricjs.com/test/misc/itext.html
to #user43250937
hey uhm. I tried it and it works! :D thanks.
I tried Underline, Bold, Italic, but I have a problem changing the color of the text, I tried :
// "cinput" is the id of the color picker.
addHandler('cinput', function(obj) {
var color = $("#cinput").val();
var isColor = (getStyle(obj, 'fill') || '').indexOf(color) > -1;
setStyle(obj, 'fill', isColor ? '' : color);
});
Usually answers without a description of what you tried and what didn't work are completely ignored here, i'm answering this time because the fabricjs library is quite complex and the documentation is a bit lacking sometimes...
That page has samples for the IText object, text that can be edited in place (the content of the basic fabricjs text object can be modified only from outside via javascript).
Building a static IText object with different styles applied it's simple:
HTML:
<canvas id="canv" width="250" height="300" style="border: 1px solid rgb(204, 204, 204); width: 400px; height: 400px; -webkit-user-select: none;"></canvas>
Javascript:
var canvas=new fabric.Canvas('canv');
var iTextSample = new fabric.IText('hello\nworld', {
left: 50,
top: 50,
fontFamily: 'Helvetica',
fill: '#333',
lineHeight: 1.1,
styles: {
0: {
0: { textDecoration: 'underline', fontSize: 80 },
1: { textBackgroundColor: 'red' }
},
1: {
0: { textBackgroundColor: 'rgba(0,255,0,0.5)' },
4: { fontSize: 20 }
}
}
});
canvas.add(iTextSample);
Link to JSFiddle
As you can see you just specify the style for every character of each for each line (first for the hello line, then for the world line).
If you want something dynamic with the ability to select some text and change the appearance/style there is some work to do, you'll need to:
Add a button or a clickable element for each style (bold,italic,change color, change background,etc...) that you want to apply dynamically;
Add a click listener to each button with some code that changes the style of the selected text inside the IText.
You'll need a basic function that add handlers that you will reuse for every style button:
function addHandler(id, fn, eventName) {
document.getElementById(id)[eventName || 'onclick'] = function() {
var el = this;
if (obj = canvas.getActiveObject()) {
fn.call(el, obj);
canvas.renderAll();
}
};
}
And some helper functions to change the styles:
function setStyle(object, styleName, value) {
if (object.setSelectionStyles && object.isEditing) {
var style = { };
style[styleName] = value;
object.setSelectionStyles(style);
}
else {
object[styleName] = value;
}
}
function getStyle(object, styleName) {
return (object.getSelectionStyles && object.isEditing)
? object.getSelectionStyles()[styleName]
: object[styleName];
}
addHandler('underline', function(obj) {
var isUnderline = (getStyle(obj, 'textDecoration') || '').indexOf('underline') > -1;
setStyle(obj, 'textDecoration', isUnderline ? '' : 'underline');
});
Link to working JSFiddle with a working underline button.
A bit of coding is involved as you can see, but it's not that complex, for the full list of available style options you can check the fabricjs documentation.
I need to create several Raphael objects in my program. field1 and field2 are div-elements. Each Raphael object (paper1,paper2,...) will have unique canvas and they all need to have exactly the same functionality. Raphael objects need to be created dynamically at later point. In the following examplecode I need to know which of the objects fires the mousedown event. I would also like to know in which div-element (field1/field2 here) is that mousedown event fired. How can I get the information?
var myProgram = (function() {
var paper1 = Raphael("field1", 200, 400, fieldActions);
var paper2 = Raphael("field2", 200, 400, fieldActions);
var planeAttrs = {
fill: "#fff"
};
function fieldActions(){
var that = this;
that.field = that.rect(0, 0, 200, 400, 30);
that.field.attr(planeAttrs);
that.field.mousedown( function(e) {
});
});
}());
that.field.mousedown( function(e) {
console.log(this, this.node, this.paper.canvas, this.paper.canvas.parentNode)
});
this - the rect raphael object
this.node - the rect svg dom element
this.paper.canvas - the svg dom element
this.paper.canvas.parentNode - div with id (field2/field1) which contains the svg that is clicked.
Here you go:
that.field.mousedown( function(e) {
var target = e.target;
var svgElem = target.parentNode;
var div = svgElem.parentNode;
alert(div.id);
});
http://jsfiddle.net/mihaifm/UyPn6/3/