Rotating a CSS3 3d cube with javascript - javascript

I am trying to rotate a 3d cube with two controls - for left and right. When you click on the left button, for example, I'd like to add extra 40 degrees to the rotateY value on the left.
My question is: Since I can't work with exact values (I want everytime to add new values to the existing ones!), I guess I will have to split up the string for the transform into something like this:
$(cube).css("-webkit-transform", "rotateX(-100deg) rotateZ(-50deg + add 40 degrees to the existing one!) translateZ(-30px)");
Here is the pen I am working on:
http://codepen.io/gbnikolov/pen/hHEBG

The easiest way to keep track of the rotation is to do it yourself, as jQuery's css() will return the matrix, and then you'll have to calculate that etc. and it's just easier to keep track of the value in an attribute instead.
Keep the value in a data attribute, and increment that together with the css value, and it should be straight forward, and it's accessible from other functions as well.
cube.data('rot', -50);
left.click(function(){
var deg = cube.data('rot') + 40;
cube.css("-webkit-transform", "rotateX(-100deg) rotateZ("+deg+"deg) translateZ(-30px)");
cube.data('rot', deg);
});
right.click(function(){
var deg = cube.data('rot') - 40;
cube.css("-webkit-transform", "rotateX(-100deg) rotateZ("+deg+"deg) translateZ(-30px)");
cube.data('rot', deg);
});
CodePen

To keep track of the rotation and increment it on each click with JS, this is what you can do :
jQuery :
var btn = $('.nav'),
body = $('body'),
cube = $(".cube"),
left = $('.left'),
v_transZ = -40 ;
// REST OF YOUR jQuery CODE
left.click(function(){
v_transZ = v_transZ - 10;
var counter = 0,
v_trans = 'rotateX(-100deg) rotateZ('+ v_transZ +'deg) translateZ(-30px)';
$(cube).css("-webkit-transform", v_trans);
});
As you are using CSS3 transitions, I would tend to use them also to rotate the cube and use JS only to trigger the animation with class changes on click. But as you ask for a JS solution...

Well, since you are changing the angle on only one axis this is not exactly what you are looking for, but it might give you a clue:
Css Tricks
I would try to solve the problem completely in the meantime, though!

I'm not sure whether your question is about how to keep do incremental changes (instead of hardcoded ones) or how to find a more elegant way for setting those values.
For the first one, I think the only way is to keep track of rotation values in your JavaScript code: http://codepen.io/anon/pen/dsIgn (I worked on it in Firefox, just change back -moz- to -webkit-.)

You have to declare a variable outside the click function and store the current value there
Then in every click you add 40 to that variable
JS
var currentRotateZ = -50
left.click(function(){
$(cube).css("-webkit-transform", "rotateX(-100deg) rotateZ(" + (currentRotateZ += 40) + "deg) translateZ(-30px)");
});

Related

Animation in svg.js dont have same result

My code of svg.js use rotate action and move action, but two results dont have the same center coordinate
Here is my code
<body>
<div id="drawing"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/svg.js/2.6.6/svg.js"></script>
<script type="text/javascript">
var draw = SVG('drawing')
var group_1 = draw.group()
var group_2 = draw.group()
var rect_1 = group_1.rect(50, 20).fill('#f06').center(50, 50)
var rect_2 = group_2.rect(50, 20).fill('#f09').center(50, 50)
rect_1.animate(1000).rotate(45).after(function(){
group_1.animate(1000).center(100, 100)})
group_2.animate(1000).center(100, 100)
</script>
</body>
In SVG groups needs to be understood as a grouping of elements. Basically the same as in photohop. You can select multiple elements and rotate or scale them at once.
That in turn means that groups do not have geometry on its own. It fully depends on the content where the group is visible on the screen (x, y) or how bif it is (with/height).
Thats the reason, why you cannot move a group. You only can transform it. To keep the api simple, svg.js gives you the handy move and center method which - under the hood - translate the group.
In your example, you move the group after you rotated it. But you do it with an absolute transformation. That means svg.js tries to incooperate the movement into the already present rotation. This maths does not goes off well sometimes.
To fix your problem you have to use relative transformations instead.
Which means the movement is ON TOP of the rotation. That also means that you have to figure out by how much you wanna move the group.
We are currently working on version 3 of svg.js which simplifies this transformation business alot. So I hope the final solution will be there soon

Move object on Canvas using textbox input

I've got a canvas on which I can add layers, these layers I can move, select, rotate, resize etc. Below the canvas I display the properties of the layer (x, y, width, height).
What I am trying to do is when I change the values in the textboxes containing the x and y coördinates the layer should be repositioned to the coördinates I typed in.
I've tried several things but everything seems to mess up the canvas, I am using this to move the layer on mousemove now:
else if (layerState == MOVING && mouseDown) {
selectedLayer.offsetX += e.pageX - canvasOffsetX - mousePrevX;
selectedLayer.offsetY += e.pageY - canvasOffsetY - mousePrevY;
drawMarker(selectedLayer);
}
And I use this to get the x and y of the layer:
document.getElementById('x').value = Math.round(layer.offsetX*100)/100;
document.getElementById('y').value = Math.round(layer.offsetY*100)/100;
A working example of my canvas and the code can be found here:
http://cdpn.io/fjzcv
I've also tried to work with these examples but couldn't get any of them to work:
http://chimera.labs.oreilly.com/books/1234000001654/ch03.html#text_arranger_version_2.0
http://fabricjs.com/controls/
I have tried setting an eventListener on the texboxes but when I do this the canvas will dissapear.
If anyone knows how to do this it would be great!
EDIT:
I found out a way to change the coördinates onchange with the textbox but now I can just change them to a set number instead of taking the value I filled in the textbox:
document.getElementById('x').onchange=function(){selectedLayer.offsetX = 100;
drawMarker(selectedLayer);
};
document.getElementById('y').onchange=function(){selectedLayer.offsetY = 100;
drawMarker(selectedLayer);
};
Anyone knows how to get it to move to the filled in coördinate?
I tried editing the HTML on the code pen and I think this should work.
Firstly add an event listener to your text boxes, where you've got the canvas ones.
document.getElementById('x').addEventListener('onchange',updatePos,false);
canvas.addEventListener('mousemove', mouseMoveEvent, false);
....
Then create a similar function to your mouseMoveEvent(e) to change the selected layer values
function updatePos(e) {
var temp = document.getElementById('x').value;
selectedLayer.offsetX = canvas.offsetX + temp;
drawMarker(selectedLayer);
}
Obviously you may need to add in validation or change this to suit your code, but it should get the object moving around.

get dynamically the x & y values of a raphaeljs element even if it moves

how to get dynamically the x & y values of a raphael js element even after a zoom or a left or right move, I want to show a div near to my element, when I show the element.getBBox() values I found that they don't change even if I move the element or I zoom in/out, my code that I use to get x & y values seems like this :
x = (myElement.getBBox().x+myElement.getBBox().x2)/2;
x+=$("#container").offset().left;
y = (myElement.getBBox().y+myElement.getBBox().y2)/2;
y+=$("#container").offset().left;
and the code that moves my div (a tooltip) is :
$("#myTooltip").css({"left":x+"px","top":y+"px"}).show();
Note : it works when I do the same thing with mouse coordinates (using x=e.pageX;y=e.pageY;) but in this case I want to do it when I click on another button in the page
thank's in advance
I am also using Raphael and came across to what I think you need in your project.
In order to understand how x and y can be shown dynamically, look at this DEMO:
While you are dragging objects around, you can see their coordinates change dynamically. Good Luck
The solution is to use myElement.attr('x') and myElement.attr('y') instead of myElement.getBBox().x and myElement.getBBox().y, thank you A.S. Roma, don't forget to say hello to Totti
Edit :
if someone have the same problem, the best solution I found untill now is to use jquery and raphael, explication :
First of all I need in the definition of the element to add an ID like this : myElement.node.id='myUniqueId' and after that you can access to the element using jquery like this x = $("#myUniqueId").offset().left and y = $("#myUniqueId").offset().top

Kinetic js how to scale layer in a stage?

I want to add zoom feature my app . I use Kinetic js and somewhere I found solutions for this feature but I can't apply these solution for some reason . I tried to adapt the solutions but unsuccesful . I have many Kinetic.Layer , some of them will scale when zooming apply. my challenge is that : zoom will happen on mouse position . solution that I found gives me : layer.setPosition() after scaling . As I mentioned before , I must not use "layer.setPosition" I will do this as using stage.setPosition() but I couldn't calculate new x and y of position 100% accurately. Could anyone suggest me any solution way ?
What you really want to do when zooming is to set the scale.
You can set the scale for any layer, node, or the entire stage. Just do:
layer1.setScale(2,2); // this doubles the layer size from the original
This doesn't affect any other layer, so your overlay will stay in place.
In addition, you should also do:
layer1.setPosition(x,y); // this will move the layer to the fixed point you want.
All together you could do:
function zoom(){
var position = stage.getUserPosition();
layer1.setScale(2,2);
layer1.setPosition(position.x - layer2.getX(), position.y - layer2.getY()); //move the layer by an offset based on the second layer. This isn't exactly correct so it's something you have to experiment with.
}
Check out this: http://jsfiddle.net/TFU7Z/1/ Maybe is what you are looking for, I did not quite understand the question.
var zoom = function(e) {
var zoomAmount = 1;
layer.setScale(layer.getScale().x+zoomAmount)
layer.draw();
}
document.addEventListener("click", zoom, false)
Just click anywhere to zoom. You can attach the "click" event listener to whatever part of the stage / document you want.
These answers seems not to work awith the KineticJS 5.1.0. These do not work mainly for the signature change of the scale function:
stage.setScale(newscale); --> stage.setScale({x:newscale,y:newscale});
However, the following solution seems to work with the KineticJS 5.1.0:
JSFiddle: http://jsfiddle.net/rpaul/ckwu7u86/3/

Inner div inside an outer rotated div does not follow mouse in case dragging with jQuery UI

I have an inner div inside an outer div. The inner div is draggable and outer is rotated through 40 degree. This is a test case. In an actual case it could be any angle. There is another div called point which is positioned as shown in the figure. ( I am from a flash background . In Flash if I were to drag the inner div it would follow the mouse even if its contained inside an outer rotated div.) But in HTML the inner div does not follow the mouse as it can be seen from the fiddle. I want the div 'point' to exactly follow the mouse. Is this possible. I tried to work it using trignometry but could not get it to work.
http://jsfiddle.net/bobbyfrancisjoseph/kB4ra/8/
Here is my approach to this problem.
http://jsfiddle.net/2X9sT/21/
I put the point outside the rotated div. That way I'm assured that the drag event will produce a normal behavior (no jumping in weird directions). I use the draggable handler to attach the point to the mouse cursor.
In the drag event, I transform the drag offset to reflect the new values. This is done by rotating the offset around the outer div center in the opposite direction of the rotation angle.
I tested it and it seems to be working in IE9, Firefox, and Chrome.
You can try different values for angle and it should work fine.
I also modified the HTML so it is possible to apply the same logic to multiple divs in the page.
Edit:
I updated the script to account for containment behavior as well as cascading rotations as suggested in the comments.
I'm also expirementing with making the outer div draggable inside another div. Right now it is almost working. I just need to be able to update the center of the dragged div to fix the dragging behavior.
Try Dragging the red div.
http://jsfiddle.net/mohdali/kETcE/39/
I am at work now, so I can't do the job for you, but I can explain the mathematics behind the neatest way of solving your problem (likely not the easiest solution, but unlike some of the other hacks it's a lot more flexible once you get it implemented).
First of all you must realize that the rotation plugin you are using is applying a transformation to your element (transform: rotate(30deg)), which in turn is changed into a matrix by your browser (matrix(0.8660254037844387, 0.49999999999999994, -0.49999999999999994, 0.8660254037844387, 0, 0)).
Secondly it is necessary to understand that by rotating an element the axis of the child elements are rotate absolutely and entirely with it (after looking for a long time there isn't any real trick to bypass this, which makes sense), thus the only way would be to take the child out of the parent as some of the other answers suggest, but I am assuming this isn't an option in your application.
Now, what we thus need to do is cancel out the original matrix of the parent, which is a two step process. First we need to find the matrix using code along the following lines:
var styles = window.getComputedStyle(el, null);
var matrix = styles.getPropertyValue("-webkit-transform") ||
styles.getPropertyValue("-moz-transform") ||
styles.getPropertyValue("-ms-transform") ||
styles.getPropertyValue("-o-transform") ||
styles.getPropertyValue("transform");
Next the matrix will be a string as shown above which you would need to parse to an array with which you can work (there are jquery plugins to do that). Once you have done that you will need to take the inverse of the matrix (which boils down to rotate(-30deg) in your example) which can be done using for example this library (or your math book :P).
Lastly you would need to do the inverse matrix times (use the matrix library I mentioned previously) a translation matrix (use this tool to figure out how those look (translations are movements along the x and y axis, a bit like left and top on a relatively positioned element, but hardware accelerated and part of the matrix transform css property)) which will give you a new matrix which you can apply to your child element giving you the a translation on the same axis as your parent element.
Now, you could greatly simplify this by doing this with left, top and manual trigonometry1 for specifically rotations only (bypassing the entire need for inverse matrices or even matrices entirely), but this has the distinct disadvantage that it will only work for normal rotations and will need to be changed depending on each specific situation it's used in.
Oh and, if you are now thinking that flash was a lot easier, believe me, the way the axis are rotated in HTML/CSS make a lot of sense and if you want flash like behavior use this library.
1 This is what Mohamed Ali is doing in his answer for example (the transformOffset function in his jsFiddle).
Disclaimer, it has been awhile since I have been doing this stuff and my understanding of matrices has never been extremely good, so if you see any mistakes, please do point them out/fix them.
For Webkit only, the webkitConvertPointFromPageToNode function handles the missing behavior:
var point = webkitConvertPointFromPageToNode(
document.getElementById("outer"),
new WebKitPoint(event.pageX, event.pageY)
);
jsFiddle: http://jsfiddle.net/kB4ra/108/
To cover other browsers as well, you can use the method described in this StackOverflow answer: https://stackoverflow.com/a/6994825/638544
function coords(event, element) {
function a(width) {
var l = 0, r = 200;
while (r - l > 0.0001) {
var mid = (r + l) / 2;
var a = document.createElement('div');
a.style.cssText = 'position: absolute;left:0;top:0;background: red;z-index: 1000;';
a.style[width ? 'width' : 'height'] = mid.toFixed(3) + '%';
a.style[width ? 'height' : 'width'] = '100%';
element.appendChild(a);
var x = document.elementFromPoint(event.clientX, event.clientY);
element.removeChild(a);
if (x === a) {
r = mid;
} else {
if (r === 200) {
return null;
}
l = mid;
}
}
return mid;
}
var l = a(true),
r = a(false);
return (l && r) ? {
x: l,
y: r
} : null;
}
This has the disadvantage of not working when the mouse is outside of the target element, but it should be possible to extend the area it covers by an arbitrary amount (though it would be rather hard to guarantee that it covers the entire window no matter how large).
jsFiddle: http://jsfiddle.net/kB4ra/122/
This can be extended to apply to #point by adding a mousemove event:
$('#outer').mousemove(function(event){
var point = convertCoordinates(event, $("#outer"));
$("#point").css({left: point.x+1, top: point.y+1});
});
Note that I adjust the x and y coordinates of #point by 1px to prevent it from being directly underneath the mouse; if I didn't do that, then it would block dragging #inner. An alternative fix would be to add handlers to #point that detect mouse events and pass them on to whichever element is directly underneath #point (and stopPropagation, so that they don't run twice on larger page elements).
jsFiddle: http://jsfiddle.net/kB4ra/123/
It seems to me that if you do not rotate the div, the div exactly follows the mouse.
This might be a problem with the plugin..maybe you could simulate the draggable function corretly?
This basically will do what you need though it is buggy. Bind the drag event handler, intercept the ui object and modify it to use the offset X and Y of the parent element. All of the X, Y, top, left etc. are in those objects. I will try to get you a better example sometime when today when I get a bit more time. Good luck!
http://jsfiddle.net/kB4ra/107/
may be this is issue of your jquery library or you can check this by assigning z-order value of inner div and outer div make sure that you give higher number to inner div.

Categories