I have a list of colors, that needs to be animated as a document body background-color.
var bgs = [
"BlanchedAlmond",
"Blue",
"BlueViolet",
"Brown",
"BurlyWood",
"CadetBlue",
"Chartreuse",
"Chocolate",
"Coral",
"CornflowerBlue",
"Cornsilk",
"Crimson",
"Cyan",
"DarkBlue",
"DarkCyan"
];
Now, using colorToHex() custom function for mootools, I ended up with the following code:
window.addEvent('domready', function() {
var current;
(function() {
selected = ~~(Math.random() * bgs.length);
// is it a right way to avoid the repetition?
current = (selected == current) ? ((bgs.length-1) % (selected+1)) : selected;
// -1 is to avoid the edge case,
// +1 is to avoid the NaN in case select is 0
$(document.body).set('tween', {duration: '1500'})
.tween("background-color",bgs[current].colorToHex());
}).periodical(1000);
});
Questions
(optimization of the aforementioned chunks of code) From the performance optimization perspective, is there a better way to perform this animation?
(vs. jQuery) Would the jQuery counterpart be more efficient and elegant?
Ok, I had 2 minutes to check it and try to make it better :)
..here is the working example : http://jsbin.com/evofuw/2 (and the code here http://jsbin.com/evofuw/2/edit#javascript)
..btw, I found some problems in your version:
selected = ~~(Math.random() * bgs.length); you haven't defined selected, + there is a built in getRandom() method available for Arrays in MooTools.
to avoid repetition and all that 'mess' you did, delete that random element from that array ;)
Why you're not using the onComplete tween callback? Using periodical (setInterval) is never the same as using callbacks (and most of the times is not correct).
Each time you're running that anonym function you're retrieving (by $) the body that is not cached. Ok, it's the body but it's a good habit to cache elements :)
About q#2, definitely not. :)
Here is my version:
// a namespace, more elegant :)
var app = {};
// the array with colors
app.bgs = [
"BlanchedAlmond",
"Blue",
"BlueViolet",
"Brown",
"BurlyWood",
"CadetBlue",
"Chartreuse",
"Chocolate",
"Coral",
"CornflowerBlue",
"Cornsilk",
"Crimson",
"Cyan",
"DarkBlue",
"DarkCyan"
];
// the function to change bg color
app.changeBGColor = function( b ){
// random select element
var selected = app.bgs.getRandom();
// delete that element from the array
app.bgs.erase(selected);
// tween background color
b.tween('background-color',selected.colorToHex());
}
window.addEvent('domready', function() {
// cache body element
var b = $(document.body);
// set tween stuff
b.set('tween', {
duration : 1500,
onComplete : function(){
if( app.bgs.length ) { // if the array contains elements, change color
app.changeBGColor(b);
} else { // otherwise do nothing
console.log('array empty! end!');
}
}
});
// start 1st time
app.changeBGColor(b);
});
ps. if you want to animate 'forever', just use another array (where to push the deleted values) and then swap array when the other one is empty
Answer 1:
I agree with stecb; You can cache the values and make use of getRandom(). But in order to continue the animation indefinitely, you don't want to delete the element from array. Therefore, to avoid the duplicate selection consecutively, you just need to switch the places of (cached_length-1) and (selected+1).
Also, the method colorToHex suggested by csuwldcat (the one you cited) is most costly in the entire animation in terms of performance. I would highly suggest you use Hex code in the bgs array. If that is not an option, the you must use colourNameToHex() function by Greg on the same page.
Finally periodical( _interval ) is for setting the delay between the adjacent tween ops whereas duration is the time taken by one color transition. Mootools also provides a delay() function to pause the sequential flow. But in this case, use of priodical() to fire the transition after fixed interval makes sense.
Here is another version of your code:
window.addEvent('domready', function() {
var cached_length = bgs.length;
var myElement = new Fx.Tween(document.body, {duration: '1500'});
var current, selected;
(function() {
selected = ~~(Math.random() * bgs.length);
current = (selected == current) ?
((selected + 1) % (cached_length - 1)) : selected;
/* console.info("current: "+bgs[current]); */
myElement.start("background-color",bgs[current]); // if you use Hex codes
// instead of color names
}).periodical(1000);
});
Answer 2:
Since jQuery would require a plugin jQuery.Color to animate the background-color, that kind of an extra layered complexity may effect the performance, but it cannot compete the performance of Mootools (which is an extended Javascript core as opposed to a layered framework).
Related
Problem: JavaScript is able to successfully draw several arcs on the screen, but they disappear when run. I am running a simple loop calling the ARC function, I don't get it!?
All I want to do is run each call step by step and sleep! This could be done in any language outside of JavaScript with ease, its a simple procedural Loop.
The problem is my graph shows up for a split second before disappearing. However if I trace through the code via JavaScript debugger it works until it disappears again!
Frustrated! I tried to wrap this with a timeOut, so I can sleep to show a slow animation. What on earth is going on my friends, is this a bug in JavaScript or are my fundamentals not there. I can't understand the codeflow when its so logically written.
https://jsfiddle.net/nd6gktmf/
var array_length = attack_list.length;
//EDITED: declare local variables
var coordinates,
origin_longitude,
origin_latitude,
dest_longitude,
dest_latitude;
for(var i=0; i < array_length; i++) {
coordinates = attack_list[i];
//EDITED: consider using dot notation
origin_longitude = coordinates.origin.longitude;
origin_latitude = coordinates.origin.latitude;
dest_longitude = coordinates.destination.longitude;
dest_latitude = coordinates.destination.latitude;
draw_arc(origin_longitude, origin_latitude, dest_longitude, dest_latitude);
}
This Works only in debug mode! What the....
function draw_arc(origin_longitude, origin_latitude, dest_longitude, dest_latitude) {
var data_to_map =
[{
origin: {
latitude: origin_latitude,
longitude: origin_longitude
},
destination: {
latitude: dest_latitude,
longitude: dest_longitude
}
}];
console.log("****** Begin******");
console.log(origin_longitude);
console.log(origin_latitude);
console.log(dest_longitude);
console.log(dest_latitude);
console.log("****** End ******");
election.arc(data_to_map, {strokeWidth: 2});
}
As Kevin pointed out: Delete all the javascript code you added to your question (of course leave your data: election and attack_list) and replace it with:
// loop over the attack_list
attack_list.forEach(function(attack_item, i){
// now attack_item is one item from the array, i is the index in the array
// we set up a timer FOR EACH item in the array
setTimeout(function(){
// we make an empty array, because election.arc needs an array,
// eventhough we'll only send 1 item in it, we wrap it in an array
var draw_list = [];
draw_list.push(attack_item);
// put the item into the array
election.arc(draw_list,{strokeWidth: 2});
}, i*2000);
// note: the time is i*2000ms = i*2s
// the "1st" item in attack_list is at index 0
// so 0*2000=0 => it'll start drawing it immediately (t=0)
// it animates for about 1s (t=1)
// the last drawn line is still displayed (t=1..2)
//
// t=2: now the timer for the "2nd" item (i=1) starts drawing
// but because we created a new empty array and only added the 2nd item
// into it, when it draws, it erases everything that we drew before
// and now you only see the 2nd item is getting animated.
// etc...
});
https://jsfiddle.net/flocsy/nd6gktmf/3/
If you look at the doc, it seems like you gotta put all of your arcs in the same array.
Calling election.arc probably re-renders the entire image which is why you can only see the last one in the end.
I'm trying to trigger an event half-way through the progress (not time) of a transition. It sounds simple, but since the transition can have any curve it's quite tricky. In my particular case it's not going to be paused or anything so that consideration is out of the way.
(Simplified) essentially I could trigger an animation on a modifier like this:
function scaleModifierTo(stateModifier, scale, animationDuration) {
stateModifier.setTransform(
Transform.scale(scale, scale, scale),
{
duration: animationDuration,
curve: this.options.curve
}
);
}
When the interpolated state of the Transitionable hits 0.5 (half-way through) I want to trigger a function.
I haven't dug that deep behind in the source of famo.us yet, but maybe need to do something like
subclass something and add the possibility to listen when the state passes through a certain point?
reverse the curve defined and use a setTimeout (or try to find a proximity using a few iterations of the chosen curve algorithm (ew))
Is it possible to do this easily? What route should I go down?
I can think of a couple of ways to achieve such, and both lend to the use of Modifier over StateModifier. If you are new, and haven't really had the chance to explore the differences, Modifier consumes state from the transformFrom method which takes a function that returns a transform. This is where we can use our own Transitionable to supply state over the lifetime of our modifier.
To achieve what you wish, I used a Modifier with a basic transformFrom that will alter the X position of the surface based on the value of the Transitionable. I can then monitor the transitionable to determine when it is closest, or in my case greater than or equal to half of the final value. The prerender function will be called and checked on every tick of the engine, and is unbinded when we hit the target.
Here is that example..
var Engine = require('famous/core/Engine');
var Surface = require('famous/core/Surface');
var Modifier = require('famous/core/Modifier');
var Transform = require('famous/core/Transform');
var Transitionable = require('famous/transitions/Transitionable');
var SnapTransition = require('famous/transitions/SnapTransition');
Transitionable.registerMethod('snap',SnapTransition);
var snap = { method:'snap', period:1000, damping:0.6};
var context = Engine.createContext();
var surface = new Surface({
size:[200,200],
properties:{
backgroundColor:'green'
}
});
surface.trans = new Transitionable(0);
surface.mod = new Modifier();
surface.mod.transformFrom(function(){
return Transform.translate(surface.trans.get(),0,0);
});
context.add(surface.mod).add(surface);
function triggerTransform(newValue, transition) {
var prerender = function(){
var pos = surface.trans.get();
if (pos >= (newValue / 2.0)) {
// Do Something.. Emit event etc..
console.log("Hello at position: "+pos);
Engine.removeListener('prerender',prerender);
}
}
Engine.on('prerender',prerender);
surface.trans.halt();
surface.trans.set(newValue,transition);
}
surface.on('click',function(){ triggerTransform(400, snap); });
The downside of this example is the fact that you are querying the transitionable twice. An alternative is to add your transitionable check right in the transformFrom method. This could get a bit strange, but essentially we are modifying our transformFrom method until we hit our target value, then we revert back to the original transformFrom method.. triggerTransform would be defined as follows..
Hope this helps!
function triggerTransform(newValue, transition) {
surface.mod.transformFrom(function(){
pos = surface.trans.get()
if (pos >= newValue/2.0) {
// Do something
console.log("Hello from position: " + pos)
surface.mod.transformFrom(function(){
return Transform.translate(surface.trans.get(),0,0);
});
}
return Transform.translate(pos,0,0)
})
surface.trans.set(newValue,transition);
}
Thank you for your responses, especially #johntraver for the prerender event, I wasn't aware of the existence of that event.
I realised it made more sense that I should handle this logic together with my move animation, not the scale one. Then, I ended up using a (very hacky) way of accessing the current state of the transition and by defining a threshold in px I can trigger my function when needed.
/**
* Move view at index to a specified offset
* #param {Number} index
* #param {Number} xOffset xOffset to move to
* #param {Object} animation Animation properties
* #return void
*/
function moveView(index, xOffset, animation) {
var rectModifier = this._views[index].modifiers.rect;
var baseXOffset = rectModifier._transformState.translate.state[0];
// After how long movement is reflow needed?
// for the sake of this example I use half the distance of the animation
var moveThreshold = Math.abs(baseXOffset - xOffset)/2;
/**
* Callback function triggered on each animation frame to see if the view is now covering
* the opposite so we can trigger a reflow of the z index
* #return void
*/
var prerender = function() {
var numPixelsMoved = Math.abs(baseXOffset - rectModifier._transformState.translate.state[0]);
if (numPixelsMoved > moveThreshold) {
Engine.removeListener('prerender', prerender);
// trigger a method when this is reached
_reflowZIndex.call(this);
}
}.bind(this);
rectModifier.setTransform(
Transform.translate(xOffset, 0, 0),
animation,
function() {
Engine.removeListener('prerender', prerender);
}
);
Engine.on('prerender', prerender);
}
Obviously the ._transformState.translate.state[0] is a complete hack, but I couldn't figure out of getting this value in a clean way without adding my own Transitionable, which I don't want. If there is a cleaner way of finding the current state as a Number between 0.0-1.0 that would be ace; anyone knows of one?
I'm having some troubles when animating the popular Simon Says game.
Here's what I'm doing:
squence = new Array();
colors = ["#green", "#red", "#yellow", "#blue"];
function add_sequence() {
var number = Math.random();
number = number * (4-1);
number = number.toFixed(0);
sequence.push(colors[number]);
for (var i = 0; i < sequence.length; i++) {
anim(sequence[i]);
};
}
function anim(id){
$(id).animate({
opacity: 0.3,
duration: 300
}, function(){
$(id).animate({
opacity: 0
}, 300);
});
}
The logic of the game is working, but I can't make the lights animate one by one. They just animate all at the same time.
I've tried with setTimeout(), but I can't get it work.
First, you'll have to pass the index in the function :
anim(sequence[i], 1);
Then use delay() like that :
$(id).delay(600*i) //Duration of the complete animation
.animate({opacity : 0.3}, 300) //Don't insert the duration in the CSS properties
.animate({opacity : 0}, 300) //You can chain animation
Partial answer since I don't personally have much experience with the jQuery animate function
Judging from the jQuery .animate documentation, I believe you have the animate function parameters incorrect. It should be something like this:
$(id).animate({
opacity: 0.3
// other CSS properties as desired
},
300)
The first parameter should contain the CSS the animate function should progress towards, while the second parameter contains the duration in milliseconds. There is another method as well, with the second parameter as an object containing options, which you can see in the documentation link provided.
Let me know whether that helps.
Change your for loop to a function that calls itself recursively. The recursive call should be from within the setTimeout, this way you get one iteration of the recursion per Timeout time.
function animate(seq, idx, timeout) {
anim(seq[idx]);
setTimeout(function() { animate(seq, idx+1, timeout) }, timeout);
}
Once you have set up the sequence you want to animate, you can start the animation by calling:
animate(sequence, 0, timeout)
and you would get one color animating per timeout.
Hello Stackoverflow community
As I am trying to build a small game, i came to a problem.
Somehow, when i try to fade out multiple shapes, respectivly a group with the shapes in it, some of the shapes will not fade out or browser gets a stack overflow.
So as i tried out several hours to fix the problem i need your help.
Heres the link to a little fiddle i made: http://jsfiddle.net/hnBPT/
As you can see theres a function newFadeShapesOut() which needs the nodes that should be fade out and also the layer of the nodes.
It moves the nodes into a group and fades the group out. Somehow and sometimes, one or more shapes will not fade out or there occurs a fatal error.
Function for fadeout:
function newFadeShapesOut(shapes, layer, callback, speed){
if(typeof(speed) == 'undefined'){
speed = 1;
}
var g = new Kinetic.Group();
console.log(layer.getChildren().length);
console.log(shapes.length);
layer.add(g);
shapes.each(function(shape){
shape.moveTo(g);
});
console.log(layer.getChildren().length);
console.log(shapes.length);
var tween = new Kinetic.Tween({
node: g,
opacity: 0,
duration: speed,
onFinish: function(){
if(typeof(callback) != 'undefined'){
callback();
tween.destroy();
}
}
}).play();
}
PS: Google Chrome is recommend, firefox tends to crash.
Thank you for your help.
EDIT: Sorry i forgot about that, you can activate the script by clicking the red square.
There's some strange behaviour going on here. Look at my comments as I tried to rewrite your function:
function fadeShapesOut(layer, callback, speed) {
var children = layer.children;
//The layer here already shows some children have moved.
//2 children remain, 1 text and 1 rect.
console.log("LAYER");
console.log(layer);
//Again, children shows that there are only 2 children of layer at this point: Test 2 and Button Rect
console.log('CHILDREN');
console.log(children);
if(typeof(speed) == 'undefined'){
speed = 1;
}
var group = new Kinetic.Group();
layer.add(group);
children.each(function(child) {
console.log("CHILD");
console.log(child); //This spits out Test 1, Test 3 and the newly added Group. (Strange order???
child.moveTo(group);
});
//Since group is already added to the layer, you're all of layer's children to group, including group itself. Which is causing a never ending loop of references to group including itself - causing the stack overflow.
var tween = new Kinetic.Tween({
node: group,
opacity: 0,
duration: speed,
onFinish: function(){
if(typeof(callback) != 'undefined'){
callback();
tween.destroy();
}
}
}).play();
}
What's messing you up is that the group is being considered a child of layer (Even though it hasn't been added yet in the order of function calls, which is strange behaviour to me). So when you loop through the children of layer in the each function, you're trying to move group --> group which screws up the reference in a never ending loop.
I logged a bunch of things in my fiddle, so go ahead and take a look to see some of the strange behaviour I was talking about above.
Anyways, if your callback is going to destroy the layer, what is the point of moving everything to a new group in the function? That Group is messing your code up and I don't see the point of it if you're just going to destroy the layer.
Instead you can achieve the effect you want by just tweening the layer itself:
function fadeLayer(layer, callback, speed) {
var tween = new Kinetic.Tween({
node: layer,
opacity: 0,
duration: 2,
onFinish: function(){
layer.destroy();
tween.destroy();
}
}).play();
}
If you must stick with your original function format, then you can grab children by using names:
newsobj[n] = new Kinetic.Text({
nid: n,
x: 140,
y: ((n == 0) ? 294.5 : 304.5 ),
text: news[n],
fill: 'white',
fontFamily: 'Corbel W01 Regular',
fontSize: 11.26,
name: 'fadeThisAway'
});
button = new Kinetic.Rect({
x: 10,
y: 10,
width: 100,
height: 100,
fill: 'red',
name: 'fadeThisAway'
});
In my example, I used the name fadeThisAway. And then, using your old function:
function newFadeShapesOut(layer, callback, speed){
var shapes = layer.get('.fadeThisAway');
if(typeof(speed) == 'undefined'){
speed = 1;
}
var g = new Kinetic.Group();
console.log(layer.getChildren().length);
console.log(shapes.length);
layer.add(g);
shapes.each(function(shape){
shape.moveTo(g);
});
console.log(layer.getChildren().length);
console.log(shapes.length);
var tween = new Kinetic.Tween({
node: g,
opacity: 0,
duration: speed,
onFinish: function(){
if(typeof(callback) != 'undefined'){
callback();
tween.destroy();
}
}
}).play();
}
Instead of passing shapes through the function, just call
var shapes = layer.get('.fadeThisAway');
at the beginning of the function (you're passing layer through the function already anyways) to grab the children that are named fadeThisAway. (Note: This works because the group is not named fadeThisAway)
Working example and comments inside: JSFIDDLE
UPDATE
Okay so I made a basic example of the issue with layer.children
2nd JSFIDDLE
And it looks like that's just how the children of layer works. This proves that you definitely have to distinguish between shapes and group, because the group will always be considered a child of layer.
The naming method works to distinguish your shapes between layers by giving all shapes a common name that excludes groups.
After several attempts to bend projeqht's function to my way i finally did it!
Somehow, the collection shapes just updates itself when adding the group to the layer!
If i use an array instead, it works.
Hope it helps someone!
So here my solution which works like a charm.
function fadeShapesOut(shapes, callback, speed){
layer = shapes[0].getLayer();
if(typeof(speed) == 'undefined'){
speed = 1;
}
var g = new Kinetic.Group();
layer.add(g);
for(i in shapes){
shapes[i].moveTo(g);
}
var tween = new Kinetic.Tween({
node: g,
opacity: 0,
duration: speed,
onFinish: function(){
if(typeof(callback) != 'undefined'){
callback();
}
tween.destroy();
}
}).play();
}
If you have further questions, don't mind contacting me.
Easing the excution flow in JS/JQuery
I've loop like this:
for (int i = 0; i < 100; i++)
{
doSomething(...); // returns momentally
}
I'm looking for a way to apply easing to the execution flow - by giving a total duration and an easing pattern (ex. 2 seconds & easeback). Is is something doable in JS (I'm using jQuery too)?
Update 2 Updated to clarify the question - I'm looking for the following:
Update 3 - Sheikh Heera is right, the sample I gave doesn't illustrate the real problem, execute function is updated to call an external module, which is closer to what I have. I don't see how jQuery's animate can be applied directly for calling functions.
easer.ease({ start: 0,
end: 100,
duration: 900,
easing: "easeOutBounce",
execute: function (e) { ExternalModule.DoSomethingUseful(e); } });
where start the end are integers, specifying the animated range, duration is animation duration in milliseconds, easing is the easing pattern used to animate the values within a range, execute - the function which gets called with values from 0 to 100, using the easing pattern supplied in the sample above it will animate myDiv's height from 0 to 100 within 0.9 seconds using easeOutBounce easing function.
Ideally as a small standalone plugin based on jQuery, definitely not part of Mootools or any other heavy hitters I can't afford bringing them in just for that.
To my best, I tried to achieve the thing you want using jQuery "animate" property.
Using this jQuery property will allow to add "easing", "duration", "callback" etc as needed by you. I used the "step" property to achieve this.
In order to work, you need to add a "dummy" tag to the HTML and hide it.
DEMO: http://jsfiddle.net/vaakash/Wtqm3/
HTML
<!-- Add a dummy tag to apply animate property -->
<span class="holder" style="display:none" ></span>
jQuery
$('.holder').animate(
{
'end': 100 // There is no such "end" css property just using as an end mark
},
{
duration: 500,
step: function(now, fx) {
myFunction(now); // Call your function here with the "now" param (i.e ExternalModule.DoSomethingUseful(now) ) in your case
}
// Add easing property if wanted
}
);
// The function
function myFunction(param){
$('body').append('Value now: ' + param + '</br/>');
}
Hope this helps.
If I understand your question correctly....You could try using .delay(100) or .delay(xmilliseconds) so it takes longer at each step.
Read more about delay on : http://api.jquery.com/delay/
Easing in jQuery
jQuery only has two easings, linear and swing. What you are looking for is the functions used in the jQuery UI. They can be accessed from $.easing.
$.easing demo where you can play with them.
You can call any function you'd like by name $.easing.easeOutBounce(e, t, n, r). The only confusing part is that they are actually 4 variable functions. From the jQuery UI docs:
based on easing equations from Robert Penner
The "standard" way to use them in f(x, 0, 0, 1) since e is the variable we typically want to change. n seems to be a "start point" for most functions, t looks to be a "power" in many of them, and r a linear scale factor.
Disclaimer
This is only my best guess from looking at the jquery and jquery-ui source files. I'd recommend if you want to do easing that you just write your own functions instead of relying on internal parts that are certainly not part of the stable API.
Your ease function
Although I wouldn't recommend making a function like this, it was an interesting experiment. Demo.
var ease = function(options) {
var t = 0;
// we need a time interval for animating
var tstep = options.interval || 10;
var duration = options.duration || 500
var i = options.start || 0;
var end = options.end || 100;
// the easing functions only work over x=0..1
var scale = end - i;
// one divided by the number of tsteps
var interval = tstep/duration;
var easing = options.easing || 'linear';
var callback = options.execute || function(){};
var timeout = function() {
// we call the callback but pass it the scale result of our easing
callback(scale*$.easing[easing](Math.min(i, 1)));
// if we haven't reached the end of the animation, queue up another frame
if (t <= duration) {
window.setTimeout(function() {
timeout();
}, tstep);
i += interval;
t += tstep;
}
};
timeout();
};
ease({
start: 0,
end: 100,
duration: 900,
easing: 'easeOutBounce',
// we'll print to the screen the results of the easing
execute: function(e) {$('body').append('<p>' + e)}
});