KonvaJS - Rotate rectangle around cursor without using offset - javascript

I am using KonvaJS to drag and drop rectangles into predefined slots. Some of the slots need to be rotated 90 degrees. I have a hit box around the slots that are rotated vertically, so when the user drags the rectangle into the area it will rotate 90 degrees automatically (to match the orientation). When it rotates, it moves out from under the mouse. This can be solved with offset, but then the rectangle doesn't visually line up with the boxes after snapping. This can (probably) be solved with additional code.
I have tried to rotate the rectangle, and then move it under the mouse. Since the user is still dragging it, this doesn't seem to work as I planned.
Is it possible to force the rectangle to rotate under the mouse without using offset?
Here is a fiddle showing the issue - The offset problems can be demonstrated by setting the first variable to true.
https://jsfiddle.net/ChaseRains/1k0aqs2j/78/
var width = window.innerWidth;
var height = window.innerHeight;
var rectangleLayer = new Konva.Layer();
var holdingSlotsLayer = new Konva.Layer();
var controlLayer = new Konva.Layer();
var stage = new Konva.Stage({
container: 'container',
width: width,
height: height,
draggable: true
});
//vertical holding spot
holdingSlotsLayer.add(new Konva.Rect({
x: 300,
y: 25,
width: 130,
height: 25,
fill: '#fff',
draggable: false,
rotation: 90,
stroke: '#000'
}));
//horizontal holding spot
holdingSlotsLayer.add(new Konva.Rect({
x: 25,
y: 75,
width: 130,
height: 25,
fill: '#fff',
draggable: false,
rotation: 0,
stroke: '#000'
}));
//mask to set boundaries around where we wannt to flip the rectangle
controlLayer.add(new Konva.Rect({
x: 215,
y: 15,
width: 150,
height: 150,
fill: '#fff',
draggable: false,
name: 'A',
opacity: 0.5
}));
stage.add(holdingSlotsLayer, controlLayer);
//function for finding intersections
function haveIntersection(placeHolder, rectangle, zone) {
if (rectangle.rotation == 0 || zone == true) {
return !(
rectangle.x > placeHolder.x + placeHolder.width ||
rectangle.x + rectangle.width < placeHolder.x ||
rectangle.y > placeHolder.y + placeHolder.height ||
rectangle.y + rectangle.height < placeHolder.y
);
} else {
return !(
rectangle.x > placeHolder.x + 25 ||
rectangle.x + rectangle.width < placeHolder.x ||
rectangle.y > placeHolder.y + placeHolder.height + 90 ||
rectangle.y + rectangle.height < placeHolder.y
);
}
}
//function to create rectangle group (so we can place text on the rectangle)
function spawnRectangle(angle) {
var rectangleGroup = new Konva.Group({
x: 95,
y: 95,
width: 130,
height: 25,
rotation: angle,
draggable: true,
});
rectangleGroup.add(new Konva.Rect({
width: 130,
height: 25,
fill: 'lightblue'
}));
rectangleGroup.add(new Konva.Text({
text: '123',
fontSize: 18,
fontFamily: 'Calibri',
fill: '#000',
width: 130,
padding: 5,
align: 'center'
}));
//function tied to an on drag move event
rectangleGroup.on('dragmove', (e) => {
//shrink rectangle hitbox for use in placeholder intersection
var dimensions = {
"height": 3,
"width": 5,
"x": e.target.attrs.x,
"y": e.target.attrs.y,
'rotation': e.target.attrs.rotation
};
//loop over holding slots to see if there is an intersection.
for (var i = 0; holdingSlotsLayer.children.length > i; i++) {
//if true, change the look of the slot we are hovering
if (haveIntersection(holdingSlotsLayer.children[i].attrs, dimensions, false)) {
holdingSlotsLayer.children[i].attrs.fill = '#C41230';
holdingSlotsLayer.children[i].attrs.dash = [10, 3];
holdingSlotsLayer.children[i].attrs.stroke = '#000';
//set attributes back to normal otherwise
} else {
holdingSlotsLayer.children[i].attrs.fill = '#fff';
holdingSlotsLayer.children[i].attrs.dash = null;
holdingSlotsLayer.children[i].attrs.stroke = null;
}
}
//check to see if we are in a zone that requires the rectangle to be flipped 90 degrees
if (haveIntersection(controlLayer.children[0].attrs, dimensions, true)) {
if (rectangleGroup.attrs.rotation != 90) {
rectangleGroup.attrs.rotation = 90;
}
} else {
rectangleGroup.attrs.rotation = 0;
}
stage.batchDraw();
});
rectangleGroup.on('dragend', (e) => {
for (var i = 0; holdingSlotsLayer.children.length > i; i++) {
//If the parking layer has an element that is lit up, then snap to position..
if (holdingSlotsLayer.children[i].attrs.fill == '#C41230') {
rectangleGroup.position({
x: holdingSlotsLayer.children[i].attrs.x,
y: holdingSlotsLayer.children[i].attrs.y
});
holdingSlotsLayer.children[i].attrs.fill = '#fff';
holdingSlotsLayer.children[i].attrs.dash = null;
holdingSlotsLayer.children[i].attrs.stroke = null;
}
}
stage.batchDraw();
});
rectangleLayer.add(rectangleGroup);
stage.add(rectangleLayer);
}
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #D3D3D3;
background-size: cover;
}
#desc {
position: absolute;
top: 5px;
left: 5px;
}
<script src="https://unpkg.com/konva#4.0.18/konva.min.js"></script>
<body>
<div id="container"></div>
<div id="desc">
<button onclick="spawnRectangle(0)">spawn rectangle</button>
</div>
</body>

Here is a simple function to rotate the rectangle under the mouse, without using konva offset(). I used a tween to apply the movement but if you prefer to use it without the tween just apply the rect.rotate() then apply the newPos x & y as the position.
EDIT: The OP pointed out that if you clicked, held the mouse down whilst the rectangle completed its animation, then dragged, then the rectangle would jump away. What gives ? Well, when the mousedown event runs Konva takes note of the shape's initial position in its internal drag function. Then when we start to actually drag the mouse, Konva dutifully redraws the shape in the position it calculates. Now, 'we' know that we moved the shape in out code, but we didn't let Konva in on our trick.
The fix is to call
rect.stopDrag();
rect.startDrag();
immediately after the new position has been set. Because I am using a tween I do this in the onFinish() callback function of one of the tweens - you would want to ensure its the final tween if you apply more than one. I got away with it because my tweens run over the same period. If you aren't using tweens, just call the above immediately you apply your last rotate() or position() call on the shape.
function rotateUnderMouse(){
// Get the stage position of the mouse
var mousePos = stage.getPointerPosition();
// get the stage position of the mouse
var shapePos = rect.position();
// compute the vector for the difference
var rel = {x: mousePos.x - shapePos.x, y: mousePos.y - shapePos.y}
// Now apply the rotation
angle = angle + 90;
// and reposition the shape to keep the same point in the shape under the mouse
var newPos = ({x: mousePos.x + rel.y , y: mousePos.y - rel.x})
// Just for fun, a tween to apply the move: See https://konvajs.org/docs/tweens/Linear_Easing.html
var tween1 = new Konva.Tween({
node: rect,
duration: 0.25,
x: newPos.x,
y: newPos.y,
easing: Konva.Easings.Linear,
onFinish: function() { rect.stopDrag(); rect.startDrag();}
});
// and a tween to apply the rotation
tween2 = new Konva.Tween({
node: rect,
duration: 0.25,
rotation: angle,
easing: Konva.Easings.Linear
});
tween2.play();
tween1.play();
}
function setup() {
// Set up a stage and a shape
stage = new Konva.Stage({
container: 'canvas-container',
width: 650,
height: 300
});
layer = new Konva.Layer();
stage.add(layer);
newPos = {x: 80, y: 40};
rect = new Konva.Rect({
width: 140, height: 50, x: newPos.x, y: newPos.y, draggable: true, stroke: 'cyan', fill: 'cyan'
})
layer.add(rect);
stage.draw()
rect.on('mousedown', function(){
rotateUnderMouse()
})
}
var stage, layer, rect, angle = 0;
setup()
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/konva/4.0.13/konva.js"></script>
<p>Click the rectangle - it will rotate under the mouse.</p>
<div id="canvas-container"></div>

Related

Rotate a group of shapes containing text whilst keeping text centered and horizontal

This is probably just maths.
I am using Konva to dynamically generate shapes, which I'm storing as a label. So there's a label which contains a textElement and a rectangle. I want to make sure text in that rectangle is always a) Centered horizontally and vertically and b) facing the right way up.
So a rectangle could have any rotation, but I always want the text centered and facing the right way up.
The code for creation; width, height, rotation, x and y all have values pulled from a database.
var table = new Konva.Label({
x: pos_x,
y: pos_y,
width: tableWidth,
height: tableHeight,
draggable:true
});
table.add(new Konva.Rect({
width: tableWidth,
height: tableHeight,
rotation: rotation,
fill: fillColor,
stroke: strokeColor,
strokeWidth: 4
}));
table.add(new Konva.Text({
width: tableWidth,
height: tableHeight,
x: pos_x, //Defaults to zero
y: pos_y, //Default to zero
text: tableNumber,
verticalAlign: 'middle',
align: 'center',
fontSize: 30,
fontFamily: 'Calibri',
fill: 'black'
}))
tableLayer.add(table);
The problem is, if rotation is in place, text is off center, as in this image:
I do manually correct in some circumstances - for example if rotation = 45 degrees:
pos_x = -tableWidth/2;
pos_y = tableHeight/5;
but that is not a permanent solution. I want the x and y co-ordinates of the text to be at the centerpoint of the shape itself.
I've tried a few approaches (such as applying rotation to the Label itself and then negative rotation value to the text)
This code snippet illustrates a solution. It is copied & modified from my other self-answer when I was looking for a robust approach to rotation around an arbitrary point - note that I consider this a slightly different question than my original so I have not suggested this is a dup. The difference is the need to work with a more complex grouped shape and to keep some element within that group unrotated.
Not in the OP's question, but I set a background rectangle into the text by making the text a group. The purpose of this was to show that the text rectangle will extend outside the label rectangle in some points of rotation. This is not a critical issue but it is useful to see it happen.
The fundamental challenge for the coder is to understand how the shapes move when rotated since we usually want to spin them around their centre but the fundamental 2D canvas pattern that Konva (and all HTML5 canvas wrappers) follow is to rotate from the top-left corner, al least for rectangles as per shapes in the question. It 'is' possible to move the rotation point (known as the offset) but again that is a conceptual challenge for the dev and a nice trap for anyone trying to support the code later.
There's a lot of code in this answer that is here to set up something dynamic that you can use to visualise what is going on. However, the crux is in this:
// This is the important call ! Cross is the rotation point as illustrated by crosshairs.
rotateAroundPoint(shape, rotateBy, {x: cross.x(), y: cross.y()});
// The label is a special case because we need to keep the text unrotated.
if (shape.name() === 'label'){
let text = shape.find('.text')[0];
rotateAroundPoint(text, -1 * rotateBy, {x: text.getClientRect().width/2, y: text.getClientRect().height/2});
}
The rotateAroundPoint() function takes as parameters the Konva shape to rotate, the clockwise rotation angle (not radians, good ole degrees), and the x & y position of the rotation point on the canvas / parent.
I constructed a group of shapes as my label, composing it from a rectangle and a text shape. I named this 'label'. Actually I switched the text shape to be another group of rect + text to that I could show the rectangle the text sits within. You could leave out the extra group. I named this 'text'.
The first call to rotateAroundPoint() rotates the group named 'label'. So the group rotates on the canvas. Since the 'text' is a child of the 'label' group, that would leave the 'text' rotated, so the next line checks if we are working with the 'label' group, and if so we need to get hold of the 'text' shape which is what this line does:
let text = shape.find('.text')[0];
In Konva the result of a find() is a list so we take the first in the list. Now all that remains for me to do is rotate the text on the 'label' group back again by applying the negative rotation degrees to its center point. The line below achieves this.
rotateAroundPoint(text, -1 * rotateBy, {x: text.getClientRect().width/2, y: text.getClientRect().height/2});
One note worthy of mention - I used a group for my 'text' shape. A Konva group does not naturally have a width or height - it is more of a means to collect shapes together but without a 'physical' container. So to get its width and height for the centre point calculations I use the group.getClientRect() method which gives the size of the minimum bounding box that would contain all shapes in the group, and yields an object formed as {width: , height: }.
Second note - the first use of rotateAroundPoint() affects the 'label' group which has as its parent the canvas. The second use of that function affects the 'text' group which has the 'label' group as its parent. Its subtle but worth knowing.
Here is the snippet. I urge you to run it fullscreen and spin a few shapes around a few different points.
// Code to illustrate rotation of a shape around any given point. The important functions here is rotateAroundPoint() which does the rotation and movement math !
let
angle = 0, // display value of angle
startPos = {x: 80, y: 45},
shapes = [], // array of shape ghosts / tails
rotateBy = 20, // per-step angle of rotation
shapeName = $('#shapeName').val(), // what shape are we drawing
shape = null,
ghostLimit = 10,
// Set up a stage
stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight
}),
// add a layer to draw on
layer = new Konva.Layer(),
// create the rotation target point cross-hair marker
lineV = new Konva.Line({points: [0, -20, 0, 20], stroke: 'lime', strokeWidth: 1}),
lineH = new Konva.Line({points: [-20, 0, 20, 0], stroke: 'lime', strokeWidth: 1}),
circle = new Konva.Circle({x: 0, y: 0, radius: 10, fill: 'transparent', stroke: 'lime', strokeWidth: 1}),
cross = new Konva.Group({draggable: true, x: startPos.x, y: startPos.y}),
labelRect, labelText;
// Add the elements to the cross-hair group
cross.add(lineV, lineH, circle);
layer.add(cross);
// Add the layer to the stage
stage.add(layer);
$('#shapeName').on('change', function(){
shapeName = $('#shapeName').val();
shape.destroy();
shape = null;
reset();
})
// Draw whatever shape the user selected
function drawShape(){
// Add a shape to rotate
if (shape !== null){
shape.destroy();
}
switch (shapeName){
case "rectangle":
shape = new Konva.Rect({x: startPos.x, y: startPos.y, width: 120, height: 80, fill: 'magenta', stroke: 'black', strokeWidth: 4});
break;
case "hexagon":
shape = new Konva.RegularPolygon({x: startPos.x, y: startPos.y, sides: 6, radius: 40, fill: 'magenta', stroke: 'black', strokeWidth: 4});
break;
case "ellipse":
shape = new Konva.Ellipse({x: startPos.x, y: startPos.y, radiusX: 40, radiusY: 20, fill: 'magenta', stroke: 'black', strokeWidth: 4});
break;
case "circle":
shape = new Konva.Ellipse({x: startPos.x, y: startPos.y, radiusX: 40, radiusY: 40, fill: 'magenta', stroke: 'black', strokeWidth: 4});
break;
case "star":
shape = new Konva.Star({x: startPos.x, y: startPos.y, numPoints: 5, innerRadius: 20, outerRadius: 40, fill: 'magenta', stroke: 'black', strokeWidth: 4});
break;
case "label":
shape = new Konva.Group({name: 'label'});
labelRect = new Konva.Rect({x: 0, y: 0, width: 120, height: 80, fill: 'magenta', stroke: 'black', strokeWidth: 4, name: 'rect'})
shape.add(labelRect);
labelText = new Konva.Group({name: 'text'});
labelText.add(new Konva.Rect({x: 0, y: 0, width: 100, height: 40, fill: 'cyan', stroke: 'black', strokeWidth: 2}))
labelText.add(new Konva.Text({x: 0, y: 0, width: 100, height: 40, text: 'Wombat',fontSize: 20, fontFamily: 'Calibri', align: 'center', padding: 10}))
shape.add(labelText)
labelText.position({x: (labelRect.width() - labelText.getClientRect().width) /2, y: (labelRect.height() - labelText.getClientRect().height) /2})
break;
};
layer.add(shape);
cross.moveToTop();
}
// Reset the shape position etc.
function reset(){
drawShape(); // draw the current shape
// Set to starting position, etc.
shape.position(startPos)
cross.position(startPos);
angle = 0;
$('#angle').html(angle);
$('#position').html('(' + shape.x() + ', ' + shape.y() + ')');
clearTails(); // clear the tail shapes
stage.draw(); // refresh / draw the stage.
}
// Click the stage to move the rotation point
stage.on('click', function (e) {
cross.position(stage.getPointerPosition());
stage.draw();
});
// Rotate a shape around any point.
// shape is a Konva shape
// angleRadians is the angle to rotate by, in radians
// point is an object {x: posX, y: posY}
function rotateAroundPoint(shape, angleDegrees, point) {
let angleRadians = angleDegrees * Math.PI / 180; // sin + cos require radians
const x =
point.x +
(shape.x() - point.x) * Math.cos(angleRadians) -
(shape.y() - point.y) * Math.sin(angleRadians);
const y =
point.y +
(shape.x() - point.x) * Math.sin(angleRadians) +
(shape.y() - point.y) * Math.cos(angleRadians);
shape.rotation(shape.rotation() + angleDegrees); // rotate the shape in place
shape.x(x); // move the rotated shape in relation to the rotation point.
shape.y(y);
shape.moveToTop(); //
}
$('#rotate').on('click', function(){
let newShape = shape.clone();
shapes.push(newShape);
layer.add(newShape);
// This ghost / tails stuff is just for fun.
if (shapes.length >= ghostLimit){
shapes[0].destroy();
shapes = shapes.slice(1);
}
for (var i = shapes.length - 1; i >= 0; i--){
shapes[i].opacity((i + 1) * (1/(shapes.length + 2)))
};
// This is the important call ! Cross is the rotation point as illustrated by crosshairs.
rotateAroundPoint(shape, rotateBy, {x: cross.x(), y: cross.y()});
// The label is a special case because we need to keep the text unrotated.
if (shape.name() === 'label'){
let text = shape.find('.text')[0];
rotateAroundPoint(text, -1 * rotateBy, {x: text.getClientRect().width/2, y: text.getClientRect().height/2});
}
cross.moveToTop();
stage.draw();
angle = angle + 10;
$('#angle').html(angle);
$('#position').html('(' + Math.round(shape.x() * 10) / 10 + ', ' + Math.round(shape.y() * 10) / 10 + ')');
})
// Function to clear the ghost / tail shapes
function clearTails(){
for (var i = shapes.length - 1; i >= 0; i--){
shapes[i].destroy();
};
shapes = [];
}
// User cicks the reset button.
$('#reset').on('click', function(){
reset();
})
// Force first draw!
reset();
body {
margin: 10;
padding: 10;
overflow: hidden;
background-color: #f0f0f0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://unpkg.com/konva#^3/konva.min.js"></script>
<p>1. Click the rotate button to see what happens when rotating around shape origin.</p>
<p>2. Reset then click stage to move rotation point and click rotate button again - rinse & repeat</p>
<p>
<button id = 'rotate'>Rotate</button>
<button id = 'reset'>Reset</button>
<select id='shapeName'>
<option value='label' selected='selected'>Label</option>
<option value='rectangle'>Rectangle</option>
<option value='hexagon'>Polygon</option>
<option value='ellipse' >Ellipse</option>
<option value='circle' >Circle</option>
<option value='star'>Star</option>
</select>
Angle : <span id='angle'>0</span>
Position : <span id='position'></span>
</p>
<div id="container"></div>

Rotating Shape with KineticJS

I'm struggling to implement a little things on canvas with KineticJS.
I want to create a circle + a line which form a group (plane).
The next step is to allow the group to rotate around itself with a button that appears when you click on the group.
My issue is that when I click on the rotate button, it does not rotate near the button but elsewhere. Have a look :
My rotation atm : http://hpics.li/b46b73a
I want the rotate button to be near the end of the line. Not far away..
I tried to implement it on jsfiddle but I'm kinda new and I didn't manage to put it correctly , if you could help me on that, I would be thankful !
http://jsfiddle.net/49nn0ydh/1/
function radians (degrees) {return degrees * (Math.PI/180)}
function degrees (radians) {return radians * (180/Math.PI)}
function angle (cx, cy, px, py) {var x = cx - px; var y = cy - py; return Math.atan2 (-y, -x)}
function distance (p1x, p1y, p2x, p2y) {return Math.sqrt (Math.pow ((p2x - p1x), 2) + Math.pow ((p2y - p1y), 2))}
jQuery (function(){
var stage = new Kinetic.Stage ({container: 'kineticDiv', width: 1200, height:600})
var layer = new Kinetic.Layer(); stage.add (layer)
// group avion1
var groupPlane1 = new Kinetic.Group ({
x: 150, y: 150,
draggable:true
}); layer.add (groupPlane1)
// avion 1
var plane1 = new Kinetic.Circle({
radius: 10,
stroke: "darkgreen",
strokeWidth: 3,
}); groupPlane1.add(plane1);
var trackPlane1 = new Kinetic.Line({
points: [10, 0, 110, 0],
stroke: "darkgreen",
strokeWidth: 2
}); groupPlane1.add(trackPlane1);
groupPlane1.on('click', function() {
controlGroup.show();
});
groupPlane1.setOffset (plane1.getWidth() * plane1.getScale().x / 2, plane1.getHeight() * plane1.getScale().y / 2)
var controlGroup = new Kinetic.Group ({
x: groupPlane1.getPosition().x + 120,
y: groupPlane1.getPosition().y ,
opacity: 1, draggable: true,
}); layer.add (controlGroup)
var signRect2 = new Kinetic.Rect({
x:-8,y: -6,
width: 20,
height: 20,
fill: 'white',
opacity:0
});
controlGroup.add(signRect2);
var sign = new Kinetic.Path({
x: -10, y: -10,
data: 'M12.582,9.551C3.251,16.237,0.921,29.021,7.08,38.564l-2.36,1.689l4.893,2.262l4.893,2.262l-0.568-5.36l-0.567-5.359l-2.365,1.694c-4.657-7.375-2.83-17.185,4.352-22.33c7.451-5.338,17.817-3.625,23.156,3.824c5.337,7.449,3.625,17.813-3.821,23.152l2.857,3.988c9.617-6.893,11.827-20.277,4.935-29.896C35.591,4.87,22.204,2.658,12.582,9.551z',
scale: {x:0.5, y:0.5}, fill: 'black'
}); controlGroup.add (sign)
controlGroup.setDragBoundFunc (function (pos) {
var groupPos = groupPlane1.getPosition();
var rotation = degrees (angle (groupPos.x, groupPos.y, pos.x, pos.y));
var dis = distance (groupPos.x, groupPos.y, pos.x, pos.y);
groupPlane1.setRotationDeg (rotation);
layer.draw()
return pos
})
controlGroup.on ('dragend', function() {
controlGroup.hide();
layer.draw()
})
controlGroup.hide();
layer.draw()
})
You can adjust the rotation point by setting the offsetX and offsetY of the group.

kinectJs Rotation of two images

I have 2 images that are the upper arm and forearm. The upper arms rotates around a point and the forearm rotates round this same point.
How can i rotate the forearm when i rotate the upper arm?
if i rotate the upperarm 12 degress,then i must move the forearm to the rotation point of the upper arm and then rotate it but i cant seem to get the forearm to move back into postion after the rotation. What am i doing wrong.
code im using for a kinectjs event;
//upper rotate
upperArmImg.on('mousedown',function(evt) {
upperArmImg.rotateDeg(12);
p.x=upperArmImg.x();
p.y=upperArmImg.y();
//move the formarm to center of rotation
foreArmImg.setPosition(p);
foreArmImg.rotateDeg(12);
//move it back some ???
foreArmImg.move(100,100);
stage.draw();
});
The key to joint movements is putting all arm parts in a group and then setting proper offset points.
The importance of an offset point is that they set where any arm part will rotate (==rotation point)
Here's an outline of how to add kinetic motion to an upper and lower arm:
Put the upper and lower arm in a group
Set the group, upper & lower offsets (==rotation points) to { x:0, y:armDiameter/2 }
To rotate the upper arm, change the groups rotation
To rotate the lower arm, change the lower arm rotation
Group Rotation == 0 degrees, Lower rotation == 0 degrees
Group Rotation == 50 degrees, Lower rotation == 0 degrees
Group Rotation == 50 degrees, Lower rotation == 65 degrees
Here's example code and a Demo:
var stage = new Kinetic.Stage({
container: 'container',
width: 350,
height: 350
});
var layer = new Kinetic.Layer();
stage.add(layer);
var lastWidth = 0;
var baseGroup = new Kinetic.Group({
x: 50,
y: 200,
draggable: true
});
layer.add(baseGroup);
var lastParent = baseGroup;
var appendages = [];
appendages.push({
name: 'shoulder',
width: 50,
diameter: 20,
fill: 'brown'
});
appendages.push({
name: 'elbow',
width: 80,
diameter: 20,
fill: 'peru'
});
appendages.push({
name: 'hand',
width: 30,
diameter: 20,
fill: 'tan'
});
appendages.push({
name: 'finger',
width: 15,
diameter: 20,
fill: 'wheat'
});
for (var i = 0; i < appendages.length; i++) {
lastParent = addAppendage(i);
appendages[i].group = lastParent;
var appendage = appendages[i];
var html = "Rotate " + appendage.name + ":";
html += "<input id=" + appendage.name;
html += " data-apindex='" + i + "'";
html += " class='apRange' type=range min=0 max=90 value=0><br>";
$('body').append(html);
}
$('.apRange').change(function() {
var appendageIndex = parseInt($(this).data('apindex'));
appendages[appendageIndex].group.rotation(-parseInt($(this).val()));
layer.draw();
});
function addAppendage(i) {
var appendage = appendages[i];
var d2 = appendage.diameter / 2;
// new group for this appendage
var newGroup = new Kinetic.Group({
x: lastWidth,
y: 0,
rotation: 0,
offset: {
x: 0,
y: 0
},
});
newGroup.appendageIndex = i;
lastParent.add(newGroup);
// new appendage
var a = new Kinetic.Rect({
x: 0,
y: 0,
width: appendage.width,
height: d2 * 2,
fill: appendage.fill,
stroke: 'black',
offset: {
x: 0,
y: d2
},
});
newGroup.add(a);
// new joint indicator
var j = new Kinetic.Circle({
x: 0,
y: 0,
radius: d2 / 2,
fill: "red",
offset: {
x: 0,
y: 0
},
});
newGroup.add(j);
// display new objects
layer.draw();
// save width for next loop
lastWidth = appendage.width;
// return latest group
return (newGroup);
}
body {
padding: 20px;
}
#container {
border: solid 1px #ccc;
margin-top: 10px;
width: 350px;
height: 350px;
}
<script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v5.1.0.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<div id="container"></div>

kineticjs group cache and layer draw is hiding kinetic arc shapes

I am creating a circle as a group of kinetic arcs. When I cache the group and subsequently call the draw function on the layer, three quarters of the circle are hidden. I think layer.draw may require an offset but really I'm only guessing. When I remove the fill, stroke or opacity from the arc or the object literal from the cache call then the full circle is displayed. http://jsfiddle.net/leydar/gm2FT/5/ Any insights gratefully received.
function createArc(n){
var arc = new Kinetic.Arc({
innerRadius: 30,
outerRadius: 50,
/* if I remove the fill, stroke or opacity
the full wheel is correctly displayed */
fill: 'blue',
stroke: 'black',
opacity: 0.3,
strokeWidth: 1,
angle: 36,
rotation: 36*n
});
return arc;
}
function init() {
var arc;
var stage = new Kinetic.Stage({
container: 'container',
width: 104,
height: 104
});
var layer = new Kinetic.Layer();
var circle = new Kinetic.Group();
for(var i=0;i<10;i++) {
arc = createArc(i);
circle.add(arc);
};
layer.add(circle);
stage.add(layer);
/* if I do not cache or do not call layer.draw()
then again the wheel is correctly displayed */
circle.cache({
x: -52,
y: -52,
width: 104,
height: 104,
drawBorder: true
});
layer.draw();
}
init();
Stephen
This is a bug of KineticJS.
You may use this workaround:
Kinetic.Arc.prototype._useBufferCanvas = function() {
return false;
};
http://jsfiddle.net/gm2FT/6/

KineticJS transform layers for scrolling

I've been trying to put scrollbars into my KineticJS app following the tutorial Kinetic have on the API. I have the scrollbars themselves appearing as they should, but I'm not sure what to do with the event listeners to actually get the stage or each of the layers to move so that the scrollbars actually move the view along.
var hscrollArea = new Kinetic.Rect({
x: 10,
y: $(window).height() - 30 - 80, // 80 to account for the fixed footer
width: $(window).width() - 40,
height: 20,
fill: "gray",
opacity: 0.3
});
var hscroll = new Kinetic.Rect({
x: 10,
y: $(window).height() - 30 - 80,// 80 to account for the fixed footer
width: 130,
height: 20,
fill: "orange",
draggable: true,
dragBoundFunc: function(pos) {
// TODO: Move stage or layers at this point
console.log("dragBoundFunc: " + this);
return {
x: pos.x,
y: this.getAbsolutePostion().y
};
},
opacity: 0.9,
stroke: "black",
strokeWidth: 1
});
var vscrollArea = new Kinetic.Rect({
x: $(window).width() - 30,
y: 10,
width: 20,
height: $(window).height() - 40 - 80,
fill: "gray",
opacity: 0.3
});
var vscroll = new Kinetic.Rect({
x: $(window).width() - 30,
y: 10,
width: 20,
height: 70,
fill: "orange",
draggable: true,
dragBoundFunc: function(pos) {
// TODO: Move stage or layers at this point
console.log("dragBoundFunc: " + this);
return {
x: this.getAbsolutePosition().x,
y: pos.y
};
},
opacity: 0.9,
stroke: "black",
strokeWidth: 1
});
Any help would be greatly appreciated :)
Thanks,
Becky
You can move stage or layer when your scrollbar(rectangle) is dragged. i.e,
stage.move(50,50);
stage.draw();
stage.move(-50,-50);
stage.draw();
I would recommend placing the objects you wish to scroll into their own group and position the group accordingly, rather than trying to position layers or the stage. The size of the group would be the (axis-aligned) bounding box of all the objects within the group. You can use the size of the group and compare it against the size of the stage to get a ratio for width and height. These ratios would be used to help calculate sizes for horizontal and vertical scroll bars (the bars being what get dragged to create a scroll effect). The ratio would also be used to determine when to show and hide the scroll bar areas. The difference in sizes would help in determining how to position the group within the stage.

Categories

Resources