I have a jsfiddle set up because there is a decent amount of code but I will also cp it here.
But first the problem. I would like to send the Shape object to the server for processing on mouseup. The problem I am having is when I add the jQuery ajax or load functions to test the AJAX I get some sort of run away code and everything freezes.
first the code I add so you can try it in the jsfiddle
$('body').load('index.php/main/add_shape', shape, function () { shape.points["x"]});
Now for the code on the js fiddle for the sake of being able to look at it here and Indexing.
HTML
<body>
<canvas id="drawn" height="200" width="200">
<p>Your browser doesn't support HTML5 and the canvas element. You should probably upgrade to one of the following<br>
Google Chrome<br>
Firefox<br>
Safari<br>
Opera
</p>
</canvas>
</body>
CSS
canvas {
border: 1px solid #000;
}
JavaScript
var eventMap = {
mousemove: "move",
touchmove: "move",
mousedown: "down",
touchstart: "down",
mouseup: "up",
touchend: "up"
};
function Shape (type, color, height, width, radius, x, y) {
this.type = type;
this.color = color;
this.h = height;
this.w = width;
this.r = radius;
this.points = ["x","y"];
this.points["x"] = [x];
this.points["y"] = [y];
};
var tools = {}
$(window).ready(function () {
function init () {
hex = 000000;
canvas = $('#drawn').get(0)
c = canvas.getContext('2d');
c.lineJoin = "round";
c.lineCap = "round";
c.strokeStyle = "#"+hex;
c.lineWidth = 1;
tool = new tools['pencil']();
$('canvas').bind('mousedown mousemove mouseup', mouse_Draw);
}
function mouse_Draw (e) {
var pos = findPos(this);
e._x = e.pageX - pos.x;
e._y = e.pageY - pos.y;
if (eventMap[e.type] == "down") {
shapes = new Shape (1, 2, null, null, null, e._x, e._y);
} else if (eventMap[e.type] == "move") {
shapes.points["x"].push(e._x);
shapes.points["y"].push(e._y);
} else if (eventMap[e.type] == 'up') {
shapes.points["x"].push(e._x);
shapes.points["y"].push(e._y);
alert(shapes.points["x"].toString());
}
var func = tool[eventMap[e.type]];
if (func) {
func(e);
}
}
init();
});
function findPos(obj) {
var curleft = curtop = 0;
if (obj.offsetParent) {
do {
curleft += obj.offsetLeft;
curtop += obj.offsetTop;
} while (obj = obj.offsetParent);
return { x: curleft, y: curtop };
}
}
/*****************TOOLS*************/
tools.pencil = function () {
var tool = this;
tool.started = false;
tool.down = function (e) {
c.beginPath();
c.moveTo(e._x, e._y);
tool.started = true;
shape = new Shape (pencil, c.strokeStyle, null, null, null, e._x, e._y);
};
tool.move = function (e) {
if (tool.started) {
c.lineTo(e._x, e._y);
c.stroke();
shape.points["x"].push(e._x);
shape.points["y"].push(e._y);
}
};
tool.up = function (e) {
if (tool.started) {
tool.started = false;
}
};
}
As you can see when you add the jQuery load line to the tool.up it freezes.
if you have a nice new browser ...
tool.up = function (e) {
if (tool.started) {
tool.started = false;
alert(JSON.stringify(shape));
}
};
(or you can steal json code from json.org) then you just have to parse the json serverside
The answer was the object was an infinite loop because of this line
shape = new Shape (pencil, c.strokeStyle, null, null, null, e._x, e._y);
needed to be
shape = new Shape ('pencil', c.strokeStyle, null, null, null, e._x, e._y);
Related
I beg you to help me. I'm creating a small chart and it's not working make the connecting lines between the rectangles so that they stretch
after moving the rectangle-nodes. What we have:
3 rectangles created in OOR js svg;
created connecting lines with rectangles (in OOP js svg);
function for moving rectangle-nodes.
What is the problem. When dragging nodes, the lines break away from the rectangles.
An example of how a diagram works
here
I ask for help in creating a function for updating the line when dragging the nodes of the diagram.
I think that it needs to put the value of the points of contact, put the name of the update function
to the code of the drag/drop function
created the updateLines() function;
inserted conn1.x1 into it; conn.y1;conn1.x2;conn1.y2;
inserted into it var connect1 = new Lines();
connect1.draw();
varx3 = rec3.location.x+100; var y3 = rec3.location.y; /////
var connect2 = new Lines(rec1.location.x+100,rec1.location.y+80, rec3.location.x+100, rec3.location.y,stroke='green',id='l2');
connect2.draw();
I inserted the name of the function into the makeDraggable(evt) code;
<style>
.background {
fill: #eee;
}
.static {
cursor: not-allowed;
}
.draggable {
cursor: move;
}
</style>
-----------------------
<svg xmlns="http://www.w3.org/2000/svg" id="forDraw" viewBox="0 0 1330 420" onload="makeDraggable(evt)">
</svg>
------------------
function makeDraggable(evt) {
var svg = evt.target;
svg.addEventListener('mousedown', startDrag);
svg.addEventListener('mousemove', drag);
svg.addEventListener('mouseup', endDrag);
function getMousePosition(evt) {
var CTM = svg.getScreenCTM();
return {
x: (evt.clientX - CTM.e) / CTM.a,
y: (evt.clientY - CTM.f) / CTM.d
};
}
var selectedElement, offset;
function startDrag(evt) {
if (evt.target.classList.contains('draggable')) {
selectedElement = evt.target;
offset = getMousePosition(evt);
offset.x -= parseFloat(selectedElement.getAttributeNS(null, "x"));
offset.y -= parseFloat(selectedElement.getAttributeNS(null, "y"));
}
}
function drag(evt) {
if (selectedElement) {
var coord = getMousePosition(evt);
selectedElement.setAttributeNS(null, "x", coord.x - offset.x);
selectedElement.setAttributeNS(null, "y", coord.y - offset.y);
}
}
function endDrag(evt) {
selectedElement = null;
}
}
var recWidth = '200';
var recHeight = '80';
function Rectangle(width,height, location={x: 400, y: 50},style='draggable', fill = 'red', stroke = 'green',id='ivan') {
this.width = recWidth;
this.height = recHeight;
this.location = location;
this.style = style;
this.fill = fill;
this.stroke = stroke;
this.id = id;
this.draw = function() {
forDraw.innerHTML += `<rect width="${this.width}" height="${this.height}" x="${this.location.x}" y="${this.location.y}"
class="${this.style}" fill="${this.fill}" stroke="${this.stroke}" id="${this.id}" />`;
}
}
Rectangle.prototype.draw = function() {
if (forDraw.getElementById(this.id)) forDraw.getElementById(this.id).remove();
forDraw.innerHTML += `<rect width="${this.width}" height="${this.height}" x="${this.location.x}" y="${this.location.y}"
class="${this.style}" fill="${this.fill}" stroke="${this.stroke}" id="${this.id}" />`;
}
var rec1 = new Rectangle();
rec1.draw();
var rec2 = new Rectangle(150,90, {x: 300, y:300}, style='draggable','yellow', 'red','petro');
rec2.draw();
var rec3 = new Rectangle(150,90, {x: 550, y:300}, style='draggable','green', 'blue','dima');
rec3.draw();
function Lines(x1=rec1.location.x+100, y1=rec1.location.y+80, x2=rec2.location.x+100, y2=rec2.location.y, stroke='blue',id='l1') {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
this.stroke = stroke;
this.id = id;
this.draw = function() {
forDraw.innerHTML += `<line x1="${this.x1}" y1="${this.y1}" x2="${this.x2}" y2="${this.y2}"
stroke="${this.stroke}" id="${this.id}" />`;
}
}
var connect1 = new Lines();
connect1.draw();
var x3 = rec3.location.x+100;
var y3 = rec3.location.y;
/////
var connect2 = new Lines(rec1.location.x+100,rec1.location.y+80, rec3.location.x+100, rec3.location.y,stroke='green',id='l2');
connect2.draw();
I'm having a little trouble getting Undo/Redo to work when I'm using multiple ways to add objects. My editor has a button to add images, background, and text.
Therefore, I believe (please correct me if I'm wrong) I need to call the updateModifications() function each time I call a function that adds an image, background or text, along with each time the canvas is modified. I'm fairly certain the issue is that updateModifications is called too many times throughout the document.
function remove(){
console.log(canvas.getActiveObject());
var activeObjects = canvas.getActiveObjects();
canvas.discardActiveObject()
if (activeObjects.length) {
canvas.remove.apply(canvas, activeObjects);
}
updateModifications(true);
}
canvas.on({
'object:modified': function () {
updateModifications(true);
},
'object:added': function() {
updateModifications(true);
}
});
function addText() {
prodName = localStorage.getItem('storedName');
var textObj = new fabric.IText(prodName, {
fontSize: 22,
top: 362.5,
left: 262.5,
hasControls: true,
fontWeight: 'bold',
fontFamily: '"Montserrat",sans-serif',
fontStyle: 'normal',
centeredrotation: true,
originX: 'center',
originY: 'center'
});
canvas.insertAt(textObj,0).setActiveObject(textObj);
textToFront();
canvas.renderAll();
updateModifications(true);
}
This is creating some issues when code based on zaid's SO question;
var mods = 0;
var state = [];
function updateModifications(savehistory) {
if (savehistory === true) {
myjson = JSON.stringify(canvas);
state.push(myjson);
}
}
undo = function undo() {
if (mods < state.length) {
canvas.clear().renderAll();
canvas.loadFromJSON(state[state.length - 1 - mods - 1]);
canvas.renderAll();
mods += 1;
}
}
redo = function redo() {
if (mods > 0) {
canvas.clear().renderAll();
canvas.loadFromJSON(state[state.length - 1 - mods + 1]);
canvas.renderAll();
mods -= 1;
}
}
When you call addText(), you are calling updateModifications() and then have an event listener 'object:added' also calling updateModifications(). Either remove the event listener or simply dont call updateModifications() in addText().
Not sure then mate but this works for me in fabric 2.5:
var CanvasState = [];
var CanvasStateIndex = -1;
saveCanvas()
function refreshCanvas(){
canvas.renderAll.bind(canvas);
}
function saveCanvas(){
var newState = canvas.toJSON();
CanvasState.push(newState);
CanvasStateIndex = CanvasStateIndex +1;
while (CanvasStateIndex < (CanvasState.length)-1){
CanvasState.pop();
}
}
function undo(){
if (CanvasStateIndex >= 0){
CanvasStateIndex = CanvasStateIndex -1;
var jsonCanvas = CanvasState[CanvasStateIndex];
canvas.loadFromJSON(jsonCanvas, refreshCanvas, function(o, obj){
})
} else{
console.log('undo error CanvasStateIndex = '+CanvasStateIndex)
}
}
function redo(){
if (CanvasStateIndex < CanvasState.length -1){
CanvasStateIndex = CanvasStateIndex +1;
var jsonCanvas = CanvasState[CanvasStateIndex];
canvas.loadFromJSON(jsonCanvas, refreshCanvas, function(o, obj){
})
}else{
console.log('redo error CanvasStateIndex = '+CanvasStateIndex)
}
};
I have a background image and a separate ground image that I want to loop infinitely as long as the character is moving forward. When the character stops, the background and ground should not be moving. For similar games it is often suggested to add this.game.background.tilePosition.x -= 1
to the update function. This is not what I am looking for as it makes the background constantly move regardless of whether the character is moving. At the moment my background and ground images are repeating, but they are restricted to this.game.world.setBounds(0, 0, 1280, 800);. Any suggestions would be greatly appreciated. My code is below:
function Hero(game, x, y) {
Phaser.Sprite.call(this, game, x, y, 'player');
//rest of code for Hero constructor....
}
Hero.prototype = Object.create(Phaser.Sprite.prototype);
Hero.prototype.constructor = Hero;
//code for Hero.prototype....
PlayState = {};
PlayState.init = function () {
//code for keyboard...
};
PlayState.preload = function () {
this.game.load.json('level:1', 'data/level01.json');
this.game.load.image('ground', 'images/ground.png'); // I need this to
//repeat infinitely
this.game.load.image('background', 'images/background.png'); // I need
//this to repeat infinitely
this.game.load.spritesheet('player', 'images/player.png', 64, 64);
};
PlayState.create = function () {
this.game.world.setBounds(0, 0, 1280, 800);
this.game.background = this.game.add.tileSprite(0, 0,
this.game.world.width, 800, 'background');
this.game.ground = this.game.add.tileSprite(0, 680,
this.game.world.width, 166, 'ground');
this.game.physics.arcade.enable(this.game.ground);
this.game.ground.body.immovable = true;
this.game.ground.body.allowGravity = false;
this.game.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
this._loadLevel(this.game.cache.getJSON('level:1'));
};
PlayState.update = function () {
this.physics.arcade.collide(this.player, this.game.ground);
};
PlayState._loadLevel = function (data) {
this._spawnPlayer({player: data.player});
const GRAVITY = 1200;
this.game.physics.arcade.gravity.y = GRAVITY;
};
PlayState._spawnPlayer = function (data) {
this.player = new Hero(this.game, data.player.x, data.player.y);
this.game.add.existing(this.player);
this.game.camera.follow(this.player,
Phaser.Camera.FOLLOW_PLATFORMER);
};
window.onload = function () {
let game = new Phaser.Game(866, 520, Phaser.CANVAS, 'game');
game.state.add('play', PlayState);
game.state.start('play');
};
I have tried the following solution (see below) where I create a constructor function for the background. This idea is from an existing tutorial done in TypeScript that does exactly what I am looking for. However, I am not familiar with TypeScript so I have just tried to interpret the code from the tutorial as best I can and put it into Javascript but at the moment I am getting the error TypeError: "a is undefined"in the console. I am still learning Javascript and I can't see where I am going wrong. (I have included the keyboard logic and the character movement this time for clarity.)
function MyBackground(game, x, y) {
Phaser.Sprite.call(this, game, x, y, 'background');
}
MyBackground.prototype = Object.create(Phaser.Sprite.prototype);
MyBackground.prototype.constructor = MyScene;
MyBackground.prototype.repeatScene = function () {
this.nextFrame = new Phaser.Sprite(this.game, this.width, 0, "background", 0);
this.game.add.existing(this.nextFrame);
};
function Hero(game, x, y) {
Phaser.Sprite.call(this, game, x, y, 'player');
this.anchor.set(0.5, 0.5);
this.game.physics.enable(this);
this.body.collideWorldBounds = false;
this.animations.add('stop', [0]);
this.animations.add('run', [1, 2, 3, 4, 5], 14, true); // 14fps looped
this.animations.add('jump', [6]);
this.animations.add('fall', [7]);
this.animations.add('die', [8, 9, 8, 9, 8, 9, 8, 9], 12); // 12fps no loop
}
Hero.prototype = Object.create(Phaser.Sprite.prototype);
Hero.prototype.constructor = Hero;
Hero.prototype.move = function (direction) {
const SPEED = 200;
this.body.velocity.x = direction * SPEED;
// update image flipping & animations
if (this.body.velocity.x < 0) {
this.scale.x = -1;
}
else if (this.body.velocity.x > 0) {
this.scale.x = 1;
}
};
Hero.prototype.jump = function () {
const JUMP_SPEED = 600;
let canJump = this.body.touching.down;
if (canJump) {
this.body.velocity.y = -JUMP_SPEED;
}
return canJump;
};
Hero.prototype.bounce = function () {
const BOUNCE_SPEED = 200;
this.body.velocity.y = -BOUNCE_SPEED;
};
Hero.prototype.update = function () {
// update sprite animation, if it needs changing
let animationName = this._getAnimationName();
if (this.animations.name !== animationName) {
this.animations.play(animationName);
}
};
Hero.prototype.die = function () {
this.alive = false;
this.body.enable = false;
this.animations.play('die').onComplete.addOnce(function () {
this.kill();
}, this);
};
Hero.prototype._getAnimationName = function () {
let name = 'stop'; // default animation
if (!this.alive) {
name = 'die';
}
else if (this.body.velocity.y > 0 && !this.body.touching.down) {
name = 'fall';
}
else if (this.body.velocity.y < 0) {
name = 'jump';
}
else if (this.body.velocity.x !== 0 && this.body.touching.down ) {
name = 'run';
}
return name;
PlayState = {};
PlayState.init = function () {
this.game.renderer.renderSession.roundPixels = true;
this.keys = this.game.input.keyboard.addKeys({
left: Phaser.KeyCode.LEFT,
right: Phaser.KeyCode.RIGHT,
up: Phaser.KeyCode.UP
};
PlayState.preload = function () {
this.game.load.json('level:1', 'data/level01.json');
this.game.load.image('ground', 'images/ground.png'); // I need this to repeat infinitely
this.game.load.image('background', 'images/background.png'); // I need this to repeat infinitely
this.game.load.spritesheet('player', 'images/player.png', 64, 64);
};
PlayState.create = function () {
this.background = new MyBackground(this.game, 0, 0);
this.game.add.existing(this.MyBackground);
this.game.physics.arcade.enable(this.game.ground);
this.game.ground.body.immovable = true;
this.game.ground.body.allowGravity = false;
this.game.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
this._loadLevel(this.game.cache.getJSON('level:1'));
this.game.world.setBounds(0, 0, this.MyBackground.width * 2, 800);
};
PlayState.update = function () {
var backgroundWidth = this.game.stage.getChildAt(0).getBounds().width; //getChildAt(0) because the background is created first in create
if (this.x > backgroundWidth * .75) {
this.x = backgroundWidth * .25;
repeatScene();
};
this._handleInput();
this.physics.arcade.collide(this.player, this.game.ground);
};
PlayState._handleInput = function () {
if (this.keys.up.isDown) {
this.player.jump();
} else if (this.keys.right.isDown) { // move hero right
this.player.move(1);
} else if (this.keys.left.isDown) { // move hero left
this.player.move(-1);
} else { // stop
this.player.move(0);
}
};
PlayState._loadLevel = function (data) {
this._spawnPlayer({player: data.player});
const GRAVITY = 1200;
this.game.physics.arcade.gravity.y = GRAVITY;
};
PlayState._spawnPlayer = function (data) {
this.player = new Hero(this.game, data.player.x, data.player.y);
this.game.add.existing(this.player);
this.game.camera.follow(this.player, Phaser.Camera.FOLLOW_PLATFORMER);
};
window.onload = function () {
let game = new Phaser.Game(866, 520, Phaser.CANVAS, 'game');
game.state.add('play', PlayState);
game.state.start('play');
};
I'm referring to the official example on Phaser.io site, but have copied it here for reference below. What I want, and repeatedly fail to achieve is that the moving (with keyboard keys) starfield sprite would not collide with other vegies sprites.
I did go through the docs and looked here on SO and their forum, and it seems that the solutions should be easy enough; to just put the following code in the update() function:
game.world.bringToTop(sprite);
But, for some reason this is not working for me, so please tell me what I'm doing wrong.
var game = new Phaser.Game(800, 600, Phaser.CANVAS, 'phaser-example', { preload: preload, create: create, update: update });
function preload() {
game.load.image('sky', 'assets/skies/sky4.png');
game.load.image('starfield', 'assets/misc/starfield.jpg');
game.load.spritesheet('veggies', 'assets/sprites/fruitnveg64wh37.png', 64, 64);
}
var sprite;
var cursors;
var veggies;
function create() {
game.add.image(0, 0, 'sky');
// Enable p2 physics
game.physics.startSystem(Phaser.Physics.P2JS);
// Make things a bit more bouncey
game.physics.p2.defaultRestitution = 0.8;
// Add a sprite
sprite = game.add.tileSprite(300, 450, 200, 50, 'starfield');
// Enable if for physics. This creates a default rectangular body.
game.physics.p2.enable(sprite);
veggies = game.add.group();
veggies.enableBody = true;
veggies.physicsBodyType = Phaser.Physics.P2JS;
var vegFrames = [ 1, 3, 4, 8 ];
for (var i = 0; i < 10; i++)
{
var veg = veggies.create(game.world.randomX, game.world.randomY, 'veggies', game.rnd.pick(vegFrames));
veg.body.setCircle(26);
}
text = game.add.text(20, 20, 'move with arrow keys', { fill: '#ffffff' });
cursors = game.input.keyboard.createCursorKeys();
}
function update() {
sprite.body.setZeroVelocity();
game.world.bringToTop(veggies);
if (cursors.left.isDown)
{
sprite.body.moveLeft(400);
sprite.tilePosition.x -= 8;
}
else if (cursors.right.isDown)
{
sprite.body.moveRight(400);
sprite.tilePosition.x += 8;
}
if (cursors.up.isDown)
{
sprite.body.moveUp(400);
sprite.tilePosition.y -= 8;
}
else if (cursors.down.isDown)
{
sprite.body.moveDown(400);
sprite.tilePosition.y += 8;
}
}
edit: Solution which worked in the end thanks to SirSandman's answer:
var game = new Phaser.Game(800, 600, Phaser.AUTO, 'phaser-example', { preload: preload, create: create, update: update, render: render });
function preload() {
game.load.image('stars', 'assets/misc/starfield.jpg');
game.load.spritesheet('ship', 'assets/sprites/humstar.png', 32, 32);
game.load.image('panda', 'assets/sprites/spinObj_01.png');
game.load.image('sweet', 'assets/sprites/spinObj_06.png');
}
var ship;
var starfield;
var cursors;
function create() {
// Enable P2
game.physics.startSystem(Phaser.Physics.P2JS);
// Turn on impact events for the world, without this we get no collision callbacks
game.physics.p2.setImpactEvents(true);
game.physics.p2.restitution = 0.8;
// Create our collision groups. One for the player, one for the pandas
var playerCollisionGroup = game.physics.p2.createCollisionGroup();
var pandaCollisionGroup = game.physics.p2.createCollisionGroup();
// This part is vital if you want the objects with their own collision groups to still collide with the world bounds
// (which we do) - what this does is adjust the bounds to use its own collision group.
game.physics.p2.updateBoundsCollisionGroup();
starfield = game.add.tileSprite(0, 0, 800, 600, 'stars');
starfield.fixedToCamera = true;
var pandas = game.add.group();
pandas.enableBody = true;
pandas.physicsBodyType = Phaser.Physics.P2JS;
for (var i = 0; i < 4; i++)
{
var panda = pandas.create(game.world.randomX, game.world.randomY, 'panda');
panda.body.setRectangle(40, 40);
// Tell the panda to use the pandaCollisionGroup
panda.body.setCollisionGroup(pandaCollisionGroup);
// Pandas will collide against themselves and the player
// If you don't set this they'll not collide with anything.
// The first parameter is either an array or a single collision group.
panda.body.collides(pandaCollisionGroup);
panda.body.velocity.x = 500;
panda.body.velocity.y = 500;
}
// Create our ship sprite
ship = game.add.sprite(200, 200, 'ship');
ship.scale.set(2);
ship.smoothed = false;
ship.animations.add('fly', [0,1,2,3,4,5], 10, true);
ship.play('fly');
game.physics.p2.enable(ship, false);
ship.body.setCircle(28);
ship.body.fixedRotation = true;
// Set the ships collision group
ship.body.setCollisionGroup(playerCollisionGroup);
// The ship will collide with the pandas, and when it strikes one the hitPanda callback will fire, causing it to alpha out a bit
// When pandas collide with each other, nothing happens to them.
game.camera.follow(ship);
cursors = game.input.keyboard.createCursorKeys();
}
function hitPanda(body1, body2) {
// body1 is the space ship (as it's the body that owns the callback)
// body2 is the body it impacted with, in this case our panda
// As body2 is a Phaser.Physics.P2.Body object, you access its own (the sprite) via the sprite property:
body2.sprite.alpha -= 0.1;
}
function update() {
ship.body.setZeroVelocity();
if (cursors.left.isDown)
{
ship.body.moveLeft(200);
}
else if (cursors.right.isDown)
{
ship.body.moveRight(200);
}
if (cursors.up.isDown)
{
ship.body.moveUp(200);
}
else if (cursors.down.isDown)
{
ship.body.moveDown(200);
}
if (!game.camera.atLimit.x)
{
starfield.tilePosition.x += (ship.body.velocity.x * 16) * game.time.physicsElapsed;
}
if (!game.camera.atLimit.y)
{
starfield.tilePosition.y += (ship.body.velocity.y * 16) * game.time.physicsElapsed;
}
}
function render() {
game.debug.text('Collide with the Pandas!', 32, 32);
}
I P2 you have to set the Collisiongroups in contrast to arcarde.
I think you have to set a collisiongroup for the sprite like that:
var veggCollisionGroup = game.physics.p2.createCollisionGroup();
and then define with which other groups this group shell collide like that in the Loop:
veggies.body.setCollisionGroup(veggCollisionGroup);
veggies.body.collides(veggCollisionGroup);
And then the your tilesprite should collide with your other tilesprites.
Source:
http://phaser.io/examples/v2/p2-physics/collision-groups
if i should be wrong you will find your answer in the examples. :)
Is there any built-in support for for undo/redo in Fabric.js? Can you please guide me on how you used this cancel and repeat in [http://printio.ru/][1]
In http://jsfiddle.net/SpgGV/9/, move the object and change its size. If the object state is changed, and then we do undo/redo, its previous state will be deleted when the next change comes. It makes it easier to do undo/redo. All events of canvas should be called before any element is added to canvas. I didn't add an object:remove event here. You can add it yourself. If one element is removed, the state and list should be invalid if this element is in this array. The simpler way is to set state and list = [] and index = 0.
This will clear the state of your undo/redo queue. If you want to keep all states, such as add/remove, my suggestion is to add more properties to the element of your state array. For instance, state = [{"data":object.originalState, "event": "added"}, ....]. The "event" could be "modified" or "added" and set in a corresponding event handler.
If you have added one object, then set state[index].event="added" so that next time, when you use undo, you check it. If it's "added", then remove it anyway. Or when you use redo, if the target one is "added", then you added it. I've recently been quite busy. I will add codes to jsfiddle.net later.
Update: added setCoords() ;
var current;
var list = [];
var state = [];
var index = 0;
var index2 = 0;
var action = false;
var refresh = true;
canvas.on("object:added", function (e) {
var object = e.target;
console.log('object:modified');
if (action === true) {
state = [state[index2]];
list = [list[index2]];
action = false;
console.log(state);
index = 1;
}
object.saveState();
console.log(object.originalState);
state[index] = JSON.stringify(object.originalState);
list[index] = object;
index++;
index2 = index - 1;
refresh = true;
});
canvas.on("object:modified", function (e) {
var object = e.target;
console.log('object:modified');
if (action === true) {
state = [state[index2]];
list = [list[index2]];
action = false;
console.log(state);
index = 1;
}
object.saveState();
state[index] = JSON.stringify(object.originalState);
list[index] = object;
index++;
index2 = index - 1;
console.log(state);
refresh = true;
});
function undo() {
if (index <= 0) {
index = 0;
return;
}
if (refresh === true) {
index--;
refresh = false;
}
console.log('undo');
index2 = index - 1;
current = list[index2];
current.setOptions(JSON.parse(state[index2]));
index--;
current.setCoords();
canvas.renderAll();
action = true;
}
function redo() {
action = true;
if (index >= state.length - 1) {
return;
}
console.log('redo');
index2 = index + 1;
current = list[index2];
current.setOptions(JSON.parse(state[index2]));
index++;
current.setCoords();
canvas.renderAll();
}
Update: better solution to take edit history algorithm into account. Here we can use Editing.getInst().set(item) where the item could be {action, object, state}; For example, {"add", object, "{JSON....}"}.
/**
* Editing : we will save element states into an queue, and the length of queue
* is fixed amount, for example, 0..99, each element will be insert into the top
* of queue, queue.push, and when the queue is full, we will shift the queue,
* to remove the oldest element from the queue, queue.shift, and then we will
* do push.
*
* So the latest state will be at the top of queue, and the oldest one will be
* at the bottom of the queue (0), and the top of queue is changed, could be
* 1..99.
*
* The initialized action is "set", it will insert item into the top of queue,
* even if it arrived the length of queue, it will queue.shift, but still do
* the same thing, and queue only abandon the oldest element this time. When
* the current is changed and new state is coming, then this time, top will be
* current + 1.
*
* The prev action is to fetch "previous state" of the element, and it will use
* "current" to do this job, first, we will --current, and then we will return
* the item of it, because "current" always represent the "current state" of
* element. When the current is equal 0, that means, we have fetched the last
* element of the queue, and then it arrived at the bottom of the queue.
*
* The next action is to fetch "next state" after current element, and it will
* use "current++" to do the job, when the current is equal to "top", it means
* we have fetched the latest element, so we should stop.
*
* If the action changed from prev/next to "set", then we should reset top to
* "current", and abandon all rest after that...
*
* Here we should know that, if we keep the reference in the queue, the item
* in the queue will never be released.
*
*
* #constructor
*/
function Editing() {
this.queue = [];
this.length = 4;
this.bottom = 0;
this.top = 0;
this.current = 0;
this.empty = true;
// At the Begin of Queue
this.BOQ = true;
// At the End of Queue
this.EOQ = true;
// 0: set, 1: prev, 2: next
this._action = 0;
this._round = 0;
}
Editing.sharedInst = null;
Editing.getInst = function (owner) {
if (Editing.sharedInst === null) {
Editing.sharedInst = new Editing(owner);
}
return Editing.sharedInst;
};
/**
* To set the item into the editing queue, and mark the EOQ, BOQ, so we know
* the current position.
*
* #param item
*/
Editing.prototype.set = function (item) {
console.log("=== Editing.set");
var result = null;
if (this._action != 0) {
this.top = this.current + 1;
}
if (this.top >= this.length) {
result = this.queue.shift();
this.top = this.length - 1;
}
this._action = 0;
this.queue[this.top] = item;
this.current = this.top;
this.top++;
this.empty = false;
this.EOQ = true;
this.BOQ = false;
console.log("==> INFO : ");
console.log(item);
console.log("===========");
console.log("current: ", 0 + this.current);
console.log("start: ", 0 + this.bottom);
console.log("end: ", 0 + this.top);
return result;
};
/**
* To fetch the previous item just before current one
*
* #returns {item|boolean}
*/
Editing.prototype.prev = function () {
console.log("=== Editing.prev");
if (this.empty) {
return false;
}
if (this.BOQ) {
return false;
}
this._action = 1;
this.current--;
if (this.current == this.bottom) {
this.BOQ = true;
}
var item = this.queue[this.current];
this.EOQ = false;
console.log("==> INFO : ");
console.log(item);
console.log("===========");
console.log("current: ", 0 + this.current);
console.log("start: ", 0 + this.bottom);
console.log("end: ", 0 + this.top);
return item;
};
/**
* To fetch the next item just after the current one
*
* #returns {*|boolean}
*/
Editing.prototype.next = function () {
console.log("=== Editing.next");
if (this.empty) {
return false;
}
if (this.EOQ) {
return false;
}
this.current++;
if (this.current == this.top - 1 && this.top < this.length) {
this.EOQ = true;
}
if (this.current == this.top - 1 && this.top == this.length) {
this.EOQ = true;
}
this._action = 2;
var item = this.queue[this.current];
this.BOQ = false;
console.log("==> INFO : ");
console.log(item);
console.log("===========");
console.log("current: ", 0 + this.current);
console.log("start: ", 0 + this.bottom);
console.log("end: ", 0 + this.top);
return item;
};
/**
* To empty the editing and reset all state
*/
Editing.prototype.clear = function () {
this.queue = [];
this.bottom = 0;
this.top = 0;
this.current = 0;
this.empty = true;
this.BOQ = true;
this.EOQ = false;
};
Here is a solution that started with this simpler answer to the similar question, Undo Redo History for Canvas FabricJs.
My answer is along the same lines as Tom's answer and the other answers that are modifications of Tom's answer.
To track the state, I'm using JSON.stringify(canvas) and canvas.loadFromJSON() like the other answers and have an event registered on the object:modified to capture the state.
One important thing is that the final canvas.renderAll() should be called in a callback passed to the second parameter of loadFromJSON(), like this
canvas.loadFromJSON(state, function() {
canvas.renderAll();
}
This is because it can take a few milliseconds to parse and load the JSON and you need to wait until that's done before you render. It's also important to disable the undo and redo buttons as soon as they're clicked and to only re-enable in the same call back. Something like this
$('#undo').prop('disabled', true);
$('#redo').prop('disabled', true);
canvas.loadFromJSON(state, function() {
canvas.renderAll();
// now turn buttons back on appropriately
...
(see full code below)
}
I have an undo and a redo stack and a global for the last unaltered state. When some modification occurs, then the previous state is pushed into the undo stack and the current state is re-captured.
When the user wants to undo, then current state is pushed to the redo stack. Then I pop off the last undo and both set it to the current state and render it on the canvas.
Likewise when the user wants to redo, the current state is pushed to the undo stack. Then I pop off the last redo and both set it to the current state and render it on the canvas.
The Code
// Fabric.js Canvas object
var canvas;
// current unsaved state
var state;
// past states
var undo = [];
// reverted states
var redo = [];
/**
* Push the current state into the undo stack and then capture the current state
*/
function save() {
// clear the redo stack
redo = [];
$('#redo').prop('disabled', true);
// initial call won't have a state
if (state) {
undo.push(state);
$('#undo').prop('disabled', false);
}
state = JSON.stringify(canvas);
}
/**
* Save the current state in the redo stack, reset to a state in the undo stack, and enable the buttons accordingly.
* Or, do the opposite (redo vs. undo)
* #param playStack which stack to get the last state from and to then render the canvas as
* #param saveStack which stack to push current state into
* #param buttonsOn jQuery selector. Enable these buttons.
* #param buttonsOff jQuery selector. Disable these buttons.
*/
function replay(playStack, saveStack, buttonsOn, buttonsOff) {
saveStack.push(state);
state = playStack.pop();
var on = $(buttonsOn);
var off = $(buttonsOff);
// turn both buttons off for the moment to prevent rapid clicking
on.prop('disabled', true);
off.prop('disabled', true);
canvas.clear();
canvas.loadFromJSON(state, function() {
canvas.renderAll();
// now turn the buttons back on if applicable
on.prop('disabled', false);
if (playStack.length) {
off.prop('disabled', false);
}
});
}
$(function() {
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Set up the canvas
canvas = new fabric.Canvas('canvas');
canvas.setWidth(500);
canvas.setHeight(500);
// save initial state
save();
// register event listener for user's actions
canvas.on('object:modified', function() {
save();
});
// draw button
$('#draw').click(function() {
var imgObj = new fabric.Circle({
fill: '#' + Math.floor(Math.random() * 16777215).toString(16),
radius: Math.random() * 250,
left: Math.random() * 250,
top: Math.random() * 250
});
canvas.add(imgObj);
canvas.renderAll();
save();
});
// undo and redo buttons
$('#undo').click(function() {
replay(undo, redo, '#redo', this);
});
$('#redo').click(function() {
replay(redo, undo, '#undo', this);
})
});
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.5.0/fabric.min.js" type="text/javascript"></script>
</head>
<body>
<button id="draw">circle</button>
<button id="undo" disabled>undo</button>
<button id="redo" disabled>redo</button>
<canvas id="canvas" style="border: solid 1px black;"></canvas>
</body>
I am allowing the user to remove the last added path (in my painting application), this works fine for me:
var lastItemIndex = (fabricCanvas.getObjects().length - 1);
var item = fabricCanvas.item(lastItemIndex);
if(item.get('type') === 'path') {
fabricCanvas.remove(item);
fabricCanvas.renderAll();
}
But you could also remove the IF statement and let people remove anything.
I know its late to answer this but this is my version of implementing this. Can be useful to someone.
I have implemented this feature by saving Canvas States as JSON. Whenever a user adds or modifies an object in the Canvas, it will save the changed canvas state and maintain it in an array. This array is then manipulated whenever user clicks on Undo or Redo button.
Take a look at this link. I have also provided a working Demo URL.
https://github.com/abhi06991/Undo-Redo-Fabricjs
HTML:
<canvas id="canvas" width="400" height="400"></canvas>
<button type="button" id="undo" >Undo</button>
<button type="button" id="redo" disabled>Redo</button>
JS:
var canvasDemo = (function(){
var _canvasObject = new fabric.Canvas('canvas',{backgroundColor : "#f5deb3"});
var _config = {
canvasState : [],
currentStateIndex : -1,
undoStatus : false,
redoStatus : false,
undoFinishedStatus : 1,
redoFinishedStatus : 1,
undoButton : document.getElementById('undo'),
redoButton : document.getElementById('redo'),
};
_canvasObject.on(
'object:modified', function(){
updateCanvasState();
}
);
_canvasObject.on(
'object:added', function(){
updateCanvasState();
}
);
var addObject = function(){
var rect = new fabric.Rect({
left : 100,
top : 100,
fill : 'red',
width : 200,
height : 200
});
_canvasObject.add(rect);
_canvasObject.setActiveObject(rect);
_canvasObject.renderAll();
}
var updateCanvasState = function() {
if((_config.undoStatus == false && _config.redoStatus == false)){
var jsonData = _canvasObject.toJSON();
var canvasAsJson = JSON.stringify(jsonData);
if(_config.currentStateIndex < _config.canvasState.length-1){
var indexToBeInserted = _config.currentStateIndex+1;
_config.canvasState[indexToBeInserted] = canvasAsJson;
var numberOfElementsToRetain = indexToBeInserted+1;
_config.canvasState = _config.canvasState.splice(0,numberOfElementsToRetain);
}else{
_config.canvasState.push(canvasAsJson);
}
_config.currentStateIndex = _config.canvasState.length-1;
if((_config.currentStateIndex == _config.canvasState.length-1) && _config.currentStateIndex != -1){
_config.redoButton.disabled= "disabled";
}
}
}
var undo = function() {
if(_config.undoFinishedStatus){
if(_config.currentStateIndex == -1){
_config.undoStatus = false;
}
else{
if (_config.canvasState.length >= 1) {
_config.undoFinishedStatus = 0;
if(_config.currentStateIndex != 0){
_config.undoStatus = true;
_canvasObject.loadFromJSON(_config.canvasState[_config.currentStateIndex-1],function(){
var jsonData = JSON.parse(_config.canvasState[_config.currentStateIndex-1]);
_canvasObject.renderAll();
_config.undoStatus = false;
_config.currentStateIndex -= 1;
_config.undoButton.removeAttribute("disabled");
if(_config.currentStateIndex !== _config.canvasState.length-1){
_config.redoButton.removeAttribute('disabled');
}
_config.undoFinishedStatus = 1;
});
}
else if(_config.currentStateIndex == 0){
_canvasObject.clear();
_config.undoFinishedStatus = 1;
_config.undoButton.disabled= "disabled";
_config.redoButton.removeAttribute('disabled');
_config.currentStateIndex -= 1;
}
}
}
}
}
var redo = function() {
if(_config.redoFinishedStatus){
if((_config.currentStateIndex == _config.canvasState.length-1) && _config.currentStateIndex != -1){
_config.redoButton.disabled= "disabled";
}else{
if (_config.canvasState.length > _config.currentStateIndex && _config.canvasState.length != 0){
_config.redoFinishedStatus = 0;
_config.redoStatus = true;
_canvasObject.loadFromJSON(_config.canvasState[_config.currentStateIndex+1],function(){
var jsonData = JSON.parse(_config.canvasState[_config.currentStateIndex+1]);
_canvasObject.renderAll();
_config.redoStatus = false;
_config.currentStateIndex += 1;
if(_config.currentStateIndex != -1){
_config.undoButton.removeAttribute('disabled');
}
_config.redoFinishedStatus = 1;
if((_config.currentStateIndex == _config.canvasState.length-1) && _config.currentStateIndex != -1){
_config.redoButton.disabled= "disabled";
}
});
}
}
}
}
return {
addObject : addObject,
undoButton : _config.undoButton,
redoButton : _config.redoButton,
undo : undo,
redo : redo,
}
})();
canvasDemo.undoButton.addEventListener('click',function(){
canvasDemo.undo();
});
canvasDemo.redoButton.addEventListener('click',function(){
canvasDemo.redo();
});
canvasDemo.addObject();
My use case was drawing simple shapes akin to blueprints, so I didn't have to worry about the overhead of saving the whole canvas state. If you are in the same situation, this is very easy to accomplish. This code assumes you have a 'wrapper' div around the canvas, and that you want the undo/redo functionality bound to the standard windows keystrokes of 'CTRL+Z' and 'CTRL+Y'.
The purpose of the 'pause_saving' variable was to account for the fact that when a canvas is re-rendered it seemingly created each object one by one all over again, and we don't want to catch these events, as they aren't REALLY new events.
//variables for undo/redo
let pause_saving = false;
let undo_stack = []
let redo_stack = []
canvas.on('object:added', function(event){
if (!pause_saving) {
undo_stack.push(JSON.stringify(canvas));
redo_stack = [];
console.log('Object added, state saved', undo_stack);
}
});
canvas.on('object:modified', function(event){
if (!pause_saving) {
undo_stack.push(JSON.stringify(canvas));
redo_stack = [];
console.log('Object modified, state saved', undo_stack);
}
});
canvas.on('object:removed', function(event){
if (!pause_saving) {
undo_stack.push(JSON.stringify(canvas));
redo_stack = [];
console.log('Object removed, state saved', undo_stack);
}
});
//Listen for undo/redo
wrapper.addEventListener('keydown', function(event){
//Undo - CTRL+Z
if (event.ctrlKey && event.keyCode == 90) {
pause_saving=true;
redo_stack.push(undo_stack.pop());
let previous_state = undo_stack[undo_stack.length-1];
if (previous_state == null) {
previous_state = '{}';
}
canvas.loadFromJSON(previous_state,function(){
canvas.renderAll();
})
pause_saving=false;
}
//Redo - CTRL+Y
else if (event.ctrlKey && event.keyCode == 89) {
pause_saving=true;
state = redo_stack.pop();
if (state != null) {
undo_stack.push(state);
canvas.loadFromJSON(state,function(){
canvas.renderAll();
})
pause_saving=false;
}
}
});
You can use "object:added" and/or "object:removed" for that — fabricjs.com/events
You can follow this post:
Do we have canvas Modified Event in Fabric.js?
I know the answer is already chosen but here is my version, script is condensed, also added a reset to original state. After any event you want to save just call saveState(); jsFiddle
canvas = new fabric.Canvas('canvas', {
selection: false
});
function saveState(currentAction) {
currentAction = currentAction || '';
// if (currentAction !== '' && lastAction !== currentAction) {
$(".redo").val($(".undo").val());
$(".undo").val(JSON.stringify(canvas));
console.log("Saving After " + currentAction);
lastAction = currentAction;
// }
var objects = canvas.getObjects();
for (i in objects) {
if (objects.hasOwnProperty(i)) {
objects[i].setCoords();
}
}
}
canvas.on('object:modified', function (e) {
saveState("modified");
});
// Undo Canvas Change
function undo() {
canvas.loadFromJSON($(".redo").val(), canvas.renderAll.bind(canvas));
}
// Redo Canvas Change
function redo() {
canvas.loadFromJSON($(".undo").val(), canvas.renderAll.bind(canvas));
};
$("#reset").click(function () {
canvas.loadFromJSON($("#original_canvas").val(),canvas.renderAll.bind(canvas));
});
var bgnd = new fabric.Image.fromURL('https://s3-eu-west-1.amazonaws.com/kienzle.dev.cors/img/image2.png', function(oImg){
oImg.hasBorders = false;
oImg.hasControls = false;
// ... Modify other attributes
canvas.insertAt(oImg,0);
canvas.setActiveObject(oImg);
myImg = canvas.getActiveObject();
saveState("render");
$("#original_canvas").val(JSON.stringify(canvas.toJSON()));
});
$("#undoButton").click(function () {
undo();
});
$("#redoButton").click(function () {
redo();
});
i developed a small script for you,hope it will help you .see this demo Fiddle
although redo is not perfect you have to click minimum two time at undo button then redo work .you can easily solve this problem with giving simple conditions in redo code.
//Html
<canvas id="c" width="400" height="200" style=" border-radius:25px 25px 25px 25px"></canvas>
<br>
<br>
<input type="button" id="addtext" value="Add Text"/>
<input type="button" id="undo" value="Undo"/>
<input type="button" id="redo" value="redo"/>
<input type="button" id="clear" value="Clear Canvas"/>
//script
var canvas = new fabric.Canvas('c');
var text = new fabric.Text('Sample', {
fontFamily: 'Hoefler Text',
left: 50,
top: 30,
//textAlign: 'center',
fill: 'navy',
});
canvas.add(text);
var vall=10;
var l=0;
var flag=0;
var k=1;
var yourJSONString = new Array();
canvas.observe('object:selected', function(e) {
//yourJSONString = JSON.stringify(canvas);
if(k!=10)
{
yourJSONString[k] = JSON.stringify(canvas);
k++;
}
j = k;
var activeObject = canvas.getActiveObject();
});
$("#undo").click(function(){
if(k-1!=0)
{
canvas.clear();
canvas.loadFromJSON(yourJSONString[k-1]);
k--;
l++;
}
canvas.renderAll();
});
$("#redo").click(function(){
if(l > 1)
{
canvas.clear();
canvas.loadFromJSON(yourJSONString[k+1]);
k++;
l--;
canvas.renderAll();
}
});
$("#clear").click(function(){
canvas.clear();
});
$("#addtext").click(function(){
var text = new fabric.Text('Sample', {
fontFamily: 'Hoefler Text',
left: 100,
top: 100,
//textAlign: 'center',
fill: 'navy',
});
canvas.add(text);
});
I have answer to all your queries :) get a smile
check this link.. its all done ... copy & paste it :P
http://jsfiddle.net/SpgGV/27/
var canvas = new fabric.Canvas('c');
var current;
var list = [];
var state = [];
var index = 0;
var index2 = 0;
var action = false;
var refresh = true;
state[0] = JSON.stringify(canvas.toDatalessJSON());
console.log(JSON.stringify(canvas.toDatalessJSON()));
$("#clear").click(function(){
canvas.clear();
index=0;
});
$("#addtext").click(function(){
++index;
action=true;
var text = new fabric.Text('Sample', {
fontFamily: 'Hoefler Text',
left: 100,
top: 100,
//textAlign: 'center',
fill: 'navy',
});
canvas.add(text);
});
canvas.on("object:added", function (e) {
if(action===true){
var object = e.target;
console.log(JSON.stringify(canvas.toDatalessJSON()));
state[index] = JSON.stringify(canvas.toDatalessJSON());
refresh = true;
action=false;
canvas.renderAll();
}
});
function undo() {
if (index < 0) {
index = 0;
canvas.loadFromJSON(state[index]);
canvas.renderAll();
return;
}
console.log('undo');
canvas.loadFromJSON(state[index]);
console.log(JSON.stringify(canvas.toDatalessJSON()));
canvas.renderAll();
action = false;
}
function redo() {
action = false;
if (index >= state.length - 1) {
canvas.loadFromJSON(state[index]);
canvas.renderAll();
return;
}
console.log('redo');
canvas.loadFromJSON(state[index]);
console.log(JSON.stringify(canvas.toDatalessJSON()));
canvas.renderAll();
canvas.renderAll();
}
canvas.on("object:modified", function (e) {
var object = e.target;
console.log('object:modified');
console.log(JSON.stringify(canvas.toDatalessJSON()));
state[++index] = JSON.stringify(canvas.toDatalessJSON());
action=false;
});
$('#undo').click(function () {
index--;
undo();
});
$('#redo').click(function () {
index++;
redo();
});