I'm making a program whereby rectangles can be modified in realtime (depending on what the user keyboard input is) using fabric.js.
I'm having an awful time doing this.
I'm trying to get the input id=width and input id=height to be able to edit the rect.width and the rect.height and PREFERABLY have it change the shape of the rectangle as I'm doing it.
I've included my code below, I've just removed all my attempts at document.getElementById('height').value; because they have all been absolutely useless, and when I include them it breaks the whole script (which I didn't think was very useful for a jsfiddle).
I think I'm in a bit over my head.
Any help would be much appreciated.
jsfiddle
HTML
<body>
<input id="width" type="number" value="100"> <br>
<input id="height" type="number" value="100"> <br>
</body>
<canvas id="canvas" width="800" height="800"></canvas>
Javascript
var canvas = new fabric.Canvas('canvas');
var grid = 20;
for (var i = 0; i < (800 / grid); i++) {
canvas.add(new fabric.Line([ i * grid, 0, i * grid, 800],
{strokeDashArray: [3, 17], strokeWidth : 3, stroke: 'black', selectable: false }));
canvas.add(new fabric.Line([ 0, i * grid, 800, i * grid],
{strokeDashArray: [3, 17], strokeWidth :3, stroke: 'black', selectable: false }))
}
var rect = new fabric.Rect({
top : 50,
left : 50,
width : 100,
height : 100,
fill : 'red',
hasControls : false
});
canvas.add(rect);
canvas.on('object:moving', function(options) {
options.target.set({
left: Math.round(options.target.left / grid) *
grid,
top: Math.round(options.target.top / grid) * grid
});
});
canvas.on('object:moving', function (e) {
var obj = e.target;
// if object is too big ignore
if(obj.currentHeight > obj.canvas.height || obj.currentWidth
> obj.canvas.width){
return;
}
obj.setCoords();
// top-left corner
if(obj.getBoundingRect().top < 0 ||
obj.getBoundingRect().left < 0){
obj.top = Math.max(obj.top, obj.top-
obj.getBoundingRect().top);
obj.left = Math.max(obj.left, obj.left-
obj.getBoundingRect().left);
}
// bot-right corner
if(obj.getBoundingRect().top+obj.getBoundingRect().height >
obj.canvas.height ||
obj.getBoundingRect().left+obj.getBoundingRect().width >
obj.canvas.width){
obj.top = Math.min(obj.top, obj.canvas.height-
obj.getBoundingRect().height+obj.top-obj.getBoundingRect().top);
obj.left = Math.min(obj.left, obj.canvas.width-
obj.getBoundingRect().width+obj.left-obj.getBoundingRect().left);
}
});
So you are really asking two questions:
1. How do I know when the value of a text box changes?
You can find the input using getElementById and attach and event listener to the change event.
let widthInput = document.getElementById('width');
widthInput.addEventListener('change', (event) => {
});
2. How do I resize an object in fabric.js
You use the set method on the Fabric.js object. Then call renderAll on the canvas.
rect.set('width', parseInt(widthInput.value));
canvas.renderAll();
All Together:
Note: You have to click out of the text box for the change event to fire.
https://jsfiddle.net/9v5zogju/
HTML
I removed in the invalid type attributes, gave each input a unique id, and removed the extra body tag.
<input id="width" value="100"><br>
<input id="height" value="100"><br>
<canvas id="canvas" width="800" height="800"></canvas>
Javascript
I left your code as is. My code is at the bottom.
var canvas = new fabric.Canvas('canvas');
var grid = 20;
for (var i = 0; i < (800 / grid); i++) {
canvas.add(new fabric.Line([ i * grid, 0, i * grid, 800],
{strokeDashArray: [3, 17], strokeWidth : 3, stroke: 'black', selectable: false }));
canvas.add(new fabric.Line([ 0, i * grid, 800, i * grid],
{strokeDashArray: [3, 17], strokeWidth :3, stroke: 'black', selectable: false }))
}
var rect = new fabric.Rect({
top : 50,
left : 50,
width : 100,
height : 100,
fill : 'red',
hasControls : false
});
canvas.add(rect);
canvas.on('object:moving', function(options) {
options.target.set({
left: Math.round(options.target.left / grid) * grid,
top: Math.round(options.target.top / grid) * grid
});
});
canvas.on('object:moving', function (e) {
var obj = e.target;
// if object is too big ignore
if(obj.currentHeight > obj.canvas.height || obj.currentWidth > obj.canvas.width){
return;
}
obj.setCoords();
// top-left corner
if(obj.getBoundingRect().top < 0 || obj.getBoundingRect().left < 0){
obj.top = Math.max(obj.top, obj.top-obj.getBoundingRect().top);
obj.left = Math.max(obj.left, obj.left-obj.getBoundingRect().left);
}
// bot-right corner
if(obj.getBoundingRect().top+obj.getBoundingRect().height > obj.canvas.height || obj.getBoundingRect().left+obj.getBoundingRect().width > obj.canvas.width){
obj.top = Math.min(obj.top, obj.canvas.height-obj.getBoundingRect().height+obj.top-obj.getBoundingRect().top);
obj.left = Math.min(obj.left, obj.canvas.width-obj.getBoundingRect().width+obj.left-obj.getBoundingRect().left);
}
});
//new code below this line
let widthInput = document.getElementById('width');
widthInput.addEventListener('change', (event) => {
rect.set('width', parseInt(widthInput.value));
canvas.renderAll();
});
let heightInput = document.getElementById('height');
heightInput.addEventListener('change', (event) =>
rect.set('height', parseInt(heightInput.value));
canvas.renderAll();
});
https://jsfiddle.net/andrwrbnsn/doynL163/30/
After a quick look into it I hope this helps somewhat — fabric has setWidth and setHeight as can be explained here:
So essentially you use document.GetElementById to get the inputs and add an eventlistener which on keyup updates the width/height and refreshes the canvas.
How do I change a value for a Fabric.js object?
Related
I am trying to create a stage using konva.js. Basically, I need to add two (at least) or more images on the stage based on some layouts.
Example:
I have a layout that splits the stage area verically into two similar groups. Each group has one image.
Example code:
var stage = new Konva.Stage({
container: 'container',
width: width, // 295px
height: height, // 600px
});
var layer = new Konva.Layer({
imageSmoothingEnabled: true
});
// add a vertical line in order to show seperated groups
var line = new Konva.Line({
points: [stage.width() / 2, 0, stage.width() / 2, stage.height()],
stroke: '#9499a3',
strokeWidth: 2,
lineCap: 'round',
lineJoin: 'round',
});
layer.add(line)
// create group #1
var group1 = new Konva.Group({
x: 0,
y: 0,
width: stage.width() / 2,
height: stage.height()
});
var image1;
var imageObj = new Image();
imageObj.onload = function () {
image1 = new Konva.Image({
x: (stage.width() / 2 - imageObj.width) - line.strokeWidth() / 2,
//y: 0,
width: imageObj.width,
height: stage.height(),
image: imageObj,
draggable: true,
});
//layer.add(image1);
group1.add(image1);
image1.on('dragstart', function () {
console.log('dragstart')
});
image1.on('dragmove', function(e){
console.log('X : ' + this.attrs.x + ', Y : ' + this.attrs.y)
});
image1.on('dragend', function () {
console.log('X : ' + this.attrs.x + ', Y : ' + this.attrs.y)
});
};
imageObj.src = 'img/1.jpg';
// create group #2
var group2 = new Konva.Group({
x: stage.width() / 2 + line.strokeWidth() / 2,
y: 0,
width: stage.width() / 2,
height: stage.height()
});
var image2;
var imageObj = new Image();
imageObj.onload = function () {
image2 = new Konva.Image({
//x: stage.width() / 2 + line.strokeWidth() / 2,
//y: stage.height() / 2 - imageObj.height / 2,
width: imageObj.width,
height: stage.height(),
image: imageObj,
draggable: true,
});
//layer.add(image2);
group2.add(image2);
};
imageObj.src = 'img/2.jpg';
layer.add(group1, group2)
stage.add(layer);
What I want to do:
Define the draggable area of each image (or group) so to not be overflowed between each other.
Check if layer is visible so re-position image to hide the layer.
So the way to achieve this is to have a rect defining the frame in which the image will be constrained. Put this into a group and set the clip region of the group to match the position and size of the frame rect. Now add the image to the group. Only the part of the image in the group will be visible.
As a bonus, if you add a dragBoundFunc to the group you can ensure that an oversized image cannot be dragged beyond frame edges.
See snippet below (best run full-screen) and editable CodePen here.
This is, of course, only a single image frame where you have described that you will have say two in your use case. I suggest that you unravel the code then make a class, then you can use as many as required, etc.
// this data gives the position and size of the frame
let data = {
frameGroup: { x: 50, y: 100, width: 800, height: 300, strokeWidth: 10, stroke: 'cyan'},
fadeImage: {opacity: 0.3}
}
// add a stage
let stage = new Konva.Stage({container: 'container', width: 1000, height: 500 }),
layer = new Konva.Layer({}), // Add a layer and group to draw on
group = new Konva.Group({clip: data.frameGroup}),
rect = new Konva.Rect(data.frameGroup),
image = new Konva.Image({draggable: true}),
fadeImage = null,
imageObj = new Image();
stage.add(layer);
layer.add(group)
group.add(rect);
rect.listening(false); // stop the frame rect intercepting events
// Use the html image object to load the image and handle when laoded.
imageObj.onload=function () {
image.image(imageObj); // set the Konva image content to the html image content
// compute image position so that it is initially centered in the frame
let imagePos = getMiddlePos(data.frameGroup, data.frameGroup, {width: imageObj.width, height: imageObj.height});
// set the Konva image attributes as needed
image.setAttrs({
x: imagePos.x, y: imagePos.y, width: imageObj.width, height: imageObj.height,
// This function ensures the oversized image cannot be dragged beyond frame edges.
// Is is firect by the drag event.
dragBoundFunc: function(pos){
var imagePos = this.getClientRect(); // get the image dimensions.
let
maxPos = { // compute max x & y position allowed for image
x: data.frameGroup.x,
y: data.frameGroup.y
},
minPos = {
x: data.frameGroup.x + data.frameGroup.width - imagePos.width,
y: data.frameGroup.y + data.frameGroup.height - imagePos.height
},
newX = (pos.x >= maxPos.x) ? maxPos.x : pos.x, // ensure left edge not within frame
newY = (pos.y >= maxPos.y) ? maxPos.y : pos.y; // ensure top edge not within frame
newX = newX < minPos.x ? minPos.x : newX; // ensure right edge not within frame
newY = newY < minPos.y ? minPos.y : newY; // ensure top edge not within frame
fadeImage.setAttrs({x: newX, y: newY}); // apply what we computed
// dragBoundFunc must return a value with x & y. Either return same value passed in
// or modify the value.
return {
x: newX,
y: newY
};
}
})
group.add(image) // add the image to the frame group
image.moveToBottom(); // ensure the frame rect is above the image in the z-index.
// make a clone of the image to be used as the fade image.
fadeImage = image.clone({draggable: false, opacity: data.fadeImage.opacity});
layer.add(fadeImage);
// ensure fade image is one step below the frame group. ! - in this simple demo Konva will raise
// a warning because group.zIndex() = 0.
fadeImage.zIndex(group.zIndex() - 1);
}
imageObj.src = "https://assets.codepen.io/255591/hubble_space_image.jpg?x=1"
// simple function to get the x & y for the image to be centered in the frame
function getMiddlePos(framePos, frameSize, imageSize){
return{
x: framePos.x + (frameSize.width - imageSize.width)/2,
y: framePos.y + (frameSize.height - imageSize.height)/2,
}
}
// Toggle use of fade image to show overflowed part of image.
$('#useFadImage').on('change', function(e){
if (fadeImage){
fadeImage.visible(!fadeImage.visible());
}
})
body {
margin: 14px;
padding: 10px;
font: 12pt Verdana, Arial, sans-serif;
}
#container {
width: 1000px;
height: 500px;
border: 1px solid red;
}
<p><h2>Constrain an image to the bounds of a frame</h2></p>
<p>1. Set the frame group clip region.</p>
<p>2. Set dragBounds function on the image so that it cannot escape the group.</p>
<p>Drag the image and note that it cannot me dragged such that white space in the frame would be visible.</p>
<p>
<input type='checkbox' id='useFadImage' checked='' /><label for="useFadImage">Use fade image - shows area of image outside the frame </label>
</p>
<div id='container' ></div>
<p>Image from NASA Image of the day.</p>
<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js'></script>
<script src="https://unpkg.com/konva#8/konva.min.js"></script>
There is a blog entry on the subject here.
I'm making a 3D box with Zdog and I want to let the height of the box grow.
Here is a codepen: https://codepen.io/anon/pen/qzBMgp.
This is my code for the box:
let progressBox = new Zdog.Box({
addTo: progress,
width: 200,
height: boxHeight,
depth: 200,
stroke: 1,
})
This is the code I use to increase the height of the box. If the box is shorter than 400, the box will increase its height with 0.1.
function animate() {
if (boxHeight < 400) {
moveUp = 'up';
} else if (boxHeight > 400) {
moveUp = 'false';
}
boxHeight += moveUp == 'up' ? 0.1 : 0;
}
The problem is that the box stays at a height of 0 (the value I gave to boxHeight), but when I console.log(boxHeight) the boxHeight will grow.
First of all, let me point out that you cannot change the value of a constant . On your code, the boxHeight is declared as const.
Second, you will need to use Zdog's copy() method. Here is your code modified accordingly.
Zdog.Anchor.prototype.renderGraphSvg = function (svg) {
if (!svg) {
throw new Error('svg is ' + svg + '. ' +
'SVG required for render. Check .renderGraphSvg( svg ).');
}
this.flatGraph.forEach(function (item) {
item.render(svg, Zdog.SvgRenderer);
});
};
const TAU = Zdog.TAU;
const light = '#EAE2B7';
const yellow1 = '#FCBF49';
const orange1 = '#F77F00';
const red1 = '#d62828';
const purple1 = '#003049';
const white1 = '#ffffff';
const isSpinning = true;
var boxHeight = 0;
let progress = new Zdog.Illustration({
element: '.progress',
dragRotate: true,
translate: {
y: 25
},
rotate: {
x: -0.4, y: 0.75
}
});
let progressBox = new Zdog.Box({
addTo: progress,
width: 200,
depth: 200,
height: boxHeight,
stroke: 1,
color: purple1, // default face color
leftFace: yellow1,
rightFace: orange1,
topFace: red1,
bottomFace: light,
translate: {
x: 0,
y: 300
},
});
function animate() {
if (boxHeight <= 400) {
boxHeight++; // 1
progressBox = progressBox.copy({
height: boxHeight, // overwrite height
translate: {
y: progressBox.translate.y - 1 // overwrite vertical position to put box in place while height is growing.
}
});
}
progress.updateRenderGraph();
requestAnimationFrame(animate);
}
animate();
I forked your pen and updated it with the code above. See Zdog - progress box
Note that it seems to be expensive doing the copy() method on every animation frame. I am also new to this library and this is currently the fix I know of.
This question already has answers here:
clearRect function doesn't clear the canvas
(5 answers)
Closed 3 years ago.
I want an HTML canvas that displays mouse position on its JS grid on mouse move, but I can't seem to clear my canvas, I tried using ctx.clearRect(0,0 canvas.width, canvas.height) and clearing with a click, but it somehow remembers the previous draw it had. I want only one black square to be displayed on canvas at a time depending on the mouse position. Heres demo on code pen and some code
<canvas id="myMap" style="width: 300px;height: 300px;background-color: beige;"></canvas>
<script>
var findDivisible = (x, scale) => {
while (x % scale !== 0 && x > 0) {
x = x - 1;
};
return x
};
var map = document.getElementById("myMap");
map.width = 300;
map.height = 300;
var mapContext = document.getElementById("myMap").getContext("2d");
map.addEventListener("mousemove", function(e) {
mapContext.clearRect(0, 0, map.width, map.height);
mapContext.rect(findDivisible(e.clientX - map.offsetLeft, 50), findDivisible(e.pageY - map.offsetTop, 50), 50, 50);
mapContext.stroke();
});
map.addEventListener("click", function() {
mapContext.clearRect(0, 0, 500, 500);
})
</script>
You're not starting a new stroke for each rectangle, but "piling them up" so they get re-re-redrawn with .stroke().
Use .beginPath():
function findDivisible(x, scale) {
while (x % scale !== 0 && x > 0) {
x = x - 1;
}
return x;
}
var map = document.getElementById("myMap");
map.width = 300;
map.height = 300;
var mapContext = map.getContext("2d");
map.addEventListener("mousemove", function(e) {
mapContext.clearRect(0, 0, map.width, map.height);
mapContext.beginPath();
mapContext.rect(
findDivisible(e.clientX - map.offsetLeft, 50),
findDivisible(e.pageY - map.offsetTop, 50),
50,
50,
);
mapContext.stroke();
});
map.addEventListener("click", function() {
mapContext.clearRect(0, 0, 500, 500);
});
<canvas id="myMap" style="width: 300px;height: 300px;background-color: beige;"></canvas>
You can try this:
map.addEventListener("mousemove", function(e){
map.width = map.width;
mapContext.rect(findDivisible(e.clientX-map.offsetLeft,50) , findDivisible(e.pageY - map.offsetTop,50), 50, 50);
mapContext.stroke();
});
map.addEventListener("click", function(){
map.width = map.width;
});
One of the ways that is implicitly endorsed in the spec and often used in people’s apps to clear a canvas
https://dzone.com/articles/how-you-clear-your-html5
I'm using fabricjs (1.7.20) and would like to create a sort of "bleed area" where some space around the canvas isn't usable by the user; a sort of "wall" if you will, to stop objects from being moved to the sides of the canvas walls. How might I accomplish this?
var canvas = new fabric.Canvas("c");
canvas.setHeight(350);
canvas.setWidth(350);
canvas.add(new fabric.IText("Some text", {
top: 25,
}));
var circle = new fabric.Circle({
radius: 20, fill: 'green', left: 100, top: 100
});
var triangle = new fabric.Triangle({
width: 20, height: 30, fill: 'blue', left: 150, top: 150
});
canvas.add(circle, triangle);
canvas {
border: 1px solid #dddddd;
margin-top: 10px;
border-radius: 3px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.20/fabric.min.js"></script>
<canvas id="c"></canvas>
This was suggested to me and it is the closest I've gotten. I'm looking to do this, but 10px, give or take, from the border of the canvas.
canvas.on('object:moving', function (e) {
var obj = e.target;
// if object is too big ignore
if(obj.currentHeight > obj.canvas.height || obj.currentWidth > obj.canvas.width){
return;
}
obj.setCoords();
// top-left corner
if(obj.getBoundingRect().top < 0 || obj.getBoundingRect().left < 0){
obj.top = Math.max(obj.top, obj.top-obj.getBoundingRect().top);
obj.left = Math.max(obj.left, obj.left-obj.getBoundingRect().left);
}
// bot-right corner
if(obj.getBoundingRect().top+obj.getBoundingRect().height > obj.canvas.height || obj.getBoundingRect().left+obj.getBoundingRect().width > obj.canvas.width){
obj.top = Math.min(obj.top, obj.canvas.height-obj.getBoundingRect().height+obj.top-obj.getBoundingRect().top);
obj.left = Math.min(obj.left, obj.canvas.width-obj.getBoundingRect().width+obj.left-obj.getBoundingRect().left);
}
});
You can add a 10 pixel bleed area by adding / subtracting the value 10 from the conditions that determine if an object is being moved out of the “bleed” area (I'd actually prefer to call it “padding”), and adding / subtracting from the calculation that repositions the object inside the padding boundaries.
Here's an updated example that works:
var padding = 10;
var canvas = new fabric.Canvas("c");
canvas.setHeight(350);
canvas.setWidth(350);
canvas.add(new fabric.IText("Some text", {
top: 25,
}));
var circle = new fabric.Circle({
radius: 20,
fill: 'green',
left: 100,
top: 100
});
var triangle = new fabric.Triangle({
width: 20,
height: 30,
fill: 'blue',
left: 150,
top: 150
});
canvas.add(circle, triangle);
canvas.on('object:moving', function(e) {
var obj = e.target;
// if object is too big ignore
if (obj.currentHeight > obj.canvas.height - padding * 2 ||
obj.currentWidth > obj.canvas.width - padding * 2) {
return;
}
obj.setCoords();
// top-left corner
if (obj.getBoundingRect().top < padding ||
obj.getBoundingRect().left < padding) {
obj.top = Math.max(obj.top, obj.top - obj.getBoundingRect().top + padding);
obj.left = Math.max(obj.left, obj.left - obj.getBoundingRect().left + padding);
}
// bot-right corner
if (obj.getBoundingRect().top + obj.getBoundingRect().height > obj.canvas.height - padding ||
obj.getBoundingRect().left + obj.getBoundingRect().width > obj.canvas.width - padding) {
obj.top = Math.min(
obj.top,
obj.canvas.height - obj.getBoundingRect().height + obj.top - obj.getBoundingRect().top - padding);
obj.left = Math.min(
obj.left,
obj.canvas.width - obj.getBoundingRect().width + obj.left - obj.getBoundingRect().left - padding);
}
});
canvas {
border: 1px solid #dddddd;
margin-top: 10px;
border-radius: 3px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.20/fabric.min.js"></script>
<canvas id="c"></canvas>
In the first line, I've defined a variable “padding” that refers to the desired padding size. This way, if you want to change the padding later on, you don't have to change it in eight different places.
The padding variable used instead of 0 in the condition for the top left corner.
If the “top left” condition is true, the padding is added to the calculation for repositioning the object.
In the “bottom right” part, we are doing the opposite – we subtract the padding from the condition and subtract from the repositioning calculation.
You can also try the code in this fiddle: https://jsfiddle.net/pahund/hz7jLnme/
I have designed a ruler using fabric.js and when the user mouses over the specific part of the ruler I want to print to the screen the X-coordinate of the ruler (i.e. not the screen coordinate). Right now the ruler canvas starts at position 37 and ends at 726 relative to the ruler, but the ruler goes from 1 to 4600 (it will always start at 1 but the length of the ruler can change). How to transform the mouse coordinates to accurately reflect it's position on the ruler? Here is the code:
var canvas = new fabric.Canvas('canvas');
line_length = input_len;
adjusted_length = (line_length / 666) * 100;
canvas.add(new fabric.Line([0, 0, adjusted_length, 0], {
left: 30,
top: 0,
stroke: '#d89300',
strokeWidth: 3
}));
$('#canvas_container').css('overflow-x', 'scroll');
$('#canvas_container').css('overflow-y', 'hidden');
drawRuler();
function drawRuler() {
$("#ruler[data-items]").val(line_length / 200);
$("#ruler[data-items]").each(function () {
var ruler = $(this).empty(),
len = Number($("#ruler[data-items]").val()) || 0,
item = $(document.createElement("li")),
i;
ruler.append(item.clone().text(1));
for (i = 1; i < len; i++) {
ruler.append(item.clone().text(i * 200));
}
ruler.append(item.clone().text(i * 200));
});
}
canvas.add(new fabric.Text('X-cord', {
fontStyle: 'italic',
fontFamily: 'Hoefler Text',
fontSize: 12,
left: 0,
top: 0,
hasControls: false,
selectable: false
}));
canvas.on('mouse:move', function (options) {
getMouse(options);
});
function getMouse(options) {
canvas.getObjects('text')[0].text =
"X-coords: " + options.e.clientX ; //+ " Y: " + options.e.clientY;
canvas.renderAll();
}
Use the getPointer merthod on canvas Instance.
In your case it should be canvas.getPointer(options.e)which returns an object with x and y properties which represent pointer coordinates relative to canvas.