How does this JavaScript function? - javascript

I was searching up how to fade an element with JavaScript earlier and I came across this function (object). I began wondering how does it work?
var fadeEffect=function(){
return{
init:function(id, flag, target){
this.elem = document.getElementById(id);
clearInterval(this.elem.si);
this.target = target ? target : flag ? 100 : 0;
this.flag = flag || -1;
this.alpha = this.elem.style.opacity ? parseFloat(this.elem.style.opacity) * 100 : 0;
this.si = setInterval(function(){fadeEffect.tween()}, 20);
},
tween:function(){
if(this.alpha == this.target){
clearInterval(this.elem.si);
}else{
var value = Math.round(this.alpha + ((this.target - this.alpha) * .05)) + (1 * this.flag);
this.elem.style.opacity = value / 100;
this.elem.style.filter = 'alpha(opacity=' + value + ')';
this.alpha = value
}
}
}
}();
I know that this is self invoking and only returns one object with two methods. My main concern this why does it use the this keyword? I am assuming the 'this' keyword is a placeholder for the object name "fadeEffect". I would understand if 'this' was used to create multiple objects... but why is it used here?
One other thing bothering me is this ternary operator...
this.target = target ? target : flag ? 100 : 0;
How the heck does that work? It's like two ternary operators combined into one which I never thought was possible?

As for your second question. This will probably make it clearer:
this.target = (target ? target : (flag ? 100 : 0));
So yes, a nested ternary operator! Written out in words:
this.target = (is target a truthy value? Then use target. If not, then use the result from the last part -> (is flag a truthy value? Use 100. Otherwise, use 0)).

Think of it as a namespace. the this keyword refers back to the object literal that the self invoking function returns. This means that this.target is accessible in the global namespace (or whatever scope the fadeEffect was defined) as an object property: fadeEffect.target, but it doesn't interfere with other variables that may exist in the outer scope.
The two methods set new properties of the returned object, that's all there is to it. Personally I find this to be, well, bad code... a closure would have been the better choice in this example:
var fadeEffect=function(){
var elem,target,flag,alpha,si;//make private
return{
init:function(id, flag, target){
elem = document.getElementById(id);
clearInterval(elem.si);
target = target ? target : flag ? 100 : 0;
flag = flag || -1;
alpha = elem.style.opacity ? parseFloat(elem.style.opacity) * 100 :0;
si = setInterval(function(){fadeEffect.tween()}, 20);
},
tween:function(){
if(alpha == target){
clearInterval(si);//this.elem.si doesn't add up, init defines it as this.si
}else{
var value = Math.round(alpha + ((target - alpha) * .05))+ (1 * flag);
elem.style.opacity = value / 100;
elem.style.filter = 'alpha(opacity=' + value + ')';
alpha = value
}
}
}
}();
This does the same thing, but other code cannot interfere with the values of the target, or mess up the interval etc... you're right to say that the this keyword isn't required in this case, but I think the person who wrote this was either unfamiliar with JS closures, or at least insecure about how they work. This code effectively simulates a singleton pattern, or at least treats the object literal as an instance of a class. My guess is, the author is familiar with classical OOP, but not with prototypal inheritance. Anyway, the above code is safer, and safer is better IMHO
On the matter of your nested ternary, I've checked the code below using JSLint, and it suggested an even shorter, yet clearer alternative: use the default operator, followed by a ternary:
//JSLint recommends this
target = argTarget || argFlag ? 100 : 0;
//over nested ternary
target = argTarget ? argTarget : argFlag ? 100 : 0;
Anyway, here's the same code, only not using the dangerous this constructs, but using a closure, one of JavaScripts amazingly powerful features BTW, worth taking a closer look at what you can do with them!
var fadeEffect=(function()
{
var elem,target,flag,alpha,si;//make private
//define private 'methods': functions will be available, but only to return object
//tween shouldn't be callable, it's a callback for the interval, which is set in init
function tween()
{
if(alpha === target)
{
clearInterval(si);//this.elem.si doesn't add up, init defines it as this.si
}
else
{
alpha = Math.round(alpha + ((target - alpha) * 0.05))+ (1 * flag);
//don't know why 1*flag is needed here, suggest:
//alpha = Math.round(alpha + ((target - alpha) * 0.05)) + (+flag); +flag coerces to numeric
elem.style.opacity = alpha / 100;
elem.style.filter = 'alpha(opacity=' + alpha + ')';
}
}
return{
init:function(id, argFlag, argTarget)//arguments !== closure scope
{
if (si !== undefined && si !== null)
{
clearInterval(si);
}
elem = document.getElementById(id);
//JSLint recommends this:
target = argTarget || argFlag ? 100 : 0;
//over nested ternary
target = argTarget ? argTarget : argFlag ? 100 : 0;
flag = argFlag || -1;
alpha = elem.style.opacity ? parseFloat(elem.style.opacity) * 100 :0;
si = setInterval(tween, 20);//just a reference to the tween function will do
}
};
})();
fadeEffect.init('someId',1,50);//will set things in motion
fadeEffect.tween();//undefined
console.log(fadeEffect.target);
fadeEffect.target = document.getElementById('someOtherId');//no problem, but won't change the value of var target
This way, the tween method cannot be called but by the interval, and the element on which the object, and its methods/functions are working their magic can never be overridden by external operations, they are inherent to the object. This makes for a safer construction, what's more, you can only really mess up 1 method: override the .init method, and the object is rendered useless, but harmless. Compare that to your code, where you could mess up both methods, but leave the interval standing... that's bad news: the interval would end up looking for a callback function that could very well have been deleted, causing your code to fail miserably:
//asume your code using this.tween();
fadeEffect.init('id',1,123);
delete fadeEffect.tween;
//inside fadeEffect:
setInterval(function(){fadeEffect.tween()}, 20);
//should be written as:
setInterval(fadeEffect.tween,20);
// === setInterval(undefined,20); === :-(

One more explanation for this.target = target ? target : flag ? 100 : 0;:
if(target){
this.target = target;
}
else{
if(flag){
this.target = 100;
} else {
this.target = 0;
}
}

Related

Javascript: If variable mets a condittion defined on another variable

My problem is simple but I can't find a way to make thag work
The idea is that if a variable (number) mets a condittion defined on another variable (cond), run some code
Example:
var cond = '> 4';
var number = 5;
// Some type of if statement to check if 5 is > 4
You can use eval but usually if you resort to eval, you're not understanding the problem correctly.
var cond = '> 4';
var number = 5;
if (eval(number + cond)) {
console.log(number + cond);
}
Another possibility would be to create functions which correlate with the condition then store the operand in another variable.
var compareFunctions = {
'>': function(a, b) {
return a > b;
}
};
var op = '>';
var operand = 4;
var number = 5;
if (compareFunctions[op](number, operand)) {
console.log(number + op + operand);
}
Do you mean like this?
if (eval(number+cond)){
console.log("success");
  }
You could use a function for the check of the condition.
var cond = function (x) { return x > 4; };
var number = 5;
console.log(cond(number));
The simple, but likely dangerous and ugly way would be to use eval()
var cond = '> 5';
console.log(eval(4 + cond));
console.log(eval(6 + cond));
However, this is dangerous because if that string is in any way coming from the user, they could enter bad things and make bad things happen.
A more proper way to handle it would be to parse it properly:
let cond = '> 5';
const evaluateCondition = (value, condition) => {
// Allows for >, <, >=, <=, !=, ==
let [, operator, operand] = condition.match(/^\s*(>|<|>=|<=|!=|==)\s*(\d+)$/);
switch(operator) {
case '>':
return value > operand;
case '<':
return value < operand;
case '==':
return value == operand;
// ... implement other operators here
}
};
console.log(evaluateCondition(4, cond));
console.log(evaluateCondition(6, cond));
This will let you define valid operators and handle them in a safe manner, also easily catching invalid input.
Below was done before an edit changed the question.
You have to declare the variable outside of it, but you can set it pretty easily. The syntax I would use looks like this:
var a = 5, b;
a == 5 && (b = 2);
The bit after the && only executes if the first condition is true. To set a variable in a syntax like this, you just wrap it in parentheses.
The more traditional way would be to use an if statement:
var a = 5, b;
if (a == 5) {
b = 2;
}
If you declare the second variable inside of the if statement, it doesn't exist outside of it:
var a = 5;
if (a == 5) {
var b = 2;
}
console.log(b) // undefined
which might be what was tripping you up.
You could use eval just be careful and make sure you read up on eval first:
var cond = '> 4';
var number = 5;
console.log(eval(("" + number + cond)));

Why has closure compiler changed 'this'?

In my compressed code, under advanced compilation, the compiler has changed the calling context of my function. I'm after some reasoning why and how, so I can figure out how to fix it.
Back story
I've generated my code into modules and for the past few days I've been converting the react js material ui library into a closure style provide/require syntax. I couldn't get CommonJS to play nicely with my modular approach and I couldn't get goog.module to work with the debug tool I use 'plovr'. Almost there but I'm stumbling with this.
My compiled code has sourcemaps so I can see where it's going wrong and it doesn't seem to make any sense to me.
The error throws here. Note that this is compressed code but you are seeing it mapped to the original code via sourcemaps. decomposeColor doesn't exist because this is equal to the window object.
If I type this into the console.
I then go one level up the stack and type this into the console and it's the correct object I would expect to see one level down.
Here's the same thing but what the actual code looks like compressed
Any idea what can cause the compiler to do this?
UPDATE:
After some pointers in the comments (Thanks Jan) it made sense what I should be looking for, it seems the compiler has converted from my object method
goog.provide('mui.utils.colorManipulator');
mui.utils.colorManipulator = {
//...
/**
* #this {mui.utils.colorManipulator}
*/
fade: function fade(color, amount) {
color = this._decomposeColor(color);
if (color.type === 'rgb' || color.type === 'hsl') color.type += 'a';
return this._convertColorToString(color, amount);
}
//...
}
into a function declared at the global scope.
function kc(f, a) {
f = this.nd(f);
if ("rgb" === f.type || "hsl" === f.type)
f.type += "a";
return this.md(f, a)
}
So the 'this' contexts will be different, I just need to figure out why the compiler would do that.
Update:
Here's all the code for the colorManipulator. It's pretty much ported from this this
goog.provide('mui.utils.colorManipulator')
mui.utils.colorManipulator = {
/**
* The relative brightness of any point in a colorspace, normalized to 0 for
* darkest black and 1 for lightest white. RGB colors only. Does not take
* into account alpha values.
*
* TODO:
* - Take into account alpha values.
* - Identify why there are minor discrepancies for some use cases
* (i.e. #F0F & #FFF). Note that these cases rarely occur.
*
* Formula: http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
*/
_luminance(color) {
color = this._decomposeColor(color);
if (color.type.indexOf('rgb') > -1) {
let rgb = color.values.map((val) => {
val /= 255; // normalized
return val <= 0.03928 ? val / 12.92 : Math.pow((val + 0.055) / 1.055, 2.4);
});
return 0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2];
}
else {
let message = 'Calculating the relative luminance is not available for ' +
'HSL and HSLA.';
console.error(message);
return -1;
}
},
/**
* #params:
* additionalValue = An extra value that has been calculated but not included
* with the original color object, such as an alpha value.
*/
_convertColorToString(color, additonalValue) {
let str = color.type + '(' +
parseInt(color.values[0]) + ',' +
parseInt(color.values[1]) + ',' +
parseInt(color.values[2]);
if (additonalValue !== undefined) {
str += ',' + additonalValue + ')';
}
else if (color.values.length === 4) {
str += ',' + color.values[3] + ')';
}
else {
str += ')';
}
return str;
},
// Converts a color from hex format to rgb format.
_convertHexToRGB(color) {
if (color.length === 4) {
let extendedColor = '#';
for (let i = 1; i < color.length; i++) {
extendedColor += color.charAt(i) + color.charAt(i);
}
color = extendedColor;
}
let values = {
r: parseInt(color.substr(1,2), 16),
g: parseInt(color.substr(3,2), 16),
b: parseInt(color.substr(5,2), 16),
};
return 'rgb(' + values.r + ',' +
values.g + ',' +
values.b + ')';
},
// Returns the type and values of a color of any given type.
_decomposeColor(color) {
if (color.charAt(0) === '#') {
return this._decomposeColor(this._convertHexToRGB(color));
}
let marker = color.indexOf('(');
let type = color.substring(0, marker);
let values = color.substring(marker + 1, color.length - 1).split(',');
return {type: type, values: values};
},
// Set the absolute transparency of a color.
// Any existing alpha values are overwritten.
/**
* #this {mui.utils.colorManipulator}
*/
fade(color, amount) {
color = this._decomposeColor(color);
if (color.type === 'rgb' || color.type === 'hsl') color.type += 'a';
return this._convertColorToString(color, amount);
},
// Desaturates rgb and sets opacity to 0.15
lighten(color, amount) {
color = this._decomposeColor(color);
if (color.type.indexOf('hsl') > -1) {
color.values[2] += amount;
return this._decomposeColor(this._convertColorToString(color));
}
else if (color.type.indexOf('rgb') > -1) {
for (let i = 0; i < 3; i++) {
color.values[i] *= 1 + amount;
if (color.values[i] > 255) color.values[i] = 255;
}
}
if (color.type.indexOf('a') <= -1) color.type += 'a';
return this._convertColorToString(color, '0.15');
},
darken(color, amount) {
color = this._decomposeColor(color);
if (color.type.indexOf('hsl') > -1) {
color.values[2] += amount;
return this._decomposeColor(this._convertColorToString(color));
}
else if (color.type.indexOf('rgb') > -1) {
for (let i = 0; i < 3; i++) {
color.values[i] *= 1 - amount;
if (color.values[i] < 0) color.values[i] = 0;
}
}
return this._convertColorToString(color);
},
// Calculates the contrast ratio between two colors.
//
// Formula: http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
contrastRatio(background, foreground) {
let lumA = this._luminance(background);
let lumB = this._luminance(foreground);
if (lumA >= lumB) {
return ((lumA + 0.05) / (lumB + 0.05)).toFixed(2);
}
else {
return ((lumB + 0.05) / (lumA + 0.05)).toFixed(2);
}
},
/**
* Determines how readable a color combination is based on its level.
* Levels are defined from #LeaVerou:
* https://github.com/LeaVerou/contrast-ratio/blob/gh-pages/contrast-ratio.js
*/
contrastRatioLevel(background, foreground) {
let levels = {
'fail': {
range: [0, 3],
color: 'hsl(0, 100%, 40%)',
},
'aa-large': {
range: [3, 4.5],
color: 'hsl(40, 100%, 45%)',
},
'aa': {
range: [4.5, 7],
color: 'hsl(80, 60%, 45%)',
},
'aaa': {
range: [7, 22],
color: 'hsl(95, 60%, 41%)',
},
};
let ratio = this.contrastRatio(background, foreground);
for (let level in levels) {
let range = levels[level].range;
if (ratio >= range[0] && ratio <= range[1]) return level;
}
},
};
Implications of object property flattening
In Advanced mode the Compiler collapses object properties to prepare
for name shortening. For example, the Compiler transforms this:
var foo = {}; foo.bar = function (a) { alert(a) };
foo.bar("hello"); into this:
var foo$bar = function (a) { alert(a) }; foo$bar("hello"); This
property flattening allows the later renaming pass to rename more
efficiently. The Compiler can replace foo$bar with a single character,
for example.
But property flattening also makes the following practice dangerous:
Using this outside of constructors and prototype methods:
Property flattening can change meaning of the keyword this within a
function. For example:
var foo = {}; foo.bar = function (a) { this.bad = a; }; // BAD
foo.bar("hello"); becomes:
var foo$bar = function (a) { this.bad = a; }; foo$bar("hello");
Before the transformation, the this within foo.bar refers to foo.
After the transformation, this refers to the global this. In cases
like this one the Compiler produces this warning:
"WARNING - dangerous use of this in static method foo.bar" To prevent
property flattening from breaking your references to this, only use
this within constructors and prototype methods. The meaning of this is
unambiguous when you call a constructor with the new keyword, or
within a function that is a property of a prototype.
Source: https://developers.google.com/closure/compiler/docs/limitations?hl=en#implications-of-object-property-flattening]
In other words, the closure compiler does not support the use of this in object literals because of property flattening.
Therefore, a simple solution is to reference the full namespace of the object whose property you are trying to access.
mui.utils.colorManipulator.name_of_function(args);
this shouldn't be really used with object literals as they can be considered like static functions off a namespace. this should be used with functions that are invoked with .bind, .call, .apply or an instance created with a constructor and new. Supposedly it is supported, but closure must not fully understand what is going on: How does "this" keyword work within a function?
I would change the code to refer to the other functions off of mui.utils directly.
fade in your code is a method on an object, and in the compressed version it's not, hence the reference to this is changed from the method's object to the global object.
An alternative to solve this would be using the new function singleton pattern to instantiate your object.
mui.utils.colorManipulator = new function() {
this.fade = function() { /*...*/ }
};
Either that or like jfriend00 suggested, specify the full namespace inside your methods so that the reference isn't lost on the compiler.

Trouble understanding Javascript nested function/closure

I'm attempting to port the following JavaScript code to ruby:
https://github.com/iguigova/snippets_js/blob/master/pokerIn4Hours/pokerIn4Hours.js
I think I have most of it sorted, the function that is giving me grief is:
var kickers = function(idx){ // http://en.wikipedia.org/wiki/Kicker_(poker)
idx = idx || -15;
var notplayed = Math.max(input.length - 1/*player input*/ - 5, 0);
return function(all, cardinality, rank) {
return (all || 0) + (((cardinality == 1) && (notplayed-- <= 0)) ? rank * Math.pow(10, ++idx) : 0);
};
}();
And it is called further down like so:
k = kickers(k, cardsofrank[i], i);
I was wondering if someone could explain how this works in JavaScript. The fact that the inner function has 3 parameters and the outer only has 1 is confusing, especially given that it is called with 3 parameters. I would like to understand what it's trying to accomplish, so that I can port the code with confidence.
function(idx) this function returns a new function function(all, cardinality, rank) and this new function is referred by kickers variable in turn. so kickers is basically pointing at the inner function you have returned.
the only way to call your returned function is this kickers(k, cardsofrank[i], i)
var kickers = function(idx){
var xyz;//below function in return statement can access this and argument idx
return function(...) {//this ensures that your kickers is infact a function
};
}();//invoke this function instantly due to () and assign the output to kickers
When Javascript interpreter read above assignment to Kickers it will execute the anonymous function
Since that function itself returns a function, the kickers will now a function (with closure).
Meaning the kickers function will have a reference to the environment variables (idx and notplayed )
Edit:
1) Where it is getting value of idx - Since nothing is passed while invoking the function(last line ();) the idx will be undefined and idx = idx || -15; will assign the value -15 to it.
2) Could this be re-written without an inner function? - Yes. But the current implementation has an advantage where the scope of idx and notplayed is limited to kicker function and will not be accessible globally. Here is how you can write it directly as a function
/* now idx and notplayed is global- but its meant to be used only by kicker
* we cant move it inside the function definition as it will behave as local variable to the function
* and hence will be freed up once executed and you cannot maintain the state of idx and notplayed
*/
var idx = -15;
var notplayed = Math.max(input.length - 1/*player input*/ - 5, 0);
var kickers = function(all, cardinality, rank) {
return(all || 0) + (((cardinality == 1) && (notplayed-- <= 0)) ? rank * Math.pow(10, ++idx) : 0);
}

Check if element is in user sight vertically

I'm looking for a function that would get an element from the DOM and determine whether it is in the sight of the user vertically?
I searched for a function that would get an element and check if it is in the current scroll height the user is viewing. I ended up trying a bunch of functions that didn't quite do what I needed and I built my own. Since I didn't find such function I'm now sharing it with you guys in case someone needs it in future! :P This is without using any frameworks or plugins.
function visible(a, t){
// a => element
// t => tolerance, how much pixels can be hidden and still return true
var w_top = window.pageYOffset || document.documentElement.scrollTop,
w_hgh = window.outerHeight,
a_top = 0,
a_hgh = a.offsetHeight;
while(a.tagName.toLowerCase() !== 'body') {
a_top += a.offsetTop;
a = a.offsetParent;
}
var b = (w_top + w_hgh) - (a_top + a_hgh);
if(b > (0 - t) && b < (w_hgh - a_hgh + t)){
return true;
}
return false;
}
An example in use:
var element = document.getElementById('id');
if(!visible(element, 50)){
element.focus();
}

Techniques for smoother image animation with JS/CSS

I am using the following code to glide an image across the top layer of a webpage but its a little jittery, giving streaky vertical lines down the image especially when over content with many nested elements. This is the case even when the border is set to zero. Any suggestions for a smoother method for gliding an image with JS/CSS?
border=4;
pps=250; // speed of glide (pixels per second)
skip=2; // e.g. if set to 10 will skip 9 in 10 pixels
refresh=3; // how often looks to see if move needed in milliseconds
elem = document.createElement("img");
elem.id = 'img_id';
elem.style.zIndex="2000";
elem.style.position="fixed";
elem.style.top=0;
elem.style.left=0;
elem.src='http://farm7.static.flickr.com/6095/6301314495_69e6d9eb5c_m.jpg';
elem.style.border=border+'px solid black';
elem.style.cursor='pointer';
document.body.insertBefore(elem,null);
pos_start = -250;
pos_current = pos_start;
pos_finish = 20000;
var timer = new Date().getTime();
move();
function move ()
{
var elapsed = new Date().getTime() - timer;
var pos_new = Math.floor((pos_start+pps*elapsed/1000)/skip)*skip;
if (pos_new != pos_current)
{
if (pos_new>pos_finish)
pos_new=pos_finish;
$("#img_id").css('left', pos_new);
if (pos_new==pos_finish)
return;
pos_current = pos_new;
}
t = setTimeout("move()", refresh);
}
I do not have a solution that I am sure of will prevent the vertical lines from appearing.
I do however have a couple of tips to improve your code so performance increases and you might have a chance that the lines disappear.
Cache the image element outside of your move function:
var image = $("#img_id")[0];
In your code, there is no reason to query the image ID against the DOM every 3 milliseconds. jQuery's selector engine, Sizzle has to a lot of work¹.
Don't use the jQuery CSS function:
image.style.left = pos_new;
Setting a property object is faster than a function call. In the case of the jQuery css function, there are at least two function calls (one to css and one inside css).
Use interval instead of timeout:
setInterval(move, refresh);
I would consider an interval for one-off animations I wanted to be as
smooth as possible
setTimeout or setInterval?
One other option for smoother animation is to use CSS transitions or animations. A great introduction and comparison can be found in CSS Animations and JavaScript by John Resig
Browser support table: http://caniuse.com/#search=transition
A JavaScript library that I find makes CSS animation via JavaScript very easy is morpheus.
¹ Under the hood, this is the code it goes through every 3 milliseconds to find your image:
In a browser that supports querySelectorAll:
Sizzle = function( query, context, extra, seed ) {
context = context || document;
// Only use querySelectorAll on non-XML documents
// (ID selectors don't work in non-HTML documents)
if ( !seed && !Sizzle.isXML(context) ) {
// See if we find a selector to speed up
var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );
if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {
// Speed-up: Sizzle("TAG")
if ( match[1] ) {
return makeArray( context.getElementsByTagName( query ), extra );
// Speed-up: Sizzle(".CLASS")
} else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {
return makeArray( context.getElementsByClassName( match[2] ), extra );
}
}
if ( context.nodeType === 9 ) {
// Speed-up: Sizzle("body")
// The body element only exists once, optimize finding it
if ( query === "body" && context.body ) {
return makeArray( [ context.body ], extra );
// Speed-up: Sizzle("#ID")
} else if ( match && match[3] ) {
var elem = context.getElementById( match[3] );
// Check parentNode to catch when Blackberry 4.6 returns
// nodes that are no longer in the document #6963
if ( elem && elem.parentNode ) {
// Handle the case where IE and Opera return items
// by name instead of ID
if ( elem.id === match[3] ) {
return makeArray( [ elem ], extra );
}
} else {
return makeArray( [], extra );
}
}
try {
return makeArray( context.querySelectorAll(query), extra );
} catch(qsaError) {}
// qSA works strangely on Element-rooted queries
// We can work around this by specifying an extra ID on the root
// and working up from there (Thanks to Andrew Dupont for the technique)
// IE 8 doesn't work on object elements
} else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
var oldContext = context,
old = context.getAttribute( "id" ),
nid = old || id,
hasParent = context.parentNode,
relativeHierarchySelector = /^\s*[+~]/.test( query );
if ( !old ) {
context.setAttribute( "id", nid );
} else {
nid = nid.replace( /'/g, "\\$&" );
}
if ( relativeHierarchySelector && hasParent ) {
context = context.parentNode;
}
try {
if ( !relativeHierarchySelector || hasParent ) {
return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );
}
} catch(pseudoError) {
} finally {
if ( !old ) {
oldContext.removeAttribute( "id" );
}
}
}
}
return oldSizzle(query, context, extra, seed);
};
And a browser that doesn't:
var Sizzle = function( selector, context, results, seed ) {
results = results || [];
context = context || document;
var origContext = context;
if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
return [];
}
if ( !selector || typeof selector !== "string" ) {
return results;
}
var m, set, checkSet, extra, ret, cur, pop, i,
prune = true,
contextXML = Sizzle.isXML( context ),
parts = [],
soFar = selector;
// Reset the position of the chunker regexp (start from head)
do {
chunker.exec( "" );
m = chunker.exec( soFar );
if ( m ) {
soFar = m[3];
parts.push( m[1] );
if ( m[2] ) {
extra = m[3];
break;
}
}
} while ( m );
if ( parts.length > 1 && origPOS.exec( selector ) ) {
if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
set = posProcess( parts[0] + parts[1], context, seed );
} else {
set = Expr.relative[ parts[0] ] ?
[ context ] :
Sizzle( parts.shift(), context );
while ( parts.length ) {
selector = parts.shift();
if ( Expr.relative[ selector ] ) {
selector += parts.shift();
}
set = posProcess( selector, set, seed );
}
}
} else {
// Take a shortcut and set the context if the root selector is an ID
// (but not if it'll be faster if the inner selector is an ID)
if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
ret = Sizzle.find( parts.shift(), context, contextXML );
context = ret.expr ?
Sizzle.filter( ret.expr, ret.set )[0] :
ret.set[0];
}
if ( context ) {
ret = seed ?
{ expr: parts.pop(), set: makeArray(seed) } :
Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
set = ret.expr ?
Sizzle.filter( ret.expr, ret.set ) :
ret.set;
if ( parts.length > 0 ) {
checkSet = makeArray( set );
} else {
prune = false;
}
while ( parts.length ) {
cur = parts.pop();
pop = cur;
if ( !Expr.relative[ cur ] ) {
cur = "";
} else {
pop = parts.pop();
}
if ( pop == null ) {
pop = context;
}
Expr.relative[ cur ]( checkSet, pop, contextXML );
}
} else {
checkSet = parts = [];
}
}
if ( !checkSet ) {
checkSet = set;
}
if ( !checkSet ) {
Sizzle.error( cur || selector );
}
if ( toString.call(checkSet) === "[object Array]" ) {
if ( !prune ) {
results.push.apply( results, checkSet );
} else if ( context && context.nodeType === 1 ) {
for ( i = 0; checkSet[i] != null; i++ ) {
if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
results.push( set[i] );
}
}
} else {
for ( i = 0; checkSet[i] != null; i++ ) {
if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
results.push( set[i] );
}
}
}
} else {
makeArray( checkSet, results );
}
if ( extra ) {
Sizzle( extra, origContext, results, seed );
Sizzle.uniqueSort( results );
}
return results;
};
There are lots of minor ways to tweak you code to run slightly smoother... Use a feedback loop to optimize the step size and delay, look for even steps that don't round up or down causing small jumps at regular intervals, etc.
But the secret API you're probably looking for (and which is used by many of the libraries you are avoiding) is requestAnimationFrame. It's currently non-standarized, so each browser has a prefixed implementation (webkitRequestAnimationFrame, mozRequestAnimationFrom, etc.)
Instead of re-explaining how it helps reduce/prevent tearing and vsync issues, I'll point you to the article itself:
http://robert.ocallahan.org/2010/08/mozrequestanimationframe_14.html
I took a shot at this with a few ideas in mind. I could never get the animation to be incredibly un-smooth, nor did I ever experience any vertical lines, so I'm not sure if it's even an improvement. Nevertheless, the function below takes a few key ideas into account that make sense to me:
Keep the element away from the DOM with a container <div> for the animation. DOM involvement in repaints makes it much longer than it should be for a basic overlay animation.
Keep as much fat as possible out of the move function. Seeing as this function will be called a large amount, the less script there is to run, the better. This includes that jQuery call to change the element position.
Only refresh as much as absolutely necessary. I set the refresh interval here to 121 Hz, but that's an absolute top-end for a 60Hz monitor. I might suggest 61 or less, depending on what's needed.
Only set a value in to the element style object if it's needed. The function in the question did do this, but again it's a good thing to keep in mind, because in some engines simply accessing the setter in a style object will force a repaint.
What I wanted to try out was using the image as the background of an element, so you could just script changing the CSS background-position property instead of changing the element position. This would mean loss DOM involvement in the repaints triggered by the animation, if possible.
And the function, for your testing, with a fairly unnecessary closure:
var border = 4;
var pps = 250;
var skip = 2;
var refresh = 1000 / 121; // 2 * refresh rate + 1
var image = new Image();
image.src = 'http://farm7.static.flickr.com/6095/6301314495_69e6d9eb5c_m.jpg';
// Move img (Image()) from x1,y1 to x2,y2
var moveImage = function (img, x1, y1, x2, y2) {
x_min = (x1 > x2) ? x2 : x1;
y_min = (y1 > y2) ? y2 : y1;
x_max = (x1 > x2) ? x1 : x2;
y_max = (y1 > y2) ? y1 : y2;
var div = document.createElement('div');
div.id = 'animationDiv';
div.style.zIndex = '2000';
div.style.position = 'fixed';
div.style.top = y_min;
div.style.left = x_min;
div.style.width = x_max + img.width + 'px';
div.style.height = y_max + img.height + 'px';
div.style.background = 'none';
document.body.insertBefore(div, null);
elem = document.createElement('img');
elem.id = 'img_id';
elem.style.position = 'relative';
elem.style.top = 0;
elem.style.left = 0;
elem.src = img.src;
elem.style.border = border + 'px solid black';
elem.style.cursor = 'pointer';
var theta = Math.atan2((y2 - y1), (x2 - x1));
(function () {
div.insertBefore(elem, null);
var stop = function () {
clearInterval(interval);
elem.style.left = x2 - x1;
elem.style.top = y2 - y1;
};
var startTime = +new Date().getTime();
var xpmsa = pps * Math.cos(theta) / (1000 * skip); // per milli adjusted
var ypmsa = pps * Math.sin(theta) / (1000 * skip);
var interval = setInterval(function () {
var t = +new Date().getTime() - startTime;
var x = (Math.floor(t * xpmsa) * skip);
var y = (Math.floor(t * ypmsa) * skip);
if (parseInt(elem.style.left) === x &&
parseInt(elem.style.top) === y) return;
elem.style.left = x + 'px';
elem.style.top = y + 'px';
if (x > x_max || x < x_min || y > y_max || y < y_min) stop();
}, refresh);
console.log(xpmsa, ypmsa, elem, div, interval);
})();
};
For your circumstance, you should consider the followings to make animation smoother:
The interval between animation steps (your refresh value) should be long enough for browser to process (JavaScript code, rendering). As my experience, it should be 10 to 20 milliseconds.
Why you made the image position multiple of skip? Set skip value as small as possible (1) could make animation smoother.
Avoid causing browsers reflow if possible (reflow vs repaint).
Using appropriate easing method instead of linear (as in your code) could make animation look better (human sight, not technical)
Optimize JavaScript code for each animation step. This is not problem in simple animation as yours, but you can improve something such as: use setInterval instead of setTimeout, cache image object for fast access, use native JS code to change image position
Hope these help.
Your question really seems to be about browser rendering engines and their capabilities. As you have noticed, there are limitations as to how quick a browser can render animation. If you hit this limitation, you'll see jitter or other 'unsmooth' behavior. Sometimes rendering faults, like not cleaning up parts of the animation or scrambled parts.
Back in the olden days, any form of decent animation was virtually impossible. In time, things got better, but I still remember using the tiniest possible images to keep my nice folding/unfolding menu performing smoothly. Of course, these days we've got hardware accelerated browser rendering, so you can do multiple animations at once, and don't need to worry a whole lot about animation being slow.
But I've been redoing some animations I've used, because my iPad (1) seems quite slow rendering some of them. Like scrolling a large div got quite choppy. So basically, I started to tune things down:
Using simple animation instead of complex, and: no combined animation (like scroll and fade)
Reduce number of html-elements inside animated object
Make animated object smaller
Preload as much as possible
Create space for the animated object (if possible, in case sliding or moving means moving a whole lot of other elements)
This did work, after some trial and error. What you've got to keep in mind is that the javascript is just changing the css properties of html-elements. The browser repaints what the JS tells him to. So the more it tells him, the heavier it gets, and the rendering falls behind.
Looking at performance, it breaks down into three components: CPU, GPU and screen updates. Every browser engine works differently, so performance can differ as well. An interesting look at how this works, comes from the people on the IE 10 team, which is more thorough than I could be: http://blogs.msdn.com/b/ie/archive/2011/04/26/understanding-differences-in-hardware-acceleration-through-paintball.aspx
Javascript animations are always somewhat jittery, since timers aren't very precise. You can get a little better peformance by using a few tricks:
Enable hardware acceleration: img { -webkit-transform: translateZ(0) };
Use setInterval, it can result in smoother animation too, although the change is usually unnoticeable
Set your refresh rate to 1000/60 (60pfs) - that's the screen limit, and timers never go below 4ms
IE9+ seems to solve this by coupling ticks with the screen refresh rate, which makes for much smoother animation, but I wouldn't count on other browsers doing that anytime soon. The future is in CSS transitions.
In CSS, you could use this:
img {
-webkit-transition:2s all linear;
-moz-transition:2s all linear;
-ms-transition:2s all linear;
transition:2s all linear;
}
But since your animation duration depends on the target position to achieve a constant speed, you can manipulate the values via JS:
var img = document.createElement('img')
document.body.appendChild(img)
var styles = {
zIndex : '2000'
, position : 'absolute'
, top : '0px'
, left : '0px'
, border : '4px solid black'
, cursor : 'pointer'
}
Object.keys(styles).forEach(function(key){
img.style[key] = styles[key]
})
var prefixes = ['webkit', 'Moz', 'O', 'ms', '']
, speed = 250
, endPosition = 2000
, transition = Math.floor(endPosition/speed)+'s all linear'
prefixes.forEach(function(prefix){
img.style[prefix+(prefix ? 'T' : 't')+'ransition'] = transition
})
img.onload = function(){
img.style.left = endPosition+'px' // starts the animation
}
img.src = 'http://farm7.static.flickr.com/6095/6301314495_69e6d9eb5c_m.jpg'
(left out a few cross-browser code paths for brevity - onload, forEach, Object.keys)
Try taking advantage of css transforms and requestanimationframe feature.
See the TweenLite library:
http://www.greensock.com/v12/

Categories