Check if element is in user sight vertically - javascript

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();
}

Related

Restore read position in dynamic content on next visit

I have a big chapter of a book that my user reads. Chapters can have any font-size; the window can be any size. How can I save the exact position in text (top most of currently visible window) in a database, so when user continues to read this chapter from any device, I can scroll him there?
I was trying with window.pageYOffset, then percentage of scroll position, but it's not accurate with dynamic window and font-size, and can only work in same environment (font-size, windows size).
My only idea now is to split the chapter into lines:
const lines = (chapter.match(/\r?\n/g) || '').length + 1
And then somehow find a line that is top most of the currently visible window, save its number, and scroll to it on load.
As an alternative, maybe something that can give me the currently top most visible html element so I can stick to it.
Any ideas?
Update: now I'm trying to get all the elements in the parent of the content div and iterate through them getting the element position in the viewport with getBoundingClientRect(). I have hopes for it.
First you get array of paragraphs
let children = Array.from(contentRef.current.children)
Them do binary search of the closest to zero (top of the visible screen) paragraph.
let closestIndex = binaryClosest(
children,
target => 0 - target.getBoundingClientRect().y
)
localStorage.setItem('index', closestIndex)
function binaryClosest(a, compare) {
let i = binarySearch()
if (i === 0) return 0
if (i === a.length) return i - 1
let d1 = -compare(a[i]),
d2 = compare(a[i - 1])
return d1 < d2 ? i : i - 1
function binarySearch() {
let le = 0,
ri = a.length - 1
while (le <= ri) {
let mid = (le + ri) >> 1,
cmp = compare(a[mid])
if (cmp > 0) {
le = mid + 1
} else if (cmp < 0) {
ri = mid - 1
} else {
return mid
}
}
return le
}
}
Now you can save this closestIndex in database and on next load scroll to it:
let children = Array.from(contentRef.current.children)
window.scrollTo(0, children[localStorage.getItem('index')].offsetTop)

What is wrong with this code? Am I going about this the right way?

Okay, so I'm fairly new to programming. I've been learning to code for quite sometime now, but I hadn't really MADE anything. That considered, I'm attempting to make my first project using JavaScript and make a snake game. Unfortunately, I've ran into multiple problems, which obviously is something that comes hand in hand with programming, but I am new and I'm stuck. Can someone help me figure out if I'm coding this in an efficient way. Also, I have a more specific issue. I've added basic movement functionality to the head of my snake, but I can't figure out how to get the rest of it's parts to follow. If someone could explain to me how to do this, that would be incredible. I've worked about two weeks now to try and figure it out and I'm just stumped. I'm using Raphael's JavaScript Library to generate the graphics on an SVG canvas.
/*
Libraries in use:
1. Rapheal
2. jQuery
*/
// This variable is set to an array so that we can add multiple snakeParts to our PrimarySnake.
var snakeParts = [],
// This variable uses Raphael to generate a canvas.
snakeCanvas = Raphael(10, 10, 400, 400),
// This generates a rectangle that fills the canvas.
snakeCanvasBg = snakeCanvas.rect(0,0,400,400),
// This variable is set to an array so that we can use each and every direction that is pressed.
direction = [],
// This variable is set to an array so that we can use the turn coordinates of our first snake part.
turnCoords = [];
// Generates and returns a random number between 0 and 400. This function is used to help generate the goal of our snake at a random location on the canvas.
function getRandNum () {
var rand = Math.round(Math.random()*400);
// This while loop ensures that our snakeGoal never exceeds the coordinates x = 390 or y = 390. If it did, it's parts would be cut from the canvas.
while (rand > 395) {
rand = Math.round(Math.random()*400);
}
// This while loop ensures that our rand variabe will always be divisible by 10, which is used to make sure our snakeGoal and snakePart elements are always rendered in coordinates divisible by 10.
while (rand % 10 !== 0) {
var randString = rand.toString(),
// This variable stores the whole length of our randString variable.
randStringLength = randString.length,
// This variable stores the last number of our rand as a string character.
subtractionChar = randString.charAt(randStringLength - 1),
// This variable stores the last number of our rand as a integer.
subtractionInt = parseInt(subtractionChar),
// Finally, this line subtracts the last number of our rand from the entirety and then sets that value equal to rand, ensuring that rand is always divisible by 10.
rand = rand - subtractionInt;
}
return rand;
}
// This function is called any time a button is pressed. The jQuery which method allows our code to compare if the key pressed is equal to the keyCode of a designated key.
$(document).keydown(
function (pressedDirection) {
if (pressedDirection.which === 37) {
direction.push("left");
} else if (pressedDirection.which === 38) {
direction.push("up");
} else if (pressedDirection.which === 39) {
direction.push("right");
} else if (pressedDirection.which === 40) {
direction.push("down");
} else if (pressedDirection.which === 32) {
direction.push("stop");
}
if (pressedDirection.which === 37 || pressedDirection.which === 38 || pressedDirection.which === 39 || pressedDirection.which === 40 || pressedDirection.which === 32) {
console.log(direction[direction.length - 1]);
PrimarySnake.addTurnCoords();
PrimarySnake.movePeice();
}
// This prevents our screen from scrolling when an arrow key is
pressedDirection.preventDefault();
}
);
function Snake () {
// This method generates a new peice to the Snake.
this.addPart = function () {
console.log(snakeParts.length);
snakeParts[snakeParts.length] = snakeCanvas.rect(0,0,10,10);
snakeParts[snakeParts.length - 1].attr("fill", "blue");
snakeParts[snakeParts.length - 1].attr("stroke-width", ".25");
}
// This method provides the movement functionality of our Snake.
this.moveDirection = function () {
for (value in snakeParts) {
var currentCoord = [snakeParts[value].attr("x"), snakeParts[value].attr("y")];
// This if-else statement moves the snakePart at the -value- index up, down, left, or right according to the last direction pressed.
if (direction[direction.length - 1] === "up") {
snakeParts[value].attr("y", currentCoord[1] - 10);
} else if (direction[direction.length - 1] === "down") {
snakeParts[value].attr("y", currentCoord[1] + 10);
} else if (direction[direction.length - 1] === "left") {
snakeParts[value].attr("x", currentCoord[0] - 10);
} else if (direction[direction.length - 1] === "right") {
snakeParts[value].attr("x", currentCoord[0] + 10);
}
}
}
this.moveInterval;
// This function makes our moveDirection move our snakePeice every 50 milliseconds.
this.movePeice = function () {
var moveDirection = this.moveDirection;
// clearInterval is used to eliminate any interval previously running, ensuring that our peices only move one direction at a time.
clearInterval(this.moveInterval);
this.moveInterval = setInterval(function(){moveDirection()}, 50);
}
// This function adds an array of coordinates to the turnCoords array.
this.addTurnCoords = function () {
turnCoords.push([snakeParts[0].attr("x"), snakeParts[0].attr("y")]);
}
}
// This generates a new instance of our Snake class.
var PrimarySnake = new Snake();
// This generates a new part on the canvas.
PrimarySnake.addPart();
// This fills our snakeCanvasBg with a grey color, giving us a grey background.
snakeCanvasBg.attr("fill", "#CDCDCD");
Well, your code seems nice, or at least "efficient" as you are calling it.
To make the parts of your snake follow its head, you must iterate through its parts and assign each (n+1) piece the coordinates from (n). To do so, start with the last piece and iterate up to the first one, which movement is defined by the user, like in:
this.moveDirection = function () {
// Move every piece except the head.
for (var i = snakeParts.length - 1; i > 0; i--) {
snakeParts[i].attr("x", snakeParts[i-1].attr("x"));
snakeParts[i].attr("y", snakeParts[i-1].attr("y"));
}
// Now move the head.
if (direction[direction.length - 1] === "up") {
snakeParts[value].attr("y", currentCoord[1] - 10);
} else if (direction[direction.length - 1] === "down") {
snakeParts[value].attr("y", currentCoord[1] + 10);
} else if (direction[direction.length - 1] === "left") {
snakeParts[value].attr("x", currentCoord[0] - 10);
} else if (direction[direction.length - 1] === "right") {
snakeParts[value].attr("x", currentCoord[0] + 10);
}
}
That code may need a bit of work but that's the idea. Hope it helps!

Difficulties in converting an recursive algorithm into an iterative one

I've been trying to implement a recursive backtracking maze generation algorithm in javascript. These were done after reading a great series of posts on the topic here
While the recursive version of the algorithm was a no brainer, the iterative equivalent has got me stumped.
I thought I understood the concept, but my implementation clearly produces incorrect results. I've been trying to pin down a bug that might be causing it, but I am beginning to believe that my problems are being caused by a failure in logic, but of course I am not seeing where.
My understanding of the iterative algorithm is as follows:
A stack is created holding representations of cell states.
Each representation holds the coordinates of that particular cell, and a list of directions to access adjacent cells.
While the stack isn't empty iterate through the directions on the top of the stack, testing adjacent cells.
If a valid cell is found place it at the top of the stack and continue with that cell.
Here is my recursive implementation ( note: keydown to step forward ): http://jsbin.com/urilan/14
And here is my iterative implementation ( once again, keydown to step forward ): http://jsbin.com/eyosij/2
Thanks for the help.
edit: I apologize if my question wasn't clear. I will try to further explain my problem.
When running the iterative solution various unexpected behaviors occur. First and foremost, the algorithm doesn't exhaust all available options before backtracking. Rather, it appears to be selecting cells at a random when there is one valid cell left. Overall however, the movement doesn't appear to be random.
var dirs = [ 'N', 'W', 'E', 'S' ];
var XD = { 'N': 0, 'S':0, 'E':1, 'W':-1 };
var YD = { 'N':-1, 'S':1, 'E':0, 'W': 0 };
function genMaze(){
var dirtemp = dirs.slice().slice(); //copies 'dirs' so its not overwritten or altered
var path = []; // stores path traveled.
var stack = [[0,0, shuffle(dirtemp)]]; //Stack of instances. Each subarray in 'stacks' represents a cell
//and its current state. That is, its coordinates, and which adjacent cells have been
//checked. Each time it checks an adjacent cell a direction value is popped from
//from the list
while ( stack.length > 0 ) {
var current = stack[stack.length-1]; // With each iteration focus is to be placed on the newest cell.
var x = current[0], y = current[1], d = current[2];
var sLen = stack.length; // For testing whether there is a newer cell in the stack than the current.
path.push([x,y]); // Store current coordinates in the path
while ( d.length > 0 ) {
if( stack.length != sLen ){ break;}// If there is a newer cell in stack, break and then continue with that cell
else {
var cd = d.pop();
var nx = x + XD[ cd ];
var ny = y + YD[ cd ];
if ( nx >= 0 && ny >= 0 && nx < w && ny < h && !cells[nx][ny] ){
dtemp = dirs.slice().slice();
cells[nx][ny] = 1;
stack.push( [ nx, ny, shuffle(dtemp) ] ); //add new cell to the stack with new list of directions.
// from here the code should break from the loop and start again with this latest addition being considered.
}
}
}
if (current[2].length === 0){stack.pop(); } //if all available directions have been tested, remove from stack
}
return path;
}
I hope that helps clear up the question for you. If it is still missing any substance please let me know.
Thanks again.
I'm not very good in javascript, but I try to implement your recursive code to iterative. You need to store For index on stack also. So code look like:
function genMaze(cx,cy) {
var dirtemp = dirs; //copies 'dirs' so its not overwritten
var path = []; // stores path traveled.
var stack = [[cx, cy, shuffle(dirtemp), 0]]; // we also need to store `for` indexer
while (stack.length > 0) {
var current = stack[stack.length - 1]; // With each iteration focus is to be placed on the newest cell.
var x = current[0], y = current[1], d = current[2], i = current[3];
if (i > d.length) {
stack.pop();
continue;
}
stack[stack.length - 1][3] = i + 1; // for next iteration
path.push([x, y]); // Store current coordinates in the path
cells[x][y] = 1;
var cd = d[i];
var nx = x + XD[cd];
var ny = y + YD[cd];
if (nx >= 0 && ny >= 0 && nx < w && ny < h && !cells[nx][ny]) {
dtemp = dirs;
stack.push([nx, ny, shuffle(dtemp), 0]);
}
}
return path;
}
Does this little code could also help ?
/**
Examples
var sum = tco(function(x, y) {
return y > 0 ? sum(x + 1, y - 1) :
y < 0 ? sum(x - 1, y + 1) :
x
})
sum(20, 100000) // => 100020
**/
function tco(f) {
var value, active = false, accumulated = []
return function accumulator() {
accumulated.push(arguments)
if (!active) {
active = true
while (accumulated.length) value = f.apply(this, accumulated.shift())
active = false
return value
}
}
}
Credits, explanations ans more infos are on github https://gist.github.com/1697037
Is has the benefit to not modifying your code, so it could be applied in other situations too. Hope that helps :)

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/

Display Div relative to Href element

Need to position a div based on the href element position in the page.
The code which I currentl have works on my machine which runs IE8.
The same code does not work on another machine with same configuration . Please help
BTW , the div is absolutely position and in the javascript we have:
divobj.style.left = event.x - 185;
divobj.style.top = event.y - 5;
try to use this function.
function getElementPosition(element) {
var x = 0; var y = 0;
while(element !=null ) {
x += element.offsetLeft || 0;
y += element.offsetTop || 0;
element = element.offsetParent;
if (!element || element.style['position'] == 'relative' || element.tagName == 'BODY') break;
}
return {'x':x, 'y':y};
}
it will retrun the position of your link element, but you have to pass that element as argument to this function.
The solution might be as simple as adding a unit of measurement, e.g.:
divobj.style.left = event.x - 185 + 'px'; divobj.style.top = event.y - 5 + 'px';
Some browsers assume 'px' as the unit of measurement; others don't make an assumption and therefore do nothing with what they consider an invalid value.

Categories