How to vertically scale with Raphael? - javascript

Please consider the following code snippet:
jQuery(function ()
{
drawLogo();
});
function drawLogo()
{
var paper = Raphael('logo', 100, 100);//creates canvas width=height=100px
var rect = paper.rect(1,1,98,98, 10);//chessboard background
rect.attr("fill","#efd5a4");
var circle1 = paper.circle(20,20,12);
var circle2 = paper.circle(50,20,12);
var circle3 = paper.circle(80,20,12);
var circle4 = paper.circle(20,50,12);
var circle5 = paper.circle(50,50,12);
var circle6 = paper.circle(80,50,12);
var circle7 = paper.circle(20,80,12);
var circle8 = paper.circle(50,80,12);
var circle9 = paper.circle(80,80,12);
paper.path("M35,0 L35,100");
paper.path("M65,0 L65,100");
paper.path("M0,35 L100,35");
paper.path("M0,65 L100,65");
circle1.animate({scale:"0"}, 2000);
//setTimeout(circle1.animate({scale:"1"}, 2000), 2000);
}
The animation I'd like to achieve is a chain of two parts, first, a vertical scale animation from 100% to 0%, second, a vertical scale animation from 0% to 100%. The above code scales down both vertically and horizontally, so it is incorrect.
I've check Raphael's documentation but couldn't get it, particularly because I cannot see the correct syntax... Any good API reference like that of jQuery's?
Also, if I make the following change, then Firefox shows an error saying too many recursions:
transform(circle1);
function transform(item)
{
item.animate({scale:"0"}, 2000, transform(item));
}
I know this is bad, but what is the correct way to get a infinite "loop" of animation?
Edit: I modified the code to the following
transform([circle1, circle3, circle5, circle7, circle9]);
function transform(elements)
{
for(var e in elements)
{
e.animate({scale:"0"}, 2000);
}
}
in the hope that this would at least run the first part of animation for 5 circles, but unfortunately, it only gives an error saying e.animate() is not a function. Probably the reason is that when elements are retrieved back from the array, it "loses its type"? (just like in Java when you get an elements from plain old ArrayList, you must explicitly downcast or everything will be just of type object.)
2nd Edit before going to bed
At least the following works for once!
var elements = [circle1, circle3, circle5, circle7, circle9];
for(var i = 0; i < elements.length; i++)
transform(elements[i]);
function transform(e)
{
e.animate({scale: 0},2000, function(){this.animate({scale:1},
2000, transform(this));});
}
Achieved parts: chained two scaling animations one after another, for five circles;
Failed parts: Still not an infinite loop, still not only vertical scale.

It seems scaling of circle in just one direction is not possible. Instead use an ellipse. The scale attribute takes a string like "1 0" which means scale 100% for x (horizontally) and 0% for y (vertically). See attr-scale
So your animation can be achieved by
ellipse.animate({"50%": {scale: "1 0"}, "100%": {scale: "1 1"}}, 2000);
see 3rd method (keyframes) in animate
which means at 50% of animation the ellipse should be scaled 0% vertically and at 100% animation scale it back to 100%.
When you call a function, it will be evaluated immediately. Your transform calls transform(item) again before passing it to animate. Instead you should wrap it in a function and pass it.
Here's the full source
jQuery(function ()
{
drawLogo();
});
function drawLogo()
{
var paper = Raphael('logo', 100, 100);//creates canvas width=height=100px
var rect = paper.rect(1,1,98,98, 10);//chessboard background
rect.attr("fill","#efd5a4");
var ellipse1 = paper.ellipse(20,20,12, 12);
var ellipse2 = paper.ellipse(50,20,12, 12);
var ellipse3 = paper.ellipse(80,20,12, 12);
var ellipse4 = paper.ellipse(20,50,12, 12);
var ellipse5 = paper.ellipse(50,50,12, 12);
var ellipse6 = paper.ellipse(80,50,12, 12);
var ellipse7 = paper.ellipse(20,80,12, 12);
var ellipse8 = paper.ellipse(50,80,12, 12);
var ellipse9 = paper.ellipse(80,80,12, 12);
paper.path("M35,0 L35,100");
paper.path("M65,0 L65,100");
paper.path("M0,35 L100,35");
paper.path("M0,65 L100,65");
var elements = [ellipse1, ellipse3, ellipse5, ellipse7, ellipse9];
for(var i = 0; i < elements.length; i++)
transform(elements[i]);
}
function transform(e)
{
e.animate({
"50%": {scale: "1 0"},
"100%": {scale: "1 1", callback: function() {transform(e);}}
}, 2000);
}

Related

Multistage of svg transform animation with Snap SVG

I'm building a small UI which provides users progress of downloading or loading certain information. Here is the codes so far
http://jsfiddle.net/pge1wukj/4/
var s = Snap("svg");
var movepath = s.select("#movePath").attr({
"fill":"none"
});
var dapath = s.select("#dapath").attr({
stroke: "#cdcdcd",
"stroke-width": 5,
});
var dapoints = [242,334.5, 372,334.5, 372,390, 320.5,390.5 ,308.5,421.5 ,293.5,391.5 ,242,391]
var circle = s.select("circle");
var poly = s.select("polygon");
$("a").click(function(){
circle.animate({
opacity: 0
},100);
poly.polyAnimate(dapoints,100,mina.linear,function(){
moveAlongPath(poly,{x:308,y:421},s.halfArc(308,421,135,382,50,0),100);
});
dapath.animate({
d:"M135,382.5c0,52.159,85,79.031,170.001,79.498 C 390.3,462.466,475.598,436.344,475,382.5",
},100,function(){
dapath.animate({d:"M135,382.5c0,0,85.999-0.467,171,0c85.299,0.468,169,0,169,0"
},100,function(){
dapath.animate({
d:"M135,382.292c0,0,85.999-22.759,171-22.292c85.299,0.468,169,22.292,169,22.292"
},100,function(){
dapath.animate({
d:"M136,382.415c0,0,90.999,13.118,176,13.585c85.299,0.468,164-13.585,164-13.585"
},100,function(){
dapath.animate({
d:"M135,382.5c0,0,85.999-0.467,171,0c85.299,0.468,169,0,169,0"
},500,mina.bounce,function(){
var pathclone = dapath.clone().attr({
stroke: "blue",
strokeDashoffset: 500,
strokeDasharray: 500
});
var datext = s.text(100,330,"90").attr("style","text-align: center");
var banner = s.group(poly,datext);
moveAlongPath(banner,{x:136,y:382.415},movepath,3600);
var tick = 0;
var interval = setInterval(function(){
tick += 1;
var red = Math.random()*255;
var blue = Math.random()*255;
var green = Math.random()*255;
var hex = Snap.rgb(red,green,blue);
var dadatext = $("text").text(tick+" %")
if(tick % 10 == 0){
dadatext.attr({"font-size":"30px","fill":hex});
};
if (tick >= 100){clearTimeout(interval)};
},36);
pathclone.animate({
"stroke-dashoffset":0,
},5300);
var paths = Snap.set().push(dapath,pathclone);
paths.animate({
d:"M135,382.5c0,0,30,17,42,17c10,0.208,298-17,298-17"
},300,function(){
paths.animate({
d:"M135,382.5c0,0,287,17.208,297,17c12,0,43-17,43-17"
},2900,function(){
paths.animate({d:"M135,382.5c0,0,287,17.208,297,17c12,0,43-17,43-17"},100,function(){
paths.animate({
d:"M135,382.5c0,0,85.999-0.467,171,0c85.299,0.468,169,0,169,0"
},200,function(){
$("a").off("click");
/* End of animation */
/* Ready for next transformation*/
banner.animate({
transform: "rotate(180deg)"
},200);
})
})
});
})
});
});
})
})
});
});`
At the end of the animation, the banner should rotate 180 deg on the sharp point. However it doesn't animate as i expected. Is there any solution to this ? transforming animation is intimidating and i don't fully understand it....
I think what you are missing is that you need to provide the original transform to include, otherwise it will assume you are just replacing that transform.
So with this line...
banner.animate({ transform: 'rotate(180)' },200);
What this really means is, I'm going to overwrite any current transforms and animate to a new one of rotate(180).
What you probably want is...keep any existing transforms, and now rotate(180) as well.
So you probably want something more like this...transform() with no parameters will give us the existing transform. So we can combine.
transform -> existingtransform then apply additional transform
This would look like the following.
banner.animate({ transform: banner.transform() + " s-1,1" },200);
I'm not quite sure of the rotation effect you are after (did you mean it to go upside down or back to front?), but 's-1,1' may have been what you were thinking of.
jsfiddle example
Edit: jsfiddle with alternate rotation.
Note, for this, you need to take into account 'where' the polygon is, in relation to the group, as its offset as you have moved it (and then also moved the group).
Edit: The rotation center point is quite difficult, as we have the polygon points offset (not centred around 0) and then translated. Also the group they are in is translated, so you've kinda of got 3 things going on.
To try and help understand getting the actual points, I have rewritten the rotation animation.
banner.animate({ transform: banner.transform() + "r180," + banner.getBBox(1).cx + ',' + banner.getBBox(1).y2 },200);
We get the bounding box, which calculates the centre for us. The pivot point is mid length cx, and the lowest y point is y2.
I suspect there is an easier way to get the whole thing working to reduce the transform complexity in the overall code, but there's a bit too much to break down for a question here.
jsfiddle with getBBox

How can i have a continues animation using Raphael JS?

Im using Raphael JS to animate a rectangle, the problem is that at the second animation, the 'x' position resets to 0.
var paper = Raphael("paper1", 640, 480);
var rect = paper.rect(20,20,50,50).attr({fill:"orange"});
var myAnim = Raphael.animation({transform:'t100,0'},"1000","elastic");
var waitTime = 0;
function animRect(){
rect.animate(myAnim.delay(waitTime));
waitTime+=1000;
rect.animate(myAnim.delay(waitTime));
}
How can i do to have a continus animation ?
Thank's !
Raphael has a repeat infinity option...
var anim = Raphael.animation({transform: "t100,0"}, 2500).repeat(Infinity);
rect.animate(anim)
Its not quite clear if you want 2 separate animations (one reversing) or repeating the same one though. If you do, you can enter multiple animations like the following (think of the number as percentage passed through)...
var anim = Raphael.animation( { 0.5: {transform: "t100,0"}, 1: { transform: 't0,0' } }, 2500 ).repeat(Infinity);
rect.animate(anim)
jsfiddle
Example with pauses...
jsfiddle 2

About image rotation once element with specific id is clicked

Logo and elements from ul once clicked rotates image. By default image is already rotated by certain degrees, then on each click image rotates to necessary value.
So far I was using the following:
$("#objRotates").css('opacity','.2');
var value = 0;
var prev_value = 0;
$( "li" ).click(function() {
var text=$(this).text();
if(text==="text1"){value=0;}
if(text==="text2"){value=33;}
if(text==="text3"){value=66;}
if(prev_value != value){
$("#objRotates").animate({opacity:'1'});
$("#objRotates").rotate({
animateTo:value,
easing: $.easing.easeInOutExpo,
center: ["25px", "150px"],
callback: function(){$("#objRotates").animate({opacity:'0.2'});}
});
}
prev_value = value;
});
Above code is the one that was used before, where images start position was 0 and its animation was triggered from link text.
Using jqueryRotate.js examples(here)
How do I change the code, so that images start position is certain degrees and animation starts if element with specific ID is clicked?
Give at least clue..Cause for now, looking at my old code, I am lost. Thanks in advance.
SIMPLIFIED FIDDLE
Ok, so I've created a couple of samples for you to check out. The first one is very basic and I've simplified the code a little to make it easier to understand. This one just uses completely static values and a static elementId for the event, which I'm pretty sure answers your question based on your response to my comment yesterday. http://jsfiddle.net/x9ja7/594/
$("#elementId").click(function () {
var startingAngle = 45;
var endingAngle = 90;
var elementToRotate = "img";
$(elementToRotate).rotate({
angle: startingAngle,
animateTo: endingAngle
});
});
But I wanted to give another example as well that would be dynamic and repeatable for multiple elements. With the code above, you would have to copy/paste the same code over and over again if you want to perform this animation by clicking different elements. Here's an alternative. In this example, you set all of your parameters in the data attributes in the clickable element, then the function is completely repeatable, you only have to write it once. Less code = everyone happy! Here's the example: http://jsfiddle.net/x9ja7/595/
//#region Default starting angles
$("#image1").rotate({ angle: 90 });
$("#image2").rotate({ angle: 20 });
//#endregion
$(".rotateAction").click(function () {
//#region Optional parameter - used in the optional callback function
var $self = $(this);
//#endregion
var startingAngle = Number($(this).attr("data-startingangle"));
var endingAngle = Number($(this).attr("data-endingangle"));
var elementToRotate = $(this).attr("data-elementtorotate");
//#region If the current angle is the ending angle, reverse the animation - this can be removed if you want, I thought it may be cool to show some of the things you can do with this.
var currentAngle = $(elementToRotate).getRotateAngle();
if ( currentAngle[0] === endingAngle) {
startingAngle = Number($(this).attr("data-endingangle"));
endingAngle = Number($(this).attr("data-startingangle"));
}
//#endregion
$(elementToRotate).rotate({
angle: startingAngle,
animateTo: endingAngle
//#region This is optional - uncommenting this code would make the animation single-use only
//, callback: function () { $self.off().removeClass("clickable"); }
//#endregion
});
});
Hope this helps. If you need any other assistance, please let me know.

How would you create a JQuery / svg click-drag select outline effect?

Not sure exactly what to call it, but I am looking for a way to create a dotted outline/selection box effect via javascript/svg when you click and drag over an area, and then goes away on mouseUp (that could be added if it wasn't an original part) .
A jQuery library would be nice if it exists. I've done some looking around, and haven't found exactly what I am looking for.
I guess the theory would be get the coord from the first click, track the mouse coord moment and adjust the box accordingly.
But not writing it from scratch would be nice.
Here's a demo I made just for you :)
Demo (Static): http://jsfiddle.net/HNH2f/1/
Demo (Animated): http://jsfiddle.net/HNH2f/2/
You can use CSS to control the visual style of the marquee.
You can pass one or two functions to the trackMarquee method; both will be called with four arguments: the x1,y1,x2,y2 bounds of the marquee. The first function will be called when the marquee is released. The second function (if present) will be called each time the marquee moves (so that you can, for example, calculate what items are within that bounding box).
When you start dragging on the SVG document (or whatever element you choose to track) it will create a <rect class="marquee" />; during dragging it will adjust the size of the rectangle. Use CSS (as seen in the demo) to style this rectangle however you want. I'm using the stroke-dasharray property to make the border dotted.
For Stack Overflow posterity, here's the code (on the off chance that JSFiddle is down):
(function createMarquee(global){
var svgNS = 'http://www.w3.org/2000/svg',
svg = document.createElementNS(svgNS,'svg'),
pt = svg.createSVGPoint();
// Usage: trackMarquee( mySVG, function(x1,y1,x2,y2){}, function(x1,y1,x2,y2){} );
// The first function (if present) will be called when the marquee is released
// The second function (if present) will be called as the marquee is changed
// Use the CSS selector `rect.marquee` to select the marquee for visual styling
global.trackMarquee = function(forElement,onRelease,onDrag){
forElement.addEventListener('mousedown',function(evt){
var point0 = getLocalCoordinatesFromMouseEvent(forElement,evt);
var marquee = document.createElementNS(svgNS,'rect');
marquee.setAttribute('class','marquee');
updateMarquee(marquee,point0,point0);
forElement.appendChild(marquee);
document.documentElement.addEventListener('mousemove',trackMouseMove,false);
document.documentElement.addEventListener('mouseup',stopTrackingMove,false);
function trackMouseMove(evt){
var point1 = getLocalCoordinatesFromMouseEvent(forElement,evt);
updateMarquee(marquee,point0,point1);
if (onDrag) callWithBBox(onDrag,marquee);
}
function stopTrackingMove(){
document.documentElement.removeEventListener('mousemove',trackMouseMove,false);
document.documentElement.removeEventListener('mouseup',stopTrackingMove,false);
forElement.removeChild(marquee);
if (onRelease) callWithBBox(onRelease,marquee);
}
},false);
};
function callWithBBox(func,rect){
var x = rect.getAttribute('x')*1,
y = rect.getAttribute('y')*1,
w = rect.getAttribute('width')*1,
h = rect.getAttribute('height')*1;
func(x,y,x+w,y+h);
}
function updateMarquee(rect,p0,p1){
var xs = [p0.x,p1.x].sort(sortByNumber),
ys = [p0.y,p1.y].sort(sortByNumber);
rect.setAttribute('x',xs[0]);
rect.setAttribute('y',ys[0]);
rect.setAttribute('width', xs[1]-xs[0]);
rect.setAttribute('height',ys[1]-ys[0]);
}
function getLocalCoordinatesFromMouseEvent(el,evt){
pt.x = evt.clientX; pt.y = evt.clientY;
return pt.matrixTransform(el.getScreenCTM().inverse());
}
function sortByNumber(a,b){ return a-b }
})(window);
You are lucky I just made this myself. I'm using jQuery SVG plugin ( http://keith-wood.name/svg.html )
$("#paper2").mousedown(function(ev) {
ev.preventDefault();
var pX= (ev.pageX - this.offsetLeft) * viewBox[2]/parseInt($("#paper2").css("width"));
var pY= (ev.pageY - this.offsetTop) * viewBox[3]/parseInt($("#paper2").css("height"));
var rect = svg2.rect(
pX, //X
pY, //Y
1,1, //width and height
{ //Settings, you can make the box dotted here
fill: 'black', "fill-opacity": 0.3, stroke: 'red', strokeWidth: 3, id:rect
}
)
$("#paper2").mousemove(function(ev) {
ev.preventDefault();
var rect= $('#rect');
var pX= (ev.pageX - this.offsetLeft) * viewBox[2]/parseInt($("#paper2").css("width")) - rect.attr("x");
var pY= (ev.pageY - this.offsetTop) * viewBox[3]/parseInt($("#paper2").css("height")) - rect.attr("y");
rect.attr("width", pX);
rect.attr("height", pY);
});
$("#paper2").mouseup(function(ev) {
ev.preventDefault();
var div= $("#paper2");
div.unbind('mousemove');
div.unbind('mouseup');
})
});
paper2 is a div in which I have an svg element (so the svg element and the div have the same height/width). This is how I created the svg2 element:
var svg2;
var root2;
$(document).ready(function() {
$("#paper2").svg({
onLoad: function() {
svg2= $("#paper2").svg('get');
svg2.configure({id: 'svg2'});
var div= $("#paper2");
root2= svg2.root();
$("#svg2").attr("viewBox", viewBox[0]+','+viewBox[1]+','+viewBox[2]+','+viewBox[3]);
},
settings: {}
});
}
If you not using viewbox on the svg element you don't need this on the calculations:
* viewBox[2]/parseInt($("#paper2").css("*****"));
viewbox[2] would be the viewbox width and viewbox[3] would be the viewbox height.

Relatively Position Raphael Objects

I've been messing around with Raphael.js recently and I've run into a problem regarding the position of each Raphael object.
I want to create an arbitrary amount of 'canvases' but have them arranged within a div, already positioned on the page. I've been trying to figure out a way to get them to behave something like a block element, but haven't come up with an answer. Each new raphael object is placed outside of any div.
Here's the html:
...
#content{height:100%; width:980px; margin:0 auto;}
</style>
</head>
<body>
<div id="content"></div>
...
and the javascript:
var previews = [];
var prevSize = 25;
var spacing = 10;
//get container
var container = document.getElementById('content');
//get container width
var containerWidth = parseInt(getComputedStyle(container,"").getPropertyValue('width'));
var prevsPerRow =containerWidth/(prevSize+spacing);
var rowsPerPage = 20;
for(var y=0; y<rowsPerPage-1; y++){
for(var x=0; x<prevsPerRow; x++){
var preview = Raphael((x*prevSize)+(x*spacing), (y*prevSize)+(y*spacing),prevSize, prevSize);
previews.push(preview);
}
}
for(var x=0; x<previews.length-1; x++){
var temp = previews[x];
var rectangle =temp.rect(0,0,prevSize,prevSize);
rectangle.attr('fill','black');
}
One solution I was considering was simply adding the offset of the desired div to the x and y coords of the object, but this doesn't seem like the best solution.
Thanks for the help!
edit: Here is a jsfiddle to help elucidate exactly what I'm getting at.
http://jsfiddle.net/xpNBr/
Well, this is two years too late but I'm not seeing an answer here. So for posterity and future seekers: I'd use the element or element Id factory methods supplied by raphael as described here.
From that page:
// Each of the following examples create a canvas
// that is 320px wide by 200px high.
// Canvas is created at the viewport’s 10,50 coordinate.
var paper = Raphael(10, 50, 320, 200);
// Canvas is created at the top left corner of the #notepad element
// (or its top right corner in dir="rtl" elements)
var paper = Raphael(document.getElementById("notepad"), 320, 200);
// Same as above
var paper = Raphael("notepad", 320, 200);
// Image dump
var set = Raphael(["notepad", 320, 200, {
type: "rect",
x: 10,
y: 10,
width: 25,
height: 25,
stroke: "#f00"
}, {
type: "text",
x: 30,
y: 40,
text: "Dump"
}]);
You could use a different container for every newly created canvas.
Here is a working example, using the addCanvas function to create every new element:
http://jsfiddle.net/creaweb/KtNPS/5/
Note that the spacing between canvas blocks is defined in CSS, as well as their size.

Categories

Resources