How to repeat Paths in Free Drawing? - fabricjs

Please look at
https://stackblitz.com/edit/js-wxidql
Here is my code.
import { fabric } from "fabric";
const canvas = new fabric.Canvas("c", { isDrawingMode: true });
canvas.setBackgroundColor("rgb(255,73,64)", canvas.renderAll.bind(canvas));
canvas.on("path:created", e => {
let mousePath = e.path;
let offsetPath = new fabric.Path();
let offsetLeft = mousePath.left + 60;
let offsetTop = mousePath.top + 60;
offsetPath.setOptions({
path: mousePath.path,
left: offsetLeft,
top: offsetTop,
width: mousePath.width,
height: mousePath.height,
fill: '',
stroke: 'black'
});
canvas.add(offsetPath);
canvas.requestRenderAll();
});
Here is an resulting image of my drawing session.
I drew the happy face in the top left corner of the canvas with my mouse.
The offset image was added by my code.
How can I change my code to make the offset drawing look like the one drawn with my mouse?
Edit: from https://github.com/fabricjs/fabric.js/wiki/When-to-call-setCoords
I tried using
offsetDraw.setCoords();
but I was unable to find a way to make it work.
Edit: What I have presented here is a minimized example. I am working on an larger project where I save each path drawn by the user. Later I recreate those paths in an animation like sequence.
Edit: I made some changes to my code in a effort to understand fabricjs.
const canvas = new fabric.Canvas("c", { isDrawingMode: true });
canvas.setBackgroundColor("rgb(255,73,64)", canvas.renderAll.bind(canvas));
canvas.on("path:created", e => {
let mousePath = e.path;
let offsetPath = new fabric.Path();
let offsetLeft = mousePath.left + 60;
let offsetTop = mousePath.top + 60;
offsetPath.setOptions({
path: mousePath.path,
//left: offsetLeft,
//top: offsetTop,
width: mousePath.width,
height: mousePath.height,
fill: '',
stroke: 'black'
});
canvas.add(offsetPath);
canvas.requestRenderAll();
console.log("mousePath left " + mousePath.left + " top " + mousePath.top);
console.log("offsetPath left " + offsetPath.left + " top " + offsetPath.top);
});
In that code, I commented out the setting of the left and top properties of the offsetPath and added console.log lines. I drew a circle in the top left corner of the canvas with my mouse. The resulting image was the following.
The following was printed in the console.
mousePath left 7.488148148148148 top 10.5
offsetPath left -0.5 top -0.5
I don't understand the results. Why was the offset circle rendered in that position?
Edit: I drew another test with my mouse.
It seems that the code repeats the paths of concentric circles rather well. But, the vertical lines are moved out of their correct position. Their displacements increase as the distance from the center increases. Why?

I found a solution for my own question. But, if someone has a better one, then please post it. Look at
https://stackblitz.com/edit/js-ncxvpg
The following is the code.
const canvas = new fabric.Canvas("c", { isDrawingMode: true });
canvas.setBackgroundColor("rgb(255,73,64)", canvas.renderAll.bind(canvas));
canvas.on("path:created", e => {
let mousePath = e.path;
mousePath.clone(clone => {
clone.setOptions({
left: mousePath.left + 100,
top: mousePath.top + 100
});
canvas.add(clone);
});
});
If someone has an explaination as to why my original code didn't work, then please post it.

thanks for sharing your comments on my question. I am facing a similar issue, just that if I click anywhere on the Canvas the first time. Then the offset issue vanishes. This happens for any object that I programmatically insert into the canvas.
In my case, I suspect that the canvas.pointer is having some offset of the current mouse position. However, when I click and add one very small path of 1pixel, then the pointer offset gets corrected. After this, all subsequent paths or objects created programatically are shown at the correct positions.

Related

Set coords for Bounding Rect of a Path after moving it in fabricjs

There are dozen of topics on Stackoverflow on this problem, none of them shows a solution. So the problem is painful for many.
The problem: when I move a path, it's BoundingRect is not moving.
Possible solutions:
the creators of the library recommend _setPath method, but there are no examples, I can't understand how to use it;
Somehow to set Left, Top, Width and Height separately to the bounding rect (pathLine.getBoundingRect()). I don't know how, it's not working with Set().
To set Left, Top, Width and Height to the path. If I do so, the bounding rect is correct, but pathline is also shifting. I can't make it work.
delete and add path from canvas every time it moves. Bad solution :(
Remarkable that _calcDimensions() works correct with path coords.
Which means there are methods that work with path, we just need to find a proper one.
var pathLine = new fabric.Path('M 10 10 L 10 100 L 100 100', {
fill: '',
stroke: 'black',
objectCaching: false
})
var rect = new fabric.Rect({
left: 100,
top: 100,
fill: 'grey',
width: 50,
height: 50,
});
function createCanvas(id){
canvas = new fabric.Canvas(id);
canvas.add(pathLine);
canvas.add(rect);
pathLine.perPixelTargetFind = true;
canvas.on('object:moving', function(e) {
rect.setCoords();
const { width, height, left, top } = pathLine._calcDimensions();
var bound = pathLine.getBoundingRect();
pathLine.path[2][1] = rect.left ;
pathLine.path[2][2] = rect.top ;
canvas.renderAll();
});
return canvas;
}
Any ideas?

KonvaJS, positioning editable text inputs

I need to position text inputs at various places on a KonvaJS layer. I found the following code at https://konvajs.github.io/docs/sandbox/Editable_Text.html and I'm trying to understand the textPosition, stageBox, and areaPosition vars in this code. I want my stage centered in the browser window, but when I do that, the textarea (activated on dblclick) pops up way off to the left. I can't get a console readout of the x/y coordinates, so I can't visualize how the positioning works &, thus, how to change it. Can anyone explain, or point me in the right direction?
var text_overlay = new Konva.Layer();
stage.add(text_overlay);
var textNode = new Konva.Text({
text: 'Some text here',
x: 20,
y: 50,
fontSize: 20
});
text_overlay.add(textNode);
text_overlay.draw();
textNode.on('dblclick', () => {
// create textarea over canvas with absolute position
// first we need to find its position
var textPosition = textNode.getAbsolutePosition();
var stageBox = stage.getContainer().getBoundingClientRect();
var areaPosition = {
x: textPosition.x + stageBox.left,
y: textPosition.y + stageBox.top
};
// create textarea and style it
var textarea = document.createElement('textarea');
document.body.appendChild(textarea);
textarea.value = textNode.text();
textarea.style.position = 'absolute';
textarea.style.top = areaPosition.y + 'px';
textarea.style.left = areaPosition.x + 'px';
textarea.style.width = textNode.width();
textarea.focus();
textarea.addEventListener('keydown', function (e) {
// hide on enter
if (e.keyCode === 13) {
textNode.text(textarea.value);
text_overlay.draw();
document.body.removeChild(textarea);
}
});
})
// add the layer to the stage
stage.add(text_overlay);
UPDATE: I solved part of the problem--the textarea showing up way out of position. You need to use 2 divs in the HTML file instead of one, like so:
<div id="containerWrapper" align="center"><div id="container"></div></div>
Thanks to Frens' answer on Draw border edges of the Konvajs container Stage in html for that one!

Phaser 3. Change dimensions of the game during runtime

Hi please help me to find out how trully responsive game can be created with Phaser3.
Respnsiveness is critical because game (representation layer of Blockly workspace) should be able to be exapanded to larger portion on screen and shrinked back many times during the session.
The question is How I can change dimentions of the game in runtime?
--- edited ---
It turns out there is pure css solution, canvas can be ajusted with css zoom property. In browser works well (no noticeable effect on performance), in cordova app (android) works too.
Here is Richard Davey's answer if css zoom can break things:
I've never actually tried it to be honest. Give it a go and see what
happens! It might break input, or it may carry on working. That (and
possibly the Scale Manager) are the only things it would break,
though, nothing else is likely to care much.
// is size of html element size that needed to fit
let props = { w: 1195, h: 612, elementId: 'myGame' };
// need to know game fixed size
let gameW = 1000, gameH = 750;
// detect zoom ratio from width or height
let isScaleWidth = gameW / gameH > props.w / props.h ? true : false;
// find zoom ratio
let zoom = isScaleWidth ? props.w / gameW : props.h / gameH;
// get DOM element, props.elementId is parent prop from Phaser game config
let el = document.getElementById(props.elementId);
// set css zoom of canvas element
el.style.zoom = zoom;
Resize the renderer as you're doing, but you also need to update the world bounds, as well as possibly the camera's bounds.
// the x,y, width and height of the games world
// the true, true, true, true, is setting which side of the world bounding box
// to check for collisions
this.physics.world.setBounds(x, y, width, height, true, true, true, true);
// setting the camera bound will set where the camera can scroll to
// I use the 'main' camera here fro simplicity, use whichever camera you use
this.cameras.main.setBounds(0, 0, width, height);
and that's how you can set the world boundary dynamically.
window.addEventListener('resize', () => {
game.resize(window.innerWidth, window.innerHeight);
});
There is some builtin support for resizing that can be configured in the Game Config. Check out the ScaleManager options. You have a number of options you can specify in the scale property, based on your needs.
I ended up using the following:
var config = {
type: Phaser.AUTO,
parent: 'game',
width: 1280, // initial width that determines the scaled size
height: 690,
scale: {
mode: Phaser.Scale.WIDTH_CONTROLS_HEIGHT ,
autoCenter: Phaser.Scale.CENTER_BOTH
},
physics: {
default: 'arcade',
arcade: {
gravity: {y: 0, x: 0},
debug: true
}
},
scene: {
key: 'main',
preload: preload,
create: this.create,
update: this.update
},
banner: false,
};
Just in case anybody else still has this problem, I found that just resizing the game didn't work for me and physics didn't do anything in my case.
To get it to work I needed to resize the game and also the scene's viewport (you can get the scene via the scenemanager which is a property of the game):
game.resize(width,height)
scene.cameras.main.setViewport(0,0,width,height)
For Phaser 3 the resize of the game now live inside the scale like this:
window.addEventListener('resize', () => {
game.scale.resize(window.innerWidth, window.innerHeight);
});
But if you need the entire game scale up and down only need this config:
const gameConfig: Phaser.Types.Core.GameConfig = {
...
scale: {
mode: Phaser.Scale.WIDTH_CONTROLS_HEIGHT,
},
...
};
export const game = new Phaser.Game(gameConfig);
Take a look at this article.
It explains how to dynamically resize the canvas while maintaining game ratio.
Note: All the code below is from the link above. I did not right any of this, it is sourced from the article linked above, but I am posting it here in case the link breaks in the future.
It uses CSS to center the canvas:
canvas{
display:block;
margin: 0;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
It listens to the window 'resize' event and calls a function to resize the game.
Listening to the event:
window.onload = function(){
var gameConfig = {
//config here
};
var game = new Phaser.Game(gameConfig);
resize();
window.addEventListener("resize", resize, false);
}
Resizing the game:
function resize() {
var canvas = document.querySelector("canvas");
var windowWidth = window.innerWidth;
var windowHeight = window.innerHeight;
var windowRatio = windowWidth / windowHeight;
var gameRatio = game.config.width / game.config.height;
if(windowRatio < gameRatio){
canvas.style.width = windowWidth + "px";
canvas.style.height = (windowWidth / gameRatio) + "px";
}
else{
canvas.style.width = (windowHeight * gameRatio) + "px";
canvas.style.height = windowHeight + "px";
}
}
I have used and modified this code in my phaser 3 project and it works great.
If you want to see other ways to resize dynamically check here
Put this in your create function:
const resize = ()=>{
game.scale.resize(window.innerWidth, window.innerHeight)
}
window.addEventListener("resize", resize, false);
Important! Make sure you have phaser 3.16+
Or else this won't work!!!
Use the game.resize function:
// when the page is loaded, create our game instance
window.onload = () => {
var game = new Game(config);
game.resize(400,700);
};
Note this only changes the canvas size, nothing else.

Hover Text effect next to mouse cursor

Dear community is there any way to do a text next to the cursor hover effect (in css?) like on this site here?
http://manuelraeder.co.uk/
I tried it with abbr and span style, but it doesn't seem to work. Text should be changeable with every picture.
Thanks in advance!
From the website you linked, they appear to be using Javascript. I don't think there is a way to do this purely with CSS.
When you hover your mouse over an image on the website, a new HTML tag is created at the end of the body:
<p class="tooltip" style="display: block; top: ...; left: ...;">TEXT</p>
Where top and left correspond to the pixel values that your cursor is at, and the text would be whatever text you want to float next to the cursor.
In the Javascript, you would create a hover observer for the associated images, and inside the trigger function create the new p tooltip element. You would set the text for your p element to be the respective image's alt title. Looking at the website's Javascript, this is what they did for their tooltip:
$(document).ready(function() {
// Tooltip only Text
$('.masterTooltip').hover(function(){
// Hover over code
var title = $(this).attr('title');
$(this).data('tipText', title).removeAttr('title');
$('<p class="tooltip"></p>')
.text(title)
.appendTo('body')
.fadeIn('fast');
}, function() {
// Hover out code
$(this).attr('title', $(this).data('tipText'));
$('.tooltip').remove();
}).mousemove(function(e) {
var mousex = e.pageX + 20; //Get X coordinates
var mousey = e.pageY + 10; //Get Y coordinates
$('.tooltip')
.css({ top: mousey, left: mousex })
});
});

Top-Left coordinates not updated on rotating image using fabricjs

When I rotate an image in fabricjs, the top-left corner's coordinates are not updated after rotated. Instead, the top-left corner of the image still refers to the old point. I believe that it should recalculate the top-left corner based on the image's new position. Is there a way to achieve this? Any help is appreciated!
Below is the code for image rotation:
function rotate(){
activeCanvas.forEachObject(function(obj){
if(obj instanceof fabric.Image){
curAngle = obj.getAngle();
obj.setAngle(curAngle-90);
}
});
activeCanvas.renderAll();
}
Now, after the rotation, I want top-left coordinates of the new rotated image but it still returns top and left from the old image state.
For example, let say the image's top-left corner was originally at (100,200) and the image's dimensions are 500x600. Now, if I rotate the image in 90 degrees, the new dimensions are 600x500 and the top-left corner changes as well, as the image is rotated related to its center. But the fabricjs image still refers to the old top-left corner. Is there any method just like setCoords() to get the new upper left corner point as its top left?
As you can see from the snippet below, if you only rotate your object, only the bounding box will be updated, you have to move your object to have the position of your object updated.
var canvas = this.__canvas = new fabric.Canvas('c');
var rect = new fabric.Rect({
left: 120,
top: 30,
width: 100,
height: 100,
fill: 'green',
angle: 20
});
canvas.on("object:rotating", function() {
var ao = canvas.getActiveObject();
if(ao){
console.log('top and left are the same after rotation');
console.log('top:' + ao.top);
console.log('left:' + ao.left);
console.log('but not the bounding box');
var bound = ao.getBoundingRect();
console.log('bounding box - top:' + bound.top);
console.log('bounding box - left:' + bound.left);
}
})
canvas.add(rect);
canvas.renderAll();
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.js"></script>
<script src="https://seikichi.github.io/tmp/PDFJS.0.8.715/pdf.min.js"></script>
<canvas id="c" style="border:1px solid black"></canvas>
I guess you are looking for something like this:
canvas.on('object:rotating', function(options) {
options.target.setCoords();
var left = options.target.oCoords.tl.x,
top = options.target.oCoords.tl.y;
console.log(left, top);
});

Resources