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
Related
I want to create an animation of a path, like a journey/timeline. The user is shown a circle (eventually to be an image), when they click this circle the animation begins and shows a path animating/traveling to another circle with a fade in effect. I have attached an image which I think explains my idea best.
My question is - what would be the recommended way of doing this? css animation or is there a jquery library that would be helpful?
Thank you
I would take svg as base. With Inkscape (or similar) like that, you can design the path visually and include the blue circle.
Than you can inject the svg-code in your html like so (copy the svg code from the generated file):
<div class="svg-container">
<svg>…</svg>
</div>
Finally you can use javascript to reference the circle and the path:
var path = document.querySelector('.path'), //these selectors are just arbitrary
circle = document.querySelector('.circle');
To get a point on the path, you can use:
var point = path.getPointAtLength();
For animation, I assume that you basically know how to do that, since this would be too much to explain here. But lets say that p is the progress of you animation and will be in the range [0,1]. To calculate a point at a given p could be done like so:
let pointAtT = (path, t) => {
let l_total = path.getTotalLength();
return path.getPointAtLength(l_total * t);
}
Having that, you can use the x and y coordinate to manipulate the circle. Be aware of possibly applied transformations, that is why I recommend to transform everything to global coordinate space, calculate there and transform the result back to the item's coordinate space.
Documentation on mdn
There are a some svg libraries that might help you: svg.js, snap.svg and Raphaël.
Alright, so I have a good deal of experience with HTML and CSS, and some experience with Javascript (I can write basic functions and have coded in similar languages).
I'm looking to start some visual projects and am specifically interested in getting into particle systems. I have an idea for something similar to Codecademy's name generator here (https://www.codecademy.com/courses/animate-your-name/0/1) where particles are mapped to a word and move if hovered over. It seems as though alphabet.js is what's really behind Codecademy's demo however I can't understand exactly how they mapped the particles to a word, etc.
I've done some basic tutorials just creating rudimentary particles in a canvas but I'm not sure a canvas is the best way to go - demos that utilize one of the many libraries available (such as http://soulwire.github.io/sketch.js/examples/particles.html) don't use a canvas.
So my question is - what is the best way for a beginner/intermediate in Javascript to start with particle systems? Specifically to accomplish the Codecademy name effect or similar? Should I try to use canvas or which library would be best to start with and how would you recommend starting?
The code for this project is achievable for your intermediate JS programmer status.
How the CodeAcademy project works ...
Start by building each letter out of circles and saving each circle's centerpoint in an array. The alphabet.js script holds that array of circle centerpoints.
On mousemove events, test which circles are within a specified radius of the mouse position. Then animate each of those discovered circles radially outward from the mouse position using simple trigonometry.
When the mouse moves again, test which circles are no longer within the specified radius of the current mouse position. Then animate each of those "outside" circles back towards their original positions.
You can also use native html5 canvas without any libraries...
Another approach allowing any text to be "dissolved" and reassembled
Start by drawing the text on the canvas. BTW, this approach will "dissolve" any drawing, not just text.
Use context.getImageData to fetch the opacity value of every pixel on the canvas. Determine which pixels on the canvas contain parts of the text. You can tell if a pixel is part of the text because it will be opaque rather than transparent.
Now do the same procedure that CodeAcademy did with their circles -- but use your pixels:
On mousemove events, test which pixels are within a specified radius of the mouse position. Then animate each of those discovered pixels radially outward from the mouse position using simple trigonometry.
When the mouse moves again, test which pixels are no longer within the specified radius of the current mouse position. Then animate each of those "outside" pixels back towards their original positions.
[Addition: mousemove event to test if circles are within mouse distance]
Note: You probably want to keep an animation frame running that moves circles closer or further from their original positions based on a flag (isInside) for each circle.
function handleMouseMove(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// calc the current mouse position
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// test each circle to see if it's inside or outside
// radius of 40px to current mouse position
// circles[] is an array of circle objects shaped like this
// {x:,y:,r:,originalX:,originalY:,isInside:}
var radius=40;
for(var i=0;i<circles.length;i++){
var c=circles[i];
var dx=c.x-mouseX;
var dy=c.y-mouseY;
if(dx*dx+dy*dy<radius*radius){
c.isInside=true;
// move c.x & c.y away from its originalX & originalY
}else{
c.isInside=false;
// if the circle is not already back at it's originalX, originalY
// then move c.x & c.y back towards its originalX, originalY
}
}
}
I have a large circle with smaller ones inside made using two.js.
My problem is that these two do not rotate in their own place but in the top left axis.
I want the group of circles (circlesGroup) rotate only inside the large one in a static position. The circlesGroup and the large circle are grouped together as rotatoGroup.
two.bind('update', function(frameCount, timeDelta) {
circlesGroup.rotation = frameCount / 120;
});
two.bind('update', function(frameCount, timeDelta) {
rotatoGroup.rotation = frameCount / 60;
});
The whole code is in CodePen.
All visible shapes when invoked with two.make... ( circles, rectangles, polygons, and lines ) are oriented in the center like this Adobe Illustrator example:
When this shape's translation, rotation, or scale change those changes will be reflected as transformations about the center of the shape.
Two.Groups however do not behave this way. Think of them as display-less rectangles. They're origin, i.e group.translation vector, always begins at (0, 0). In your case you can deal with this by normalizing the translation your defining on all your circles.
Example 1: Predefined in normalized space
In this codepen example we're defining the position of all the circles around -100, 100, effectively half the radius in both positive-and-negative x-and-y directions. Once we've defined the circles within these constraints we can move the whole group with group.translation.set to place it in the center of the screen. Now when the circles rotate they are perceived as rotating around themselves.
Example 2: Normalizing after the fact
In this codepen example we're working with what we already have. A Two.Group that contains all of our shapes ( the bigger circle as well as the array of the smaller circles ). By using the method group.center(); ( line 31 ) we can normalize the children of the group to be around (0, 0). We can then change the translation of the group in order to be in the desired position.
N.B: This example is a bit complicated because it invokes underscore's defer method which forces the centering of the group after all the changes have been registered. I'm in the process of fixing this.
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/
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.