I am trying to flip a group (horizontally) using Konvajs.Following the advice of this post, I am using the scaleX property. This works---mostly. However, it doesn't flip around the center.
function reverse(shape){
var layer = shape.getLayer();
var oldScaleX = shape.attrs.scaleX;
var width = shape.getClientRect().width;
var adjuster = oldScaleX * width;
var startX = shape.attrs.x + adjuster;
var startY = shape.attrs.y;
shape.scaleX(-oldScaleX);
shape.position({x: startX, y: startY});
layer.draw();
};
I tried using the offsetX and offsetY properties like in this post, but it doesn't seem to do anything.
shape.offsetX(shape.width()/2);
shape.offsetY(shape.height()/2);
shape.x(shape.x() - shape.attrs.offsetX);
shape.y(shape.y() - shape.attrs.offsetY);
The shapes will flip initially, but if they are rotated first, they tend to jump around.
Codepen
Based on my function posted in this other question, here is a snippet to rotate a shape around its centre. In this case the shape is a group composed of 2 rects but any shape will work.
// Set up the canvas / stage
var stage = new Konva.Stage({container: 'container', width: 600, height: 200});
var layer = new Konva.Layer({draggable: false});
stage.add(layer);
var shape = new Konva.Group({x: 80, y: 40, width: 60, height: 60 });
var r1 = new Konva.Rect({ x:10, y:10, width: 70, height: 40, fill: 'cyan'})
var r2 = new Konva.Rect({ x:50, y:10, width: 40, height: 100, fill: 'cyan'})
shape.add(r1)
shape.add(r2)
layer.add(shape)
// set the group rotate point. Note - x,y is relative to top-left of stage !
var pos = shape.getClientRect();
RotatePoint(shape, {x: pos.x + pos.width/2, y: pos.y + pos.height/2});
stage.draw();
// This is where the flip happens
var scaleX = 1, inc = -0.2; // set inc = 1 for full flip in one call
function flipShape(){
scaleX = scaleX + inc;
shape.scaleX(scaleX); // and that's it
// fun with colors on front and back of shape
r1.fill(scaleX < 0 ? 'magenta' : 'cyan');
r2.fill(scaleX < 0 ? 'magenta' : 'cyan');
$('#info').html('Set ScaleX(' + scaleX.toFixed(2) + ')'); // What's happening?
layer.draw(); // show the change
inc = (scaleX % 1 === 0 ? -1 * inc : inc); // decide next increment, reversed at 1 & -1
}
$('#flip').on('click', function(e){
flipShape(); // click button - flip shape a bit
})
/*
Set the offset for rotation to the given location and re-position the shape
*/
function RotatePoint(shape, pos){ // where pos = {x: xpos, y: yPos}
var initialPos = shape.getAbsolutePosition();
var moveBy = {x: pos.x - initialPos.x, y: pos.y - initialPos.y};
// offset is relative to initial x,y of shape, so deduct x,y.
shape.offsetX(moveBy.x);
shape.offsetY(moveBy.y);
// reposition x,y because changing offset moves it.
shape.x(initialPos.x + moveBy.x);
shape.y(initialPos.y + moveBy.y);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/konva/2.5.1/konva.js"></script>
<p>Clck the button to progressively flip a shape using scaleX <button id='flip'>Flip a bit</button> <span id='info'></span></p>
<div id='container' style="position: absolute; top: 0; z-index: -1; display: inline-block; width: 600px, height: 200px; background-color: silver; "></div>
Related
I'm trying to implement a rotation and zoom feature with a slider. I need an image to always rotate from the center of the viewport. The main idea is changing the position and offset of the stage after drag event. Here's what I've tried
const width = window.innerWidth
const height = window.innerHeight
// scale is temp
let stage = new Konva.Stage({
container: 'canvas',
width,
height,
draggable: true,
offset: {
x: width / 2,
y: height / 2
},
x: width / 2,
y: height / 2
})
let mapLayer = new Konva.Layer()
let imageObj = new Image()
imageObj.onload = function () {
let map = new Konva.Image({
image: imageObj,
prevX: 0,
prevY: 0
})
layer.add(map)
stage.add(mapLayer)
stage.on('dragstart', () => {
map.prevX = map.getAbsolutePosition().x
map.prevY = map.getAbsolutePosition().y
})
stage.on('dragend', () => {
let curX = map.getAbsolutePosition().x
let curY = map.getAbsolutePosition().y
let deltaX = Math.abs(map.prevX - curX)
let deltaY = Math.abs(map.prevY - curY)
if (curX > map.prevX) {
stage.offsetX(stage.offsetX() - deltaX)
stage.x(stage.x() - deltaX)
} else {
stage.offsetX(stage.offsetX() + deltaX)
stage.x(stage.x() + deltaX)
}
if (curY > map.prevY) {
stage.offsetY(stage.offsetY() - deltaY)
stage.y(stage.y() - deltaY)
} else {
stage.offsetY(stage.offsetY() + deltaY)
stage.y(stage.y() + deltaY)
}
stage.draw()
})
}
(if you want a full source code, clone it from here and run yarn run dev from terminal, the app lives on localhost:3000
It works fine when the image is in the normal position (not zoomed and rotated yet) but after ANY kind of rotation or zooming, dragging the stage will cause the stage to be re-positioned in a weird fashion (the offset is still correct though). How can I set position and offset correctly?
The solution to your initial issue below.
Note that I put a circle and a tooltip as well to a single layer to be able to drag them as a single one. Also, it should be added after map to have binding work.
import Konva from 'konva'
import map from './../resources/unpar.svg'
const width = window.innerWidth
const height = window.innerHeight
// scale is temp
let stage = new Konva.Stage({
container: 'canvas',
width,
height,
offset: {
x: width / 2,
y: height / 2
},
x: width / 2,
y: height / 2
})
let layer = new Konva.Layer({draggable: true})
let testCircle = new Konva.Circle({
x: 633,
y: 590,
radius: 10,
fill: 'white',
stroke: 'black'
})
testCircle.on('mousemove', function () {
tooltip.position({
x: testCircle.x() - 90,
y: testCircle.y() - 50
})
tooltip.text('Pintu Depan Gedung 10')
tooltip.show()
layer.batchDraw()
})
testCircle.on('mouseout', function () {
tooltip.hide()
layer.draw()
})
var tooltip = new Konva.Text({
text: '',
fontFamily: 'Calibri',
fontSize: 18,
padding: 5,
textFill: 'white',
fill: 'black',
alpha: 0.75,
visible: false
})
let imageObj = new Image()
imageObj.onload = function () {
const map = new Konva.Image({
image: imageObj,
})
layer.add(map)
layer.add(testCircle)
layer.add(tooltip)
stage.add(layer)
}
imageObj.src = map
export default stage
I am new to Kinetic js and having issue figuring out how to stop the image from moving outside the boundary which then creates a white space
Here is my code so far
//photo layer
var photoGroup = new Kinetic.Group({ x: layout.photoGroup.x, y: layout.photoGroup.y, draggable: true });
var Photolayer = new Kinetic.Layer();
var cropArea = new Kinetic.Rect({
x: layout.cropAreaRect.x,
y: layout.cropAreaRect.y,
width: layout.cropAreaRect.width,
height: layout.cropAreaRect.height,
fill: "white",
listening: false
});
Photolayer.add(cropArea);
Photolayer.add(photoGroup);
stage.add(Photolayer);
Here is a working code snippet illustrating a dragBoundFunc().
The gold rect is the area we intend to restrict the drag within. The blue rect shows the area we compute - different from the gold because we use the topleft of the draggable as the basis for our maths and we have to account for its own width and height. The red block could be an image or any Konvajs element.
Drag the red block!
// add a stage
var s = new Konva.Stage({
container: 'container',
width: 800,
height: 600
});
// add a layer
var l = new Konva.Layer();
s.add(l);
// Add a green rect to the LAYER just to show boundary of the stage.
var green = new Konva.Rect({stroke: 'lime', width: 800, height: 600, x: 0, y: 0});
l.add(green);
// Add a gold rect to the LAYER just to give some visual feedback on intended drag limits
var gold = new Konva.Rect({stroke: 'gold', width: 400, height: 200, x: 55, y: 55});
l.add(gold);
// Add a red rect to act as our draggable
var red = new Konva.Rect({fill: 'red', stroke: 'red', width: 40, height: 50, x: 65, y: 65, draggable: true,
dragBoundFunc: function(pos) {
var newX = pos.x;
if (newX < minX){ newX = minX};
if (newX > maxX){ newX = maxX};
var newY = pos.y;
if (newY < minY){ newY = minY};
if (newY > maxY){ newY = maxY};
$("#info").html('Info: Pos=(' + newX + ', ' + newY + ') range X=' + minX + ' - ' + maxX + ' Y=' + minY + ' - ' + maxY);
return {
x: newX,
y: newY
}
}
});
l.add(red);
// calculate the drag boundary once for performance - we need to have the draggable element size for this !
var goldPos = gold.getAbsolutePosition()
var minX = goldPos.x;
var maxX = goldPos.x + gold.width() - red.width();
var minY = goldPos.y;
var maxY = goldPos.y + gold.height() - red.height();
// Add a blue rect to the LAYER just show the computed drag limits - note the subtraction of the draggable element dimensions from maxX & Y. Note the 1px adjustment is to avoid overlapping the gold rect in the demo and is not required for live.
var blue = new Konva.Rect({stroke: 'blue', opacity: 0.2, width: 399 - red.width(), height: 199 - red.height(), x: 56, y: 56});
l.add(blue);
// bring red back to top so we can drag it
red.moveToTop();
l.draw(); // redraw the layer it all sits on.
#container {
border: 1px solid #ccc;
}
#info {
height: 20px;
border-bottom: 1px solid #ccc;
}
#hint {
font-style: italic;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/konvajs/konva/1.6.3/konva.min.js"></script>
<body>
<div id='hint'>Hint: green is stage, gold is intended drag limit, blue is computed drag limit.<br/>Drag the red block!</div>
<div id='info'>Info:</div>
<div id="container"></div>
</body>
As an example of dragBoundFunc:
const dragRect = new Konva.Rect({
x: window.innerWidth / 2 - 50,
y: window.innerHeight / 2 - 50,
width: 100,
height: 100,
fill: 'green',
draggable: true,
dragBoundFunc: (pos) => {
const leftBound = cropRect.x();
const rightBound = cropRect.x() + cropRect.width() - dragRect.width();
pos.x = Math.max(leftBound, Math.min(rightBound, pos.x));
const topBound = cropRect.y();
const bottomBound = cropRect.y() + cropRect.height() - dragRect.height();
pos.y = Math.max(topBound, Math.min(bottomBound, pos.y));
return pos;
}
});
Demo: http://jsbin.com/jilagadeqa/1/edit?js,output
I have two boxes positioned on a canvas that I am trying to center. You can view this on JS fiddle: http://jsfiddle.net/FVU47/5/
My canvas has 1000 height and 1000 width as follows:
<canvas id="myCanvas" width="1000" height="1000" style="border:3px solid #385D8A; outline:1px solid #7592B5; margin-left: 0px; margin-top: 0px; background-color: #B9CDE5"></canvas>
I am then attempting to center the two boxes with the following code, which would place either box1 or box2 in the center of the canvas, depending on whether I click on "Go to Box 1" or "Go to Box 2" (see the bottom of the JSFiddle Result quadrant:
$(document).ready(function(){
$("#box1click").click(function(){
if (rect1.x <= 500) {
positionWidthSet = Math.abs(rect1.x - canvas.width/2) + rect1.x;
}
else{
positionWidthSet = Math.abs(Math.abs(rect1.x - canvas.width/2) + rect1.x)
}
if (rect1.y >= 500) {
positionHeightSet = Math.abs(rect1.y -canvas.height/2);
}
else{
positionHeightSet = Math.abs(Math.abs(rect1.y - canvas.height/2) + rect1.y);
}
positionCanvasContext(positionWidthSet,positionHeightSet);
});
});
$(document).ready(function(){
$("#box2click").click(function(){
if (rect2.x <= 500) {
positionWidthSet = Math.abs(rect2.x - canvas.width/2) + rect2.x;
}
else{
positionWidthSet = Math.abs(Math.abs(rect2.x - canvas.width/2) + rect2.x)
}
if (rect2.y >= 500) {
positionHeightSet = Math.abs(rect2.y -canvas.height/2);
}
else{
positionHeightSet = Math.abs(Math.abs(rect2.y - canvas.height/2) + rect2.y);
}
positionCanvasContext(positionWidthSet,positionHeightSet);
});
});
Currently, clicking on either "Go to Box 1" or "Go to Box 2" does not center the canvas around either Box 1 or Box 2, even though experimenting with my formulas in the console would seem to indicate otherwise.
Here's one way: http://jsfiddle.net/m1erickson/GpMsk/
Put all your rects in an array:
var rects=[];
rects.push({
x: 103,
y: 262,
w: 200,
h: 100,
fillStyle: 'red',
hovered: false
});
rects.push({
x: 484,
y: 170,
w: 200,
h: 100,
fillStyle: 'blue',
hovered: false
});
Create a draw() function that draws all rects in the rects[] array
Draw all rects with a specified x/y offset to their original x/y:
function draw(offsetX,offsetY){
ctx.clearRect(0,0,canvas.width,canvas.height);
for(var i=0;i<rects.length;i++){
var r=rects[i];
ctx.fillStyle=r.fillStyle;
ctx.fillRect(r.x+offsetX,r.y+offsetY,r.w,r.h);
}
}
When you click a button, calculate the offsets necessary to pull the specified rectangle to the center of the canvas
Then redraw all rects with the calculated offsets.
var centerX=canvas.width/2;
var centerY=canvas.height/2;
function centerThisRect(rectsIndex){
var r=rects[rectsIndex];
var offsetX=centerX-r.x-r.w/2;
var offsetY=centerY-r.y-r.h/2;
draw(offsetX,offsetY);
}
I am new to Kineticjs and not a great javascript coder so I am hoping to get some help with this example.
http://jsfiddle.net/pwM8M/
I am trying to store the x axis on doors so when a redraw unrelated to the doors is done they don't go back to the default position. (multiple types of doors and windows too)
Each form element can have multiple quantities (more than one door) so I need a way to store/retrieve the data currently contained in the alert on jsfiddle.
I did some research but have come up empty. Can anyone help with what I have provided?
function OH(x,y,w,h) {
var oh = new Kinetic.Text({
x:x,
y: y,
width: w*scale,
height: h*scale,
stroke: wtc,
strokeWidth: 1,
fill: 'brown',
fontSize: 6,
fontFamily: 'Calibri',
padding:4,
text: w+' x '+h+' OH\n\n<- DRAG ->',
align: 'center',
textFill: 'white',
draggable: true,
dragConstraint:'horizontal',
dragBounds: {
left:xoffset, right: xoffset+width+length-(w*scale)
}
});
oh.on('dragend', function(e) {
alert(oh.getPosition().x);
});
window.south.add(oh);
}
Thanks,
I have fixed sized 40x40 rectangle here in which i am using dragging function. try this
var box = new Kinetic.Rect({
x: parseFloat(area.size.x),
y: parseFloat(area.size.y),
width: 40, //parseFloat(area.size.width)
height: 40,
fill: area.color,
stroke: 'black',
strokeWidth: 1,
opacity: 0.6,
id : area.id + id,
draggable: true,
dragBoundFunc: function(pos) {
// x
var newX = pos.x < 0 ? 40 : pos.x;
var newX = newX > _self.canvasWidth - area.size.width ? _self.canvasWidth - area.size.width : newX;
// y
var newY = pos.y < 0 ? 40 : pos.y;
var newY = newY > _self.canvasHeight - area.size.height ? _self.canvasHeight - area.size.height : newY;
return {
x: newX,
y: newY
};
Use this function
box.on('dragend', function() {
_self.draw = false;
_self.dragArea(area, box);
});
and try this to play with x y coordinates
dragArea: function(area, box){
if(box){
$$('#' + this.formId + ' [name="areas[' + area.id + '][size][x]"]').first().value = parseInt(box.attrs.x);
$$('#' + this.formId + ' [name="areas[' + area.id + '][size][y]"]').first().value = parseInt(box.attrs.y);
area.size.x = parseInt(box.attrs.x);
area.size.y = parseInt(box.attrs.y);
}
},
Create a new array before the function, like so:
var xArray = new Array();
then inside your function
oh.on('dragend', function(e) {
alert(oh.getPosition().x);
// ADD NEW ITEM TO ARRAY, STORE X POSITION
xArray.push(oh.getPosition().x);
});
and so all the x values get stored in the array.
If you need to clear the array, you can simply create a new one again with the same name.
And you can iterate through it with a loop if needed.
updated:
http://jsfiddle.net/pwM8M/2/
How would I make my object reach the end of the animation paths? I am making a basic pong game, but the ball stops early if it misses the paddle, It should reach the end of the path. Here is my code:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf8">
<title> Rapahel test </title>
<script type="text/javascript" src="raphael.js">
</script>
</head>
<body>
<script type="text/javascript">
//create the canvas, which all the shapes are on
var paper = Raphael(0,0,500,500);
ballpath = '';
ballNum = 0;
//the paths the ball will follow, one at a time, starting from 0. which is the first in an array.
paths = ["M480, 180L35, 180", "M35, 180L480, 325", "M480, 325L35, 110", "M35, 110L480, 50", "M480, 50L35, 340", "M35, 340L480, 340", "M480, 340L35, 200", "M35, 200L35, 200"];
Raphael(function () {
ballpath = paper.path(paths[ballNum]).attr({stroke: "#0000ff", opacity: .3, "stroke-width": 1});
var ball = paper.circle(0, 0, 5).attr("fill", "rgba(255, 0, 0, 1)");
//the length variable (and arribute)
var len = ballpath.getTotalLength();
ball.onAnimation(function () {
var t = this.attr("transform");
});
paper.customAttributes.along = function (v) {
var point = ballpath.getPointAtLength(v * len);
return {
transform: "t" + [point.x, point.y] + "r" + point.alpha
};
};
ball.attr({along: 0});
//the variable rotateAlongThePath is true
var rotateAlongThePath = true;
//th function that actually makes the ball move
function run() {
//the path which the wall will follow
wallpath = paper.path("M480, 180L480, 380, M480, 380L480, 60, M480, 60L480, 340").attr({stroke: "#0000ff", opacity: .3, "stroke-width": 1});
//create a rectangle
var wall2 = paper.rect(0 - 50, 0 - 9, 100, 20).attr("fill", "rgba(0, 0, 255, 1)");
//the length variable (and arribute)
var wallLen = wallpath.getTotalLength();
//not sure aout these commands, they are to do with animation paths
wall2.onAnimation(function () {
var t = this.attr("transform");
});
paper.customAttributes.along2 = function (v2) {
var point2 = wallpath.getPointAtLength(v2 * wallLen);
return {
transform: "t" + [point2.x, point2.y] + "r" + point2.alpha
};
};
//set the wall's movemnt to 0
wall2.attr({along2: 0});
var collision = function(){
//find the paddle's height and width, the ball's center point, and AI Paddle's x position
var paddleheight = Math.round(paddle.getBBox().y + paddle.getBBox().height / 2);
var paddleWidth = Math.round(paddle.getBBox().x + paddle.getBBox().width / 2);
var ballcenterpoint = Math.round(ball.getBBox().y + ball.getBBox().height / 2);
var AIpaddle = Math.round(wall2.getBBox().x + wall2.getBBox().width / 2);
//the height from the center point of the paddle to the edge (heigh variance), finding the height of the top edge of the boundingbox (where the ball will colide with) by adding the centerpoint and height variance. finding the bottom edge by subtracting the centerpoint from the bounding box.
var heightvariance = pHeight;
var heightPlusboundingbox = paddleheight + heightvariance;
var heightMinusboundingbox = paddleheight - heightvariance;
//if the edge of the box is less than or equal to the center of the ball.
var balledge = Math.round(ball.getBBox().x + (ball.getBBox().width / 2) + 22);
//continue ballbounce is used for the if below; it equals: if the ball center is largerthan or equal to the height of the paddle minus its bounding box AND the center point is equal to or less than the height of the paddle plus the bounding box AND the center is equal to or greater than the width (extending the detected edge ofthe box by a few pixels so as to fix some collision problems) the if below will work.
console.log('Ball X: ', balledge);
console.log('AI X: ', AIpaddle);
console.log(balledge);
console.log(AIpaddle);
//the if
if(ballcenterpoint >= heightMinusboundingbox && ballcenterpoint <= heightPlusboundingbox || balledge >= AIpaddle){
//if the ball number is less than or equal to 6 it will increase theball number by one an change the ballpath to one in the array called paths[ballNum], the ball will then begin at the beginning of that path and be animated along it
if(ballNum <= 6){
ballNum++
ballpath = paper.path(paths[ballNum]).attr({stroke: "#0000ff", opacity: .3, "stroke-width": 1});
ball.attr({along: 0});
ball.animate({along: 1}, 2500, onanimationdone);
}
//if the ballNum is greater than 6 it will remove the ball (fixin the error of an infinite loop of theball just sitting the corner acting all boring and doing nothing)
}
else {
// ball.remove(); //- YOU LOSE
};
};
//animates the wall
wall2.animate({along2: 1}, 16200, function () {
});
//loops through all the ball paths
var onanimationdone = function () {
collision();
};
ball.animate({along: 1}, 2500, onanimationdone);
ball.attr({along: 0});
};
//create a rectangles
var pHeight = 100;
var pWidth = 20;
var paddle = paper.rect(10, 130, pWidth, pHeight);
paddle.attr("fill", "rgba(0, 0, 255, 1)");
var start = function () {
// storing original coordinates
this.originalY = this.attr("y");
this.attr({opacity: 0.9});
},
move = function (dx, dy) {
// moves the object up and down, when clicked and dragged
this.attr({y: this.originalY + dy});
},
up = function () {
// restoring state
this.attr({opacity: 1.0});
};
//runs the functions
paddle.drag(move, start, up);
run();
});
</script>
</body
</html>
I am new to JavaScript, I apologise if my code is a little hard to understand (this is my fourth day doing JavaScript).
Using raphael is valid for rendering your graphics, but I would suggest using the paths array for the ball's motion is not. If you are in fact trying to make a game you are best off having the ball store it's current position and velocity, and update these per frame via some some of timer.