Javascript Animation on scroll, Vanilla JS - javascript

I want to use this code that i have seen on github, but I don't know how to apply this code on my HTML, to have an scrolling effect.
The point is, I don't know how to run use this code
source https://gist.github.com/andjosh/6764939
document.getElementsByTagName('button')[0].onclick = function () {
scrollTo(document.body, 0, 1250);
}
function scrollTo(element, to, duration) {
var start = element.scrollTop,
change = to - start,
currentTime = 0,
increment = 20;
var animateScroll = function(){
currentTime += increment;
var val = Math.easeInOutQuad(currentTime, start, change, duration);
element.scrollTop = val;
if(currentTime < duration) {
setTimeout(animateScroll, increment);
}
};
animateScroll();
}
//t = current time
//b = start value
//c = change in value
//d = duration
Math.easeInOutQuad = function (t, b, c, d) {
t /= d/2;
if (t < 1) return c/2*t*t + b;
t--;
return -c/2 * (t*(t-2) - 1) + b;
};

First you have to replace document.body with document.documentElement, as document.body.scrollTop() has been deprecated.
Edit: it seems that I was not completely right about document.body.scrollTop() being deprecated. The best solution to support multiple browsers is to check for both cases.
Second, you need to set a value > 0 for the 'to' parameter, as Quantastical already pointed out.
Also make sure you have a <button> element. It should work then.

Related

HTML progress element not updating

This is a problem I've been struggling with for a while now. I've implemented an HTML5 progress element like so:
<progress id="progress" value="0" max="100"></progress><output class="percent" id="percent">0</output><span class="prozent">%</span>
Suppose I update the value of progress and percent in a JS loop:
i = 0;
pBar = document.getElementById('progress');
pBar.value = i;//start at 0%
percent = 1;//set target for next progress bar update in %
//DO
do {
//IF(i > i_max) EXIT
//CALL VERLET
VERLET(i, x, v, a, time, const0, gamma, A_o, omega, dt);
x_plot[i] = [time[i], x[i]];
v_plot[i] = [time[i], v[i]];
a_plot[i] = [time[i], a[i]];
//i = i + 1
i = i + 1;
if (parseInt(i / i_max * 100, 10) === percent) {
pBar.value = percent;//update progress bar
document.getElementById('percent').innerHTML = percent;
percent = percent + 1;
}
//END DO
} while (i < i_max);
This is the relevant portion of my script. Please assume all variables are properly declared and initialized, and the script as whole is linted. A draft of the page can be found here.
I don't understand why the values of the progress and output elements are not updated until after the calculation is finished. Or this is what appears to happen. IE11(?) seems to show some updating when initially loading the page, but again doesn't update the progress bar for a new calculation without reloading.
Here is something along similar lines, but not using this modern method. I've taken a stab at getting this example to work, without success. This example, though better written I think, might shed some light on the problem.
Is it a timeout issue? Is there a simple jQuery method I could make use of? Any feedback on this problem will be appreciated.
You can't do those kind of operations in a single while-loop.
What happens is the loop will run until it ends and you'll only be able to see the result when it finishes the operation.
For this kind of problems you should go for an asynchronous way.
function updateDom() {
// do stuff
setTimeout(updateDom, 500);
}
setTimeout(updateDom, 500);
As #epascarello says, you cant update the DOM in a while loop.
Update it with an interval:
var interval = setInterval(function () {
VERLET(i, x, v, a, time, const0, gamma, A_o, omega, dt);
x_plot[i] = [time[i], x[i]];
v_plot[i] = [time[i], v[i]];
a_plot[i] = [time[i], a[i]];
i = i + 1;
if (parseInt(i / i_max * 100, 10) === percent) {
pBar.value = percent;//update progress bar
document.getElementById('percent').innerHTML = percent;
percent = percent + 1;
}
if (i < i_max) {
clearInterval(interval);
}
}, 100);
Here's how I got my progress bar to animate:
HTML
<progress id="progress" value="0" max="100"></progress><output class="percent" id="percent">0</output><span class="prozent">%</span>
JS
start = new Date().getTime();
//do stuff
end = new Date().getTime();
t = (end - start);
document.getElementById('progress').value = 0;
document.getElementById('percent').innerHTML = 0;
if (parseInt(t / 100.0, 10) <= 1) {
t = 1;//minimum update interval in ms
} else {
t = parseInt(t / 100.0, 10);//actual update interval in ms
}
go = setInterval(animate, t);
value = 0;
max = 100;
go = 0;
function animate() {
if (value >= max) {
clearInterval(go);
return;
}
value += 1;
document.getElementById('progress').value = value;
document.getElementById('percent').innerHTML = value;
if (value === max) {
clearInterval(go);
}
}
This script can be invoked when the page loads via <body onload="amp()"> or when prompted by the user with <input type="number" name="theta0" id="theta0" value="0.1" min="0.0" max="1.6" step="0.1" onchange="amp()">. As you can see, the update interval is tied to the CPU clock time in ms since I can't update the DOM while in a while loop. It's clunky, but it works.

Record and "playback" css 3d transforms

I'm trying to record a user's mouse and key inputs, then "play them back" to the user by triggering events at chronologically the same time they occurred. That sentence is a little confusing so I'll explain my code:
var $dancerContainer = $('.dancerContainer');
var count = 3;
var captured;
var countInterval;
$dancerContainer is the element I want to animate, which is a div.
count is the duration of the 'recording' phase.
captured will eventually be an object that holds the events, keyed by the elapsed time in milliseconds they occurred since the start of the recording.
function captureInput() {
var mouseCapture = [];
var keyCapture = [];
var start = new Date().getTime();
$(document).on('mousemove.capture', function(event) {
event.t = new Date().getTime() - start;
mouseCapture.push(event);
});
$(document).on('keyup.capture', function(event) {
event.t = new Date().getTime() - start;
keyCapture.push(event);
});
setTimeout(function() {
$(document).off('.capture');
captured = chronoCaptures(mouseCapture, keyCapture);
}, 3000);
}
The captureInput func tags an elapsed time on the event before pushing them to mouse and keyup arrays, mouseCapture and keyCapture, then unbinds the listeners.
function chronoCaptures(mouse, keyboard) {
var greater = (mouse.length > keyboard.length) ? mouse.length : keyboard.length;
var chrono = {};
var j = 0;
var k = 0;
for (var i = 0; i < greater; i++) {
if (keyboard[k] == undefined) {
chrono[mouse[j].t] = mouse[j];
j++;
} else if (mouse[j] == undefined) {
chrono[keyboard[k].t] = keyboard[k];
k++;
} else {
if (mouse[j].t < keyboard[k].t) {
chrono[mouse[j].t] = mouse[j];
j++;
} else {
chrono[keyboard[k].t] = keyboard[k];
k++;
}
}
}
return chrono;
}
chronoOrderCaptures then takes the two arrays of events and returns an object whose keys are the times each event occurred. Now that I look at this code again, since I'm putting the events into an object anyway, it doesn't matter what order they get put in. I might overwrite an event with another, in which case I want the key event to take precedence (going to refactor this, but this is beside the point).
function replayDance(captured, duration) {
var d = duration * 1000;
var start = new Date().getTime();
var elapsed = 0;
while (elapsed <= d) {
elapsed = new Date().getTime() - start;
var c = captured[elapsed];
if (c) {
$dancerContainer.trigger(c)
}
}
}
Finally, replayDance waits for the duration of the recording and checks how much time has elapsed. If the captured object contains an entry # that amount of elapsed time, I trigger the event on the document.
WHEW. Thank you if you've gotten this far. Now to the problem!! (what?) The problem I'm having is the mouse events all get played back 'at once'. There's no pausing occurring, I don't see them executed in sequence as if I was actually moving the mouse, although it seems like they ought to be getting triggered at roughly the same time they were recorded.
Finally here is the handler for mousemove events:
$(document).on('mousemove', followMouse);
function followMouse(event) {
var width = $(window).width();
var height = $(window).height();
var mouseX = event.pageX - (width * 0.25);
var mouseY = event.pageY - (height * 0.25);
var angleX = (mouseY / height) * 45;
var angleY = (mouseX / width) * 45;
dancer.style.webkitTransform = "rotateX(" + angleX + "deg) rotateY(" + angleY + "deg)";
}

Back to top button without jQuery

I want to create a button "Back to top" with javascript. My code (which I found on StackOverflow) does not work when I click the button nothing happens.
HTML
<button type="button" id="backtotop_js">To the top</button>
JAVASCRIPT
document.getElementById('backtotop_js').onclick = function () {
scrollTo(document.documentElement, 0, 1250);
};
function scrollTo(element, to, duration) {
var start = element.scrollTop,
change = to - start,
currentTime = 0,
increment = 20;
var animateScroll = function(){
currentTime += increment;
var val = Math.easeInOutQuad(currentTime, start, change, duration);
element.scrollTop = val;
if(currentTime < duration) {
setTimeout(animateScroll, increment);
}
};
animateScroll();
}
Math.easeInOutQuad = function (t, b, c, d) {
t /= d/2;
if(t < 1)
return c/2*t*t + b;
t--;
return -c/2 * (t*(t-2) - 1) + b;
};
(I'm using Chrome and Firefox)
Where's the mistake?
Will make the scroll to top without animation in vanilla JS
document.getElementById('backtotop_js').onclick = function () {
document.documentElement.scrollTop = 0;
}
EDIT:
changed document.getElementsByTagName('body')[0] to document.documentElement as per Rudie's comment below.
Definition and Usage (of document.document.Element)
The documentElement property returns the documentElement of a document, as an Element object.
For HTML documents the returned object is the HTML element.
Note: If the HTML element is missing, the return value is null.
http://www.w3schools.com/jsref/prop_document_documentelement.asp
you shoud scroll not the html element but instead the body... as vikton was giving an example.

Dynamically change style of all <img> tags

I am trying to simulate shaking by changing the position of img tags within a div.
I have it working for one img at a time.
Is there a way to change the style of all the img tags within a div at once?
This is what I am currently doing:
bird = document.createElement('img');
bird.setAttribute('src',birdie);
bird.setAttribute('class', 'birdie');
bird.setAttribute('id', id);
bird.setAttribute('onLoad', 'shakeAll()');
map.appendChild(bird);
birds++;
if(birdmap[0] == 0){
birdmap = [id];
}else{
birdmap+=[,id];
}
this ShakeAll function is also at onLoad of body:
function shakeAll(){
if (birdmap[0] == 0) return;
i = 1;
while(i <= birds){
shakeIt(birdmap[i]);
i++;
}
setTimeout("shakeAll()",initialSpeed);
}
Note: the imgs are absolute
function shakeIt(id){
shake = document.getElementById(id);
j=1;
while(j<4){
if (j==1){
shake.style.top=parseInt(shake.style.top)+distance+"px";
}
else if (j==2){
shake.style.left=parseInt(shake.style.left)+distance+"px";
}
else if (j==3){
shake.style.top=parseInt(shake.style.top)-distance+"px";
}
else{
shake.style.left=parseInt(shake.style.left)-distance+"px";
}
j++;
}
//setTimeout("shakeIt(id)", 50);
}
I couldn't really tell what you were trying to do in your code. Here's some code that shows the basics of moving some images back and forth:
// quadratic easing in/out - acceleration until halfway, then deceleration
// t = time into the animation
// d = duration of the total animation
// b = base value
// c = max change from base value (range)
var easeInOutQuad = function (t, b, c, d) {
if ((t/=d/2) < 1) return c/2*t*t + b;
return -c/2 * ((--t)*(t-2) - 1) + b;
};
var linearTween = function (t, b, c, d) {
return c*t/d + b;
};
// cubic easing in/out - acceleration until halfway, then deceleration
var easeInOutCubic = function (t, b, c, d) {
if ((t/=d/2) < 1) return c/2*t*t*t + b;
return c/2*((t-=2)*t*t + 2) + b;
};
function shakeAll() {
var container = document.getElementById("container");
var imgs = container.getElementsByTagName("img");
// save away original position
for (var i = 0; i < imgs.length; i++) {
imgs[i].basePos = parseInt(imgs[i].style.left, 10);
}
var numShakes = 0;
var maxShakes = 10;
var range = 100;
var direction = 1;
var duration = 300; // ms
var startTime = (new Date()).getTime();
var deltas = [];
function shakeImgs() {
var now = (new Date()).getTime();
var elapsed = Math.min(now - startTime, duration);
var delta = Math.round(easeInOutQuad(elapsed, 0, range, duration));
delta *= direction;
for (var i = 0; i < imgs.length; i++) {
var basePos = imgs[i].basePos;
if (direction < 0) {
basePos += range;
}
imgs[i].style.left = (basePos + delta) + "px";
}
if (now - startTime >= duration) {
startTime = now;
direction *= -1;
++numShakes;
}
if (numShakes < maxShakes) {
setTimeout(shakeImgs, 10);
}
}
shakeImgs();
}
Working demo and HTML shown here: http://jsfiddle.net/jfriend00/ED5yA/
And, here's a fun one that adds some random shakiness (jitter) to the animation: http://jsfiddle.net/jfriend00/jM8jx/.
The basics of obtaining the list of images to operate on is this part:
var container = document.getElementById("container");
var imgs = container.getElementsByTagName("img");
This gets the container object and then gets all image objects in that container. You can see the corresponding HTML in the jsFiddle. This code implements a positioning scheme that slows the velocity down at the edges and goes the fastest in the middle of the range. The rest of the motion is controlled by the intial value of the variables declared in shakeAll(). These can be edited or can be changed to be passed into the function.
give a common class name to all imgs that you wanna shake. and then, user getElementsByClassName() instead of getElementById() to return an array of the elements which have the specific class name. then use a loop to animate each.
but if you want ALL imgs to be animated, use element.getElementsByTagName() or document.getElementsByTagName() instead.

Why is this 'for' loop not working?

I have a Javascript object called TweenManager which contains an array of Tween objects. The TweenManager should call the step() method on each tween in the 'tweens' array and all the tweens should run at the same time.
However, what's actually happening is that the TweenManager only runs one tween at a time, and doesn't start the next one until the previous tween is complete.
Here's the code for the tween manager
UPDATE: It might make more sense to look at it here
//Manage all tweens
function TweenManager(){
this.tweens = new Array();
this.timer;
this.start = function(){
this.timer = setInterval(this.run, 1, this);
}
// Loop through all tweens and call the step method
this.run = function(myself){
console.log(myself.tweens.length);
// stop the interval if the tween array is empty
if(myself.tweens.length == 0){
clearInterval(myself.timer)
}
// loop through all tweens and call the step() method
// !! Here's there the problem appears to be
for(i = 0; i < myself.tweens.length; i++){
thisTween = myself.tweens[i]
console.log(thisTween.element.attr('id'));
thisTween.step() // if I remove this, the line above logs the id's as expected
// clean up if the tween is complete
if(thisTween.t == thisTween.d){
myself.tweens.splice(i, 1);
}
}
}
this.addTween = function(b,c,d,element,suffix, decimal){
this.tweens.push( new Tween(b,c,d,element,suffix, decimal) )
}
}
The problem appears to be in the for loop. I have a hunch that this might have something to do with passing in this in the setInterval, although it's just a hunch, I don't understand what the problem could be. I get confused with variable scopes and whatnot.
Here's the Tween Object (Yup, ripped off form Robert Penner)
// Tween a number, add a suffix and insert it into an element
function Tween(b, c, d, element, suffix, decimal){
this.t = 0;
this.c = c;
this.d = d;
this.b = b;
this.element = element;
this.suffix = suffix;
this.step = function(){
if(this.t != this.d){
this.t += 1
var flip = 1
if (this.c < 0) {
flip *= -1
this.c *= -1
}
i = flip * (-Math.exp(-Math.log(this.c)/this.d * (this.t-this.d)) + this.c + 1) + this.b
if(!decimal){
this.element.html(Math.round(i) + this.suffix)
}else{
output = (Math.round(i * 10) / 10 + this.suffix)
formattedOutput = ( output - Math.round(output) == 0 ) ? output + ".0" : output;
this.element.html(formattedOutput)
}
}
}
}
And here's the implementation
tweenManager = new TweenManager();
tweenManager.addTween(0,80,300, $("#el1"), "°", false)
tweenManager.addTween(0,60,400, $("#el2"), "’", false)
tweenManager.addTween(0,12.5,300, $("#el3"), "", true)
tweenManager.start()
As always, any help, hinting or nudging the the right direction is greatly appreciated.
I think the problem is that you are trying to use setInterval as some sort of fork() function which means that you should moving it from where it is to put it on the step itself so that you call:
setInterval(thisTween.step, 1, ...
That is how you can make your tweens run in fake 'parallel'.
However what I really think you want is the new HTML5 Web Workers feature; I think that is for exactly this kind of activity.

Categories