I'm learning Javascript / Raphael and got stuck in what should be something really simple to solve. I need to move and rotate a number of figures with the mouse, but when I rotate them (with doubleclick) as:
rect1.dblclick(function() {
this.rotate('90');
});
the reference points I am using to keep track of the mouse also get rotated. In Processing I would use pushMatrix() and popMatrix(), but I can't find something similar for Raphael nor any simple examples using save() and restore() canvas (...not sure if they can be used here either).
Here is a working example.
var canvas = document.getElementById("the_canvas");
var paper = Raphael(canvas, '500', '500');
var rect1 = paper.rect(380,10,100,50).attr({fill: '#ddd', 'stroke': 'none', opacity: '0.8'});
var startX, startY;
function onstart() {
startX = this.attr('x');
startY = this.attr('y');
}
function onend() {
}
function onmove(dx, dy) {
this.attr({
x: startX + dx,
y: startY + dy
});
}
rect1.drag(onmove, onstart, onend);
//// ... problem here
rect1.dblclick(function() {
this.rotate('90');
});
Cheers,
Related
Hello Fellow Developers,
I'm new to react and canvas api, I want to create 10k blocks(square) of particular dimensions and each block is different from one another by ID. I have created 10k blocks
of 100 * 100 rows and columns and each box is 10 * 10.
Now what I want is to assign an ID to each box to identify that particular box that I select onClick and read data from smart contract
Once the ID is selected I want to able to select multiple boxes using input fields, eg, I selected box# 4 and in input fields I have to select width and height, so when I select width 5 block numbers 5 6 7 8 9 in x-direction will also get selected, similarly if I select height 2 then including 4 one more box is selected in y-direction.
Now these selection can only happen if that box is available to select if its value is false, no-one can select that box no matter the height and width.
I don't know how to assign an ID please anyone can help me in this, It'll be great help
Reference Website to understand my points: https://milliondollartokenpage.com/
Code is attached that I've done so far:
https://jsfiddle.net/ahmedzafar/d3twnfe1/1/
Thanks in advance:)
Canvas has no concept of "ID" for the elements that are drawn, you have to keep track of that...
But canvas as good options to help us deal with this obstacle, my favorite is Path2d:
https://developer.mozilla.org/en-US/docs/Web/API/Path2D/Path2D
Here is a starting point:
class Shape {
constructor(x, y) {
this.path = new Path2D();
this.path.arc(x, y, 16, 0, 2 * Math.PI);
}
draw(ctx, pos) {
ctx.beginPath();
ctx.fillStyle = ctx.isPointInPath(this.path, pos.x, pos.y)? "red": "tan"
ctx.fill(this.path);
}
}
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return { x: evt.clientX - rect.left, y: evt.clientY - rect.top };
}
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
shapes = []
for (var i = 0; i < 6; i++) {
shapes.push(new Shape(50 + i * 40, 40))
}
canvas.addEventListener('mousemove', function(evt) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
shapes.forEach(s => { s.draw(ctx, getMousePos(canvas, evt)) })
}, false);
shapes.forEach(s => { s.draw(ctx, { x: 0, y: 0}) })
<canvas id="canvas" width=500 height=150></canvas>
On that example we can see I used Path2D and the isPointInPath to determine if the mouse is on an element, and we change the fill color accordingly.
Here is another example, this time we detect if user clicked in one of our shapes, and just output some info to the console.
class Shape {
constructor(name, x, y) {
this.obj = {name, x, y}
this.path = new Path2D();
this.path.rect(x, y, 50, 50);
}
draw(ctx) {
ctx.beginPath();
ctx.stroke(this.path);
ctx.strokeText(this.obj.name, this.obj.x +10, this.obj.y +20)
}
}
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return { x: evt.clientX - rect.left, y: evt.clientY - rect.top };
}
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
shapes = []
shapes.push(new Shape("A", 10, 10))
shapes.push(new Shape("B", 80, 10))
shapes.push(new Shape("C", 150, 10))
canvas.addEventListener('mouseup', function(evt) {
var pos = getMousePos(canvas, evt);
shapes.forEach(s => {
if (ctx.isPointInPath(s.path, pos.x, pos.y))
console.log(s.obj)
})
}, false);
shapes.forEach(s => { s.draw(ctx) })
<canvas id="canvas" width=300 height=100></canvas>
My examples are not React/NodeJs but...
I hope you can easily integrate this concept in your project.
I had a bit of time to play with your sample, I made some changes here two versions:
https://jsfiddle.net/heldersepu/078ztk5h/28/
https://jsfiddle.net/heldersepu/078ztk5h/47/
I want to replicate the basic functionality of a free transform tool (no rotation), by dragging on the border of a easeljs Shape and adjusting the container to match it. I'm currently using the scaleX and scaleY properties and it sort of works but is not quite right.
If you do one scaling transformation it works pretty well. However if you release, then do another scaling transformation, it jumps very glitchily, and can occasionally break sending the x/y coordinates all the way to stage 0. Any help on this issue would be great!
http://jsfiddle.net/frozensoviet/dsczvrpw/13/
//circle
var circle = new createjs.Shape(new createjs.Graphics()
.beginFill("#b2ffb2")
.drawCircle(0, 0, 50));
circle.setBounds(0, 0, 50, 50);
//create the border as a seperate object
var cBorder = new createjs.Shape(new createjs.Graphics().setStrokeStyle(10)
.beginStroke("#000").drawCircle(0, 0, 50));
cBorder.setBounds(0, 0, 50, 50);
//add both to the container
circleContainer.addChild(circle);
circleContainer.addChild(cBorder);
var cWidth = circleContainer.getBounds().width;
var cHeight = circleContainer.getBounds().height;
//find initial mouse position relative to circle center
cBorder.on("mousedown", function (evt) {
//initial mouse pos
this.initial = {
x: Math.abs(-(circleContainer.x - evt.stageX)),
y: Math.abs(circleContainer.y - evt.stageY)
};
});
//set the relevant circle axis scale to ratio of mouse pos/initial mouse pos
cBorder.on("pressmove", function (evt) {
//current moouse pos
this.offset = {
x: Math.abs(-(circleContainer.x - evt.stageX)),
y: Math.abs(circleContainer.y - evt.stageY)
};
if (this.initial.x > this.initial.y) {
//sides
circleContainer.scaleX = this.offset.x / this.initial.x;
} else if (this.initial.x < this.initial.y) {
//top/bottom
circleContainer.scaleY = this.offset.y / this.initial.y;
} else {
//diagonals
circleContainer.scaleX = this.offset.x / this.initial.x;
circleContainer.scaleY = this.offset.y / this.initial.y;
}
stage.update();
});
The issue is your initial calculations don't account for the change in the scale of the circle. You would have to transform the coordinates using localToGlobal. Fortunately, there is an even easier way:
this.initial = {
x: Math.abs(evt.localX),
y: Math.abs(evt.localY)
};
You can also turn on ignoreScale on the border, which makes it not stretch:
createjs.Graphics().setStrokeStyle(10,null,null,null,true) // The 5th argument
Lastly, your bounds setting might work for your demo, but it is not correct. Your circle draws from the center, so it should be:
cBorder.setBounds(-25, -25, 50, 50);
Here is an updated fiddle: http://jsfiddle.net/tfy1sjnj/3/
I have a canvas that is drawing an image and clipping to create the effect that the image is being revealed. I have the code working properly I have tried using a debouce method and also rAF to increase the canvas rendering performance but I only saw small gains if any.
I suspect the way I am iterating through my array of x and y coordinates could be the issue.
It seems to lag quite a bit when it is out putting the array in console about the same rate as the circle appear on the screen.
Here is the redraw function:
function redraw(mouse) {
m.push(mouse);
m.forEach(function (a) {
ctx.drawImage(img, 0, 0);
ctx.beginPath();
ctx.rect(0, 0, 500, 500);
ctx.arc(a.x, a.y, 70, 0, Math.PI * 2, true);
ctx.clip();
ctx.fillRect(0, 0, 500, 500)
})
}
I guess what I am looking for is some advice to speed up my code so the rendering of the circles seems more like drawing.
Here is the working demo -> http://jsfiddle.net/naeluh/4h7GR/
There are several issues here :
• Your mouse code is a nightmare, traversing the DOM on every move.
• You are redrawing everything on each move.
So i suggest a way more efficient solution :
• stack two canvases, the one below is your image, the one on top is the mask.
• Deal efficiently with the mouse.
• Only clear part of the mask canvas on mouse move : just one circle drawn on the mask canvas for each move.
(for that i used a globalCompositeOperation = 'destination-out' )
Result is perfectly smooth either on Firefox, Chrome, or Safari .
(tested on mac OS).
the fiddle :
(you have to click to clear)
http://jsfiddle.net/gamealchemist/4h7GR/22/
html
<canvas style='position: absolute; top: 0;left: 0;' id="canvas1" width="500" height="500"></canvas>
<canvas style='position: absolute;top: 0;left: 0;' id="canvas2" width="500" height="500"></canvas>
js
var can = document.getElementById("canvas1");
var ctx = can.getContext("2d");
var can2 = document.getElementById("canvas2");
var ctx2 = can2.getContext("2d");
var img = new Image();
img.onload = function () { ctx.drawImage(img,0,0); };
img.src = "http://placekitten.com/500/500";
ctx2.fillStyle='#000';
ctx2.fillRect(0,0,500,500);
ctx2.globalCompositeOperation = 'destination-out';
function clearThis(x,y) {
console.log('toto');
ctx2.fillStyle='#F00000';
ctx2.beginPath();
ctx2.arc(x, y, 70, 0, Math.PI * 2, true);
ctx2.fill();
}
var mouse = {
x: 0,
y: 0,
down: false
};
function setupMouse(canvas, onMouseMove, preventDefault) {
var rectLeft, rectTop;
var hook = canvas.addEventListener.bind(canvas);
var mouseDown = updateMouseStatus.bind(null, true);
var mouseUp = updateMouseStatus.bind(null, false);
hook('mousedown', mouseDown);
hook('mouseup', mouseUp);
hook('mousemove', updateCoordinates);
hook('scroll', updateRect);
// var mouseOut = function() { mouse.down=false ; } ;
// hook('mouseout', mouseOut);
function updateMouseStatus(b, e) {
mouse.down = b;
updateCoordinates(e);
if (preventDefault) {
e.stopPropagation();
e.preventDefault();
}
}
function updateCoordinates(e) {
mouse.x = (e.clientX - rectLeft);
mouse.y = (e.clientY - rectTop);
onMouseMove(mouse.x, mouse.y);
}
function updateRect() {
var rect = canvas.getBoundingClientRect();
rectLeft = rect.left;
rectTop = rect.top;
}
updateRect();
};
setupMouse(can2, clearThis, true);
The Above Code will do Fine .. But nEed some Editing
I have Edited the Code in Fiddle ..and i beleive there Is some Improvement in perforamnce
So I looked a little more and found a bug as expected.
The main problem is the accumulation of the drawing path.
Why Need to add clip and fillRect at every go ..Do it at last... the Major issue solved,Like
can.addEventListener("mousemove", function (e) {
var mouse = getMouse(e, can);
requestAnimationFrame(function () {
redraw(mouse);
ctx.clip();
ctx.fillRect(0, 0, 500, 500);
console.log(mouse);
});
}, false);
2.The Updated JSFiidle is
UpdatedFiddle
I am trying to make an image object rotate in accordance to the mouse being clicked down on that image and the angle of the mouse to the center of the object.
Think of a unit circle and having the image being rotated about the circle based on where the mouse is on that circle.
I currently have
var stage = new Kinetic.Stage({
container: "container",
width: 800,
height: 500,
id: "myCanvas",
name: "myCanvas"
});
var layer = new Kinetic.Layer();
//gets the canvas context
var canvas = stage.getContainer();
var mousePosX;
var mousePosY;
var mouseStartAngle;
var selectedImage;
var mouseAngle;
var mouseStartAngle;
console.log(canvas)
var shiftPressed
window.addEventListener('keydown', function (e) {
if (e.keyCode == "16") {
shiftPressed = true;
}
console.log(shiftPressed)
}, true);
window.addEventListener('keyup', function (e) {
if (e.keyCode == "16") {
shiftPressed = false;
}
console.log(shiftPressed)
}, true);
function drawImage(imageObj) {
var dynamicImg = new Kinetic.Image({
image: imageObj,
x: stage.getWidth() / 2 - 200 / 2,
y: stage.getHeight() / 2 - 137 / 2,
width: 100, //imageObj.width,
height: 100, // imageObj.height,
draggable: true,
offset: [50,50] //[(imageObj.width/2), (imageObj.height/2)]
});
dynamicImg.on('mousedown', function () {
selectedImage = this;
console.log("x: " + this.getX())
console.log("y: " + this.getY())
var mouseStartXFromCenter = mousePosX - (this.getX() + (this.getWidth() / 2));
var mouseStartYFromCenter = mousePosY - (this.getY() + (this.getHeight() / 2));
mouseStartAngle = Math.atan2(mouseStartYFromCenter, mouseStartXFromCenter);
if (shiftPressed) {
//console.log("trying to switch draggable to false");
//console.log(this)
this.setDraggable(false);
}
});
dynamicImg.on('mouseup mouseout', function () {
//console.log('mouseup mouseout')
this.setDraggable(true);
});
dynamicImg.on('mouseover', function () {
document.body.style.cursor = 'pointer';
});
dynamicImg.on('mouseout', function () {
document.body.style.cursor = 'default';
});
imageArray.push(dynamicImg);
layer.add(dynamicImg);
stage.add(layer);
}
var imageObj = new Image();
imageObj.onload = function () {
drawImage(this);
};
imageObj.src = 'http://localhost:60145/Images/orderedList8.png';
function getMousePos(evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
canvas.addEventListener('mouseup', function () {
selectedImage = undefined;
});
canvas.addEventListener('mousemove', function (evt) {
mousePos = getMousePos(evt);
//console.log('Mouse position: ' + mousePos.x + ',' + mousePos.y)
mousePosX = mousePos.x;
mousePosY = mousePos.y;
if (selectedImage != undefined) {
mouseXFromCenter = mousePosX - selectedImage.getX();
mouseYFromCenter = mousePosY - selectedImage.getY();
mouseAngle = Math.atan2(mouseYFromCenter, mouseXFromCenter);
var rotateAngle = mouseAngle - mouseStartAngle;
selectedImage.setRotation(rotateAngle);
}
}, false);
the code has a couple things to it.
-It should only allow a rotate if 'shift' is pressed and mousedown event happens on an image.
-it needs to maintain the dynamic image drawing as they will be populating the canvas dynamically over the life of the page.
here is a good example of something similar i want to happen, but just simply cannot get it to work in canvas/kineticjs.
http://jsfiddle.net/22Feh/5/
I would go simpler way using dragBoundFunc. http://jsfiddle.net/bighostkim/vqGmL/
dragBoundFunc: function (pos, evt) {
if (evt.shiftKey) {
var x = this.getX() - pos.x;
var y = this.getY() - pos.y;
var radian = Math.PI + Math.atan(y/x);
this.setRotation(radian);
return {
x: this.getX(),
y: this.getY()
}
} else {
return pos;
}
}
My mistake, I misread your question. You were essentially missing one line:
layer.draw();
Hold shift and move the mouse, you'll see it rotates nicely.
http://jsfiddle.net/WXHe6/2/
canvas.addEventListener('mousemove', function (evt) {
mousePos = getMousePos(evt);
//console.log('Mouse position: ' + mousePos.x + ',' + mousePos.y)
mousePosX = mousePos.x;
mousePosY = mousePos.y;
if (selectedImage != undefined) {
mouseXFromCenter = mousePosX - selectedImage.getX();
mouseYFromCenter = mousePosY - selectedImage.getY();
mouseAngle = Math.atan2(mouseYFromCenter, mouseXFromCenter);
var rotateAngle = mouseAngle - mouseStartAngle;
selectedImage.setRotation(rotateAngle);
layer.draw(); // <--------------- right here
}
}, false);
You should be more clear what your .on() events do. In your code, the mousedown on the shape doesn't do anything other than calculate an mouseStartAngle, but doesn't do anything. And your mouseUp on the shape event doesn't do much either. It's your browser/client mousedown/mouseup that do all the work, and that's why it rotates properly on some clicks and not others.
For setting the rotation you have many options, here are two:
Option one: set the radians manually on mousedown
dynamicImg.on('mousedown', function () {
dynamicImg.setRotation(mouseStartAngle); //set the rotation degree
layer.draw(); // redraw the layer
}
Option two: let kineticJS animate the rotation for you
dynamicImg.on('mousedown', function () {
dynamicImg.transitionTo({
duration: 1, // length of animation in seconds
rotation: mouseStartAngle // your angle, in radians
});
}
your updated jsfiddle: http://jsfiddle.net/WXHe6/1/
Here are some additional notes:
The reason you are having trouble is because your code is structured is a bit of a messy manner, sorry to say. You are mixing browser events with and adding listeners to the canvas rather than using built-in kineticJS functionality, for example, you could use stage.getUserPosition() to get the mouse coordinates. Unless you have some dire need to structure your project this way, try to avoid it.
What I do like is that you have created functions to break up your code, but on the down-side you have multiple functions doing the same thing, like calculating and updating the rotation angle. Why not just make one function to get the angle?
Some Tips:
Try using mainly KineticJS for functionality (I know you'll need event listeners for buttons like SHIFT). What I mean is, use things like stage.getUserPosition(); to get the mouse/touch coordinates, rather than evt.clientX, it's cleaner, and the work has been done for you already, no need to re-invent the wheel.
Make one function for setting/getting the rotation for a shape. Then you can call it whenever you want. (use less repetitive code)
If you don't want the shape to be draggable when shift is clicked, you should set that before an item is clicked. You have button listeners so you can either disable rotation for all shapes when shift is pressed down, or just for a shape on that is under the cursor.
imageArray.push(dynamicImg); not sure if you really need this, if you need to access all the elements as an array, you could alway do layer.getChildren(); to get all the images in the layer, or access them numerically like layer.getChildren()[0]; (in creation order)
I am trying to build a floorplan model in Canvas. Currently I have a grid image in my canvas and have the ability for users to draw lines by clicking and dragging their mouse. But, this doesn't guarantee straight lines.
Is there anyway I can provide input fields in the html page for users to input the beginning and ending x and y coordinates of the lines, and have it updated in my canvas code? I'm a beginner when it comes to JS/AJAX, so any remedial help is appreciated :)
Right now, this is the section that dictates how the lines get drawn:
$(document).ready(function() {
var canvas = document.getElementById('canvas');
var context = canvas.getContext("2d");
if(canvas.getContext) {
$('#canvas').mousedown(function (evt) {
var offset = $('#canvas').offset();
context.beginPath();
context.moveTo(20, 20);
});
$(document).mousemove(function(evt) {
var offset = $('#canvas').offset();
context.lineTo(evt.pageX - offset.left, evt.pageY - offset.top);
context.stroke();
}).mouseup(function() {
$(document).unbind('mousemove');
$(document).unbind('mouseup');
});
$('#clearcanvas').click(function () {
context.clearRect(0, 0, 600, 580);
});
}
});
I suspect it involves modifying following code:
context.lineTo(evt.pageX - offset.left, evt.pageY - offset.top);
Very simply you could use 4 input fields and take the value of each when a button is pressed
button.addEventListener('click', function() {
ctx.beginPath();
ctx.moveTo(x1.value, y1.value);
ctx.lineTo(x2.value, y2.value);
ctx.stroke();
}, false);
http://jsfiddle.net/TeGGx/