I'm using Snap.svg to create some SVGs that animate on hover.
A very simplified jsfiddle is here:
http://jsfiddle.net/62UA7/2/
var s = Snap("#svg");
var smallCircle = s.circle(100, 150, 70);
//animation
function animateSVG(){
smallCircle.animate({cy: 300}, 5000,mina.bounce);
smallCircle.animate({fill:"red"},200);
}
//reset function?
function resetSVG(){
// something here to reset SVG??
}
smallCircle.mouseover(animateSVG,resetSVG);
The hover / animation is working fine.
The intention is to stop the animation and return to original SVG state if the user moves the mouse off the SVG - and this is where I'm currently stuck.
The actual SVG files I'm using are complex, so hoping for a quick way of 'refreshing' the SVG rather than manually moving it back to original position and colour
I'm assuming there's a really easy way of doing this - just can't seem to work it out or find the answer in any documentation anywhere.
Hopefully someone can help - thanks in advance if you can!
If you are only willing to animate between 2 states I found that Codrops animated svg icons did great job with handling this type of snap.svg animations. I have started using their code as a basis for my future exploration of SNAP.SVG. But getting back to the code: the most fun part is that you configure your animation with simple JSON objects such as:
plus : {
url : 'plus',
animation : [
{
el : 'path:nth-child(1)',
animProperties : {
from : { val : '{"transform" : "r0 32 32", "opacity" : 1}' },
to : { val : '{"transform" : "r180 32 32", "opacity" : 0}' }
}
},
{
el : 'path:nth-child(2)',
animProperties : {
from : { val : '{"transform" : "r0 32 32"}' },
to : { val : '{"transform" : "r180 32 32"}' }
}
}
]
},
and you can easily attach any sort of event trigger for animation In/Out. Hope that helps.
Personally I'd probably do it something like the following, storing it in a data element. It depends what problems you are really trying to overcome though, how you are actually animating it (I suspect it could be easier than my solution with certain animations, but trying to think of something that covers most bases), and if you really need it reset, also how many attributes you are animating and if there is other stuff going on...
var smallCircle = s.circle(100, 150, 70);
var saveAttributes = ['fill', 'cy'];
Snap.plugin( function( Snap, Element, Paper, global ) {
Element.prototype.resetSVG = function() {
this.stop();
for( var a=0; a<saveAttributes.length; a++) {
if( this.data( saveAttributes[a] ) ) {
this.attr( saveAttributes[a], this.data( saveAttributes[a] ) );
};
};
};
Element.prototype.storeAttributes = function() {
for( var a=0; a<saveAttributes.length; a++) {
if( this.attr( saveAttributes[a]) ) {
this.data( saveAttributes[a], this.attr( saveAttributes[a] ) );
};
};
};
});
function animateSVG(){
smallCircle.animate({cy: 300}, 5000,mina.bounce);
smallCircle.animate({fill:"red"},200);
};
smallCircle.storeAttributes();
smallCircle.mouseover( animateSVG );
smallCircle.mouseout( smallCircle.resetSVG );
jsfiddle
Related
I'm using Tween.js to animate the uniforms value of a shader upon clicking a button. Here is what I have:
Shader.uniforms.threshold.needsUpdate = true;
function fadeIn() {
new TWEEN.Tween( Shader.uniforms.threshold )
.to( { value : 0.6 }, 100 )
.start();
}
function fadeOut() {
new TWEEN.Tween( Shader.uniforms.threshold )
.to( { value : 2 }, 100 )
.start();
}
document.getElementById("FadeIn").onclick = function() {
fadeIn();
}
document.getElementById("FadeOut").onclick = function() {
fadeOut();
}
The above does not work. When I try refreshing the page, the value does change but the button click does nothing. Does anyone know the mistake in my implementation? Can Tween.js be used like this or is there a better method? Thanks.
Ok here's how I got it working. It seems changing the value of the "Shader" itself is the cause. The above works when I change the uniforms value of the individual objects. So instead of Shader.uniforms.threshold, I did this:
myObject.material.uniforms.threshold, which works.
it's the first time I tried to animate polygon points with snap.svg and I have the felling I'm doing something wrong there.
Here is my code :
var fdr = Snap('#fdright');
var time1_stp0 = [363.617,262.895, 363.562,367.4, 273.145,315.191, 273.145,315.191];
var time1_stp1 = [363.617,262.895, 363.562,367.4, 273.145,315.191, 273.145,210.688];
var timeline1 = fdr.polygon(time1_stp0).attr({fill:'red',opacity:'0.5'});
timeline1_anim = function(){
timeline1.animate({"points":time1_stp1},3000,mina.linear);
}
timeline1_anim();
As soon as the page is loaded, my polygon disappears (I guess it's because my function is called right after the creation of the polygon). I checked the html, my polygon's still there but here is what i get :
<polygon fill="#ff0000" style="opacity: 0.5;" points="363.617"></polygon>
I don't get what might be the issue, so if someone's got an answer i'll be glad to hear it.
EDIT : I tried to add "toString()" but it's still not working :
timeline1_anim = function(){
timeline1.animate({"points":time1_stp1.toString()},3000,mina.linear);
}
I think there's a bug in Snaps polygon animation.. its listed here There is a patch submitted linked from there.
However, you can get around this easily by animating the array values though if needed.
timeline1_anim = function(){
Snap.animate(time1_stp0, time1_stp1,
function( val ){
timeline1.attr({ points: val })
},
2000);
}
jsfiddle
If you are doing a lot of them, you could write a small plugin to include it...
Snap.plugin( function( Snap, Element, Paper, global ) {
Element.prototype.polyAnimate = function( destPoints, duration, easing, callback ) {
var poly = this;
Snap.animate( this.attr('points'), destPoints,
function( val ){ poly.attr({ points: val }) }, duration, easing, callback)
};
});
timeline1.polyAnimate( time1_stp1, 2000, mina.linear, function() { alert('finished')})
jsfiddle
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.
I have a small program that uses RaphaelJS to accept some nodes and edges, much like arborjs and sigmajs, to produce tree graphs. I would use either of the said libraries but I am trying to learn some JS and graph theory and I figured why not try something like that on my own.
The nodes are iterated through and placed onto the paper, and a callback function is given for hover over and hover out. The animation part of the hover over hover out works however the popup text shows the last value that was apart of the iteration when it was assigned.
I have found that this is because I need to use closures, however I have attempted to write a closure following several different examples that I have found.
I have posted an example on JSFiddle: link
var jgraph = {
obj_nodes : [],
obj_edges : [],
addNodes : function(obj) {
this.obj_nodes[obj.id] = obj;
},
getNodes : function() {
return this.obj_nodes;
},
addEdges : function(obj) {
this.obj_edges[obj.id] = obj;
},
getEdges : function() {
return this.obj_edges;
},
createGraph : function(paper, obj) {
var point_x, point_y,
source_x, source_y, target_x, target_y, popup;
for (var i = 0; i < obj.nodes.length; i++) {
this.obj_nodes[obj.nodes[i].id] = obj.nodes[i],
point_x = this.obj_nodes[obj.nodes[i].id].x,
point_y = this.obj_nodes[obj.nodes[i].id].y;
popup = jgraph.createPopup(paper, obj.nodes[i].id);
paper.circle(point_x, point_y, 10).attr({
fill : "#e74c3c",
stroke : "none"
}).hover(function() {
this.animate({
fill : "#3498db",
transform : "s1.5"
}, 900, "elastic");
popup.show().toFront();
}, function() {
this.animate({
fill : "#e74c3c",
transform : "s1"
}, 900, "elastic");
popup.hide();
});
}
for (var i = 0; i < obj.edges.length; i++) {
this.obj_edges[obj.edges[i].id] = obj.edges[i];
source_x = this.obj_nodes[obj.edges[i].source].x,
source_y = this.obj_nodes[obj.edges[i].source].y,
target_x = this.obj_nodes[obj.edges[i].target].x,
target_y = this.obj_nodes[obj.edges[i].target].y;
p.path("M " + source_x + " " + source_y + " L " + target_x + " " + target_y).toBack().attr({
stroke : "#e74c3c",
"stroke-width" : 4
});
}
},
createPopup : function(paper, id) {
return paper.text(this.obj_nodes[id].x, this.obj_nodes[id].y - 20, this.obj_nodes[id].label).hide().attr({
"font-size" : "13px"
});
}
};
Thanks in advance!
Closures can certainly be maddening. In this case, the problem is that you're installing your hover handlers in the context of the for loop, and the closure ties the handler's reference to popup to the variable defined outside of the context of the for loop. Naturally, by the time the hover handlers fire, the for loop has finished executing and the value of popup reflects the last label the loop handled.
Try this instead -- you're basically just declaring an anonymous function inside your for loop and passing each value of popup into that function, which makes the closure bind to the values of specific calls to that anonymous function:
popup = jgraph.createPopup(paper, obj.nodes[i].id);
( function( popup )
{
paper.circle(point_x, point_y, 10).attr({
fill : "#e74c3c",
stroke : "none"
}).hover(function() {
this.animate({
fill : "#3498db",
transform : "s1.5"
}, 900, "elastic");
popup.show().toFront();
}, function() {
this.animate({
fill : "#e74c3c",
transform : "s1"
}, 900, "elastic");
popup.hide();
});
} )( popup );
If I were you, I would probably assign the label to each element using Raphael elements' data method, and then create and destroy the text elements each time a hover is started and ended respectively. That way you don't need to worry about managing many invisible elements at all =)
Here's a fork of your fiddle in working condition.
Looking for some advice (example would be great) on dragging and dropping SVGs using RaphaelJS.
I have found how to drag an object created withint Raphael
window.onload = function() {
var R = Raphael("canvas", 500, 500);
var c = R.circle(100, 100, 50).attr({
fill: "hsb(.8, 1, 1)",
stroke: "none",
opacity: .5
});
var start = function () {
...
},
move = function (dx, dy) {
...
},
up = function () {
...
};
c.drag(move, start, up);
};
But I need to be able to adapt this to work with a seperate SVG file(s) so for example
myPage.html has myImage.svg, I need to be able to drag myImage.svg around the 'canvas'.
I'm thinking something like
var c = R.SOMEMETHOD('myImage.svg');
...
c.drag(move, start, up);
For example.
Is there a way to do this and if so, an example would be brilliant!
This magic method doesn't exist in RaphaelJS. But there is a way to accomplish this. You can have a look at the raphael-svg-import project on GitHub which works well for basic svgs
Then, you'll want to use a grouping facility as you cannot use the Set functionnality of RaphaelJS
1 - import your SVG
2 - As you import, mark the elements so they belong to the same group
Enjoy !!