Using Velocity.js to animate progressbar - javascript

I'm trying to switch from using jQuery animation to Velocity as it's supposed to have better performance across devices. From the documentation it seems like it should be fairly easy to do- I've downloaded the source code and added it to my js folder and in my function I should just be able to switch .animate() to .velocity(). Still not working though and I have no console log errors. (Also keep in mind that it worked before with jQuery animate. I've also tried downloading the src code and using the CDNs with no luck)
Here's the code:
// PROGRESSBAR
var show_complete_time = 2000; // time to show completed green progressbar and 100% text
function progressbar(progressbar, time) {
var progressbar = $(progressbar);
$({someValue: 0}).velocity({someValue: 99}, {
duration: time,
easing: 'linear',
step: function() {
var widthNumber = this.someValue;
var number = Math.floor(this.someValue+1);
progressbar.css({"width": widthNumber + "%"});
progressbar.html("<p>" + number + '%' + "</p>");
},
complete: function(){ // progressbar completed
progressbar.css({"width": "100%"});
progressbar.addClass("complete").html("<p>100%</p>");
}
});
};

It turns out the way the jQuery animate function was written wasn't in the correct format for velocity. Velocity takes a map of CSS properties and values as its first argument. An options object can optionally be passed in as a second argument.
Here is the new Velocity function:
// PROGRESSBAR
var show_complete_time = 2000; // time to show completed green progressbar and 100% text
function progressbar(progressbar, time) {
var progressbar = $(progressbar);
progressbar.velocity({"width": "100%"}, {
duration: time,
easing: "linear",
progress: function(elements, c, r, s, t) {
var number = Math.floor((c * 100) + 1);
progressbar.html("<p>" + number + '%' + "</p>");
},
complete: function(){ // progressbar completed
progressbar.addClass("complete").html("<p>100%</p>");
}
});
};

Related

Javascript fade in/out effect freezes when hovering quickly over images

I'm playing around with pure JavaScript, so I created a small fade in/out object, to adjust images opacity onmouseover and onmouseout. Fading works fine when the mouseover and mouseout actions are precise:
Start moving the cursor from the white background
Hover over an image
Hover back over the white background
The problem is, as soon as I start to move the mouse "naturally" from one image to another, the fading (or rather the script itself) freezes.
I'm not sure whether it's a animation-speed problem, or there's something I'm missing in the implementation.
If someone has the time to take a look, I would appreciate a peer check, so I can crack the issue and learn new stuff.
Here's a fiddle: http://jsfiddle.net/6bd3xepe/
Thanks!
As I see it, you have one INTERVAL for you FADER, you need one for each IMG.
My jsfiddle fixes this. I added an ALT-attribute to each IMG with "dome" content, so as to circumvent the jsfiddle working on non-cat-images .. ignore that part - commented out below.
There are some fundamental things wrong with the design - keeping track of objects & references is key. Usage of "this" & "that" aren't helping in the current implementation (see comments to OP). Also, on another note, the usage of "toFixed(2)" is not really required IMHO and you can shorten "o = o + 0.1" to "o += 0.1".
JS:
var fader = {
target: document.getElementsByTagName('img'),
interval: [],
speed: 25,
default_opacity: 1,
init: function() {
this.bindEvents();
},
// Get element's opacity and increase it up to 1
fadeIn: function(element) {
var element_opacity = this.getOpacity(element),
that = this,
idx = element.getAttribute('data-idx');
console.log("fI: "+idx+" "+element_opacity);
this.default_opacity = element_opacity.toFixed(2);
this.interval[idx] = setInterval(function() {
if (element_opacity.toFixed(2) < 1) {
element_opacity = element_opacity + 0.1;
element.style.opacity = element_opacity.toFixed(2);
} else {
clearInterval(that.interval[idx]);
}
}, that.speed);
},
// Get current opacity and decrease it back to the default one
fadeOut: function(element) {
var element_opacity = this.getOpacity(element),
that = this,
idx = element.getAttribute('data-idx');
console.log("fO: "+idx+" "+element_opacity);
this.interval[idx] = setInterval(function() {
if (element_opacity.toFixed(2) > that.default_opacity) {
element_opacity = element_opacity - 0.1;
element.style.opacity = element_opacity.toFixed(2);
} else {
clearInterval(that.interval[idx]);
element.removeAttribute('style');
}
}, that.speed);
},
// Get opacity of an element using computed styles
getOpacity: function(element) {
var styles = window.getComputedStyle(element),
opacity = parseFloat(styles.getPropertyValue('opacity'));
return opacity;
},
bindEvents: function() {
var that = this, count = 0;
for (var i in this.target) {
// the whole "dome" is just a fsfiddle hack - otherwise it sees 7 images instead of 4!
//if( this.target[i].alt == "dome" ){
console.log("COUNT: "+count);
this.target[i].setAttribute('data-idx',count);
this.target[i].onmouseover = function() {
that.fadeIn(this);
}
this.target[i].onmouseout = function() {
that.fadeOut(this);
}
count++;
//}
}
}
};
fader.init();

How to make JQuery wait inside a loop in rotation animation

I have a circle and in it a percentage text, starts with 0%. Once I hover the circle it goes from 0 to 100% (with another effect around the circle). As for now, the percentage goes from 0 to 100 directly and I want it to show the progress (0,1,2...,99,100) but I can't manage to make JQuery wait between each for iteration.
This is what I've tried: JSFiddle demo.
Note: My code works with chrome for now.
That's one iteration:
function actions(i){
var box = $('#box');
box.css('transform','rotate(' + i + ' deg)');
box.css('-ms-transform','rotate(' + i + 'deg)');
box.css('-webkit-transform','rotate(' + i + 'deg)');
prec = (100*(i + 135))/360;
$("div.prec").delay(100).html(Math.round(prec)+"%");
}
I understand that delay() needs to be queued and html() is not queued so I already tried setTimeout, but that it didn't work either. I also tried setInterval - see the next code snippet:
setInterval(function () {
$("div.prec").html(Math.round(prec)+"%");
},100);
To be more clear, I want the percentage to fit the effect progress - if the triangle that goes around travels half of the way, the percentage should be 50, and so, when I'm not hovering the circle anymore it should gradually go back to 0.
First, remove the surplus transition: all 1s css rule! This is making trouble in all the solutions.
1. Educational - using setTimeout
The for loop will not work as you expect, javascript is not build for active-wait loop like this:
for (var i = -135; i < 225; i++){
actions(i);
sleep(some time);
}
You have to use timeout and callbacks. Disable the .delay function call and rewrite your for loop to iterative setTimeout callback as shown here:
function loopit(dir, i){
if (typeof i == "undefined")
i = -135;
if (i >= 225)
return;
actions(i);
setTimeout(function () {
loopit(dir, i + 1);
}, 1);
}
The back-rotation would be written analogically - you can write it yourself as a homework :-)
http://jsfiddle.net/NNq3z/10/
2. Easy - using jQuery .animate()
The easiest way is to use jQuery .animate() function, that will do the animation "loop" with timing for you. To animate the percent text, use progress callback. Animating rotation is tricky though, you need to use special trick:
$({ deg: deg_from } ).animate({
deg: deg_to
}, {
duration: 1000,
progress: function (animation, progress) {
$("div.prec").html(Math.round(progress*100)+"%");
},
step: function(now) {
$('#box').css({
transform: 'rotate(' + now + 'deg)'
});
}
});
http://jsfiddle.net/q9VXC/1/
http://jsfiddle.net/NNq3z/13/
var i = -135,box = $("#box"),prec;
setTimeout(function(){
if($("#circle").is(":hover"))
loopit("c");
else
loopit("nc");
},1);
function loopit(dir){
if (dir=="c")
i++;
else
i--;
if(i<-135)
i=-135;
if(i>225)
i=225;
prec = (100*(i + 135))/360;
$(".prec").html(Math.round(prec)+"%");
box.css("transform","rotate("+i+"deg)")
.css("-ms-transform","rotate("+i+"deg)")
.css("-moz-transform","rotate("+i+"deg)")
.css("-webkit-transform","rotate("+i+"deg)");
setTimeout(function(){
if($("#circle").is(":hover"))
loopit("c");
else
loopit("nc");
},1);
}
Removed transition:all 1s.
In your code, the percentage of the box immediately change to 100% because you use for loop. For loop will be executed very fast as if it immediately change to 100% so it is not recommended.
You should use setInterval, and you have to increment percentage each time:
var percentage = 0;
var timer = setInterval(function() {
percentage++;
if (percentage > 100) {
clearInterval(timer);
} else {
$("div.prec").html(percentage + "%");
}
}
You could also call actions() inside there.

jQuery Animation Triggered By User Interaction Depends on Previous Animation Completion

I have a timeline that can be zoomed by clicking a zoom in or zoom out button. This timeline doesn't all fit on the screen at once, so it is a scrollable div. When the user clicks to zoom, I want the position in the timeline to be the same, so I calculate a new scrollTop for the scrollable div. Here's a simplified version of what I'm doing:
var self = this;
...
this.zoomIn = function() {
var offset = $("#scrollable").scrollTop();
self.increaseZoomLevel(); // Assume this sets the correct zoom level
var newOffset = offset * self.zoomLevel();
$("#scrollable").scrollTop(newOffset);
};
This works fine. Now I'd like to animate the scrolling. This almost works:
var self = this;
...
this.zoomIn = function() {
var offset = $("#scrollable").scrollTop();
self.increaseZoomLevel(); // Assume this sets the correct zoom level
var newOffset = offset * self.zoomLevel();
$("#scrollable").animate({ scrollTop: newOffset });
};
It works if it's clicked once. However, if a second call to zoomIn happens while the animation is still running, the newOffset calculation is wrong because the offset is set to scrollTop() before scrollTop() is correct since the animation is still manipulating it.
I've tried to use jQuery's queue in various ways to make this calculation happen first, and that seems to work sometimes:
var self = this;
...
this.zoomIn = function() {
$("#scrollable").queue(function(next) {
var offset = $("#scrollable").scrollTop();
self.increaseZoomLevel(); // Assume this sets the correct zoom level
var newOffset = offset * self.zoomLevel();
next();
}).animate({ scrollTop: newOffset });
};
I think I'm just not understanding queue properly. How do I keep everything in order even when zoomIn is called repeatedly and rapidly? I want:
zoomIn x 2 clicks
to give me:
calculate 1 -> animate 1 start -> animate 1 finish -> calculate 2 -> animate 2 start -> animate 2 finish
and not
calculate 1 -> animate 1 start -> calculate 2 -> animate 1 finish -> animate 2 start -> animate 2 finish
Because then animate 2 is based on incorrect calculations.
Thanks!
Hm... what about: stop(true,true)? See: http://api.jquery.com/stop/
var self = this;
...
this.zoomIn = function() {
var offset = $("#scrollable").stop(true,true).scrollTop();
self.increaseZoomLevel(); // Assume this sets the correct zoom level
var newOffset = offset * self.zoomLevel();
$("#scrollable").animate({ scrollTop: newOffset });
};
Here's an implementation of #RobinJonsson's comment, which would be my proposed solution too, using a boolean to allow a new zoom action only after the previous animation is complete:
var self = this;
...
this.zooming = false;
this.zoomIn = function() {
if(!self.zooming){
self.zooming = true;
var offset = $("#scrollable").scrollTop();
self.increaseZoomLevel(); // Assume this sets the correct zoom level
var newOffset = offset * self.zoomLevel();
$("#scrollable").animate({ scrollTop: newOffset },function(){
self.zooming = false;
});
}
};
I very much appreciate the answers given. They both would work, but my unwritten requirements included animations that completed entirely as well as no loss of clicks. I know, I should have been more thorough in my question.
Anyway, I believe I have a solution that fits both of those requirements using jQuery queues. There were a couple of things I didn't realize about queues that I learned that got me going in the right direction. The biggest thing is this from the jQuery .animate docs:
When a custom queue name is used the animation does not automatically
start...
This allowed me to have complete control over the queue. I believe this is similar to (or maybe exactly what) #RobinJonsson's comment meant.
var top = 0;
var animating = false;
function calcAndAnimate(top) {
$("#block").queue("other", function() {
// Calculations go here
animating = true;
// This kicks off the next animation
$("#block").dequeue("other");
});
$("#block").animate({
top: top
}, {
duration: 2000,
queue: "other",
complete: function () {
animating = false;
// No need; it looks like animate dequeues for us, which makes sense.
// So the next calculation will be kicked off for us.
//$("#block").dequeue("other");
}
});
}
$("#queueButton").click(function() {
top += 20;
calcAndAnimate(top);
if (!animating) {
// Initial animation, need to kick it off
$("#block").dequeue("other");
}
});
There's a working example with log messages showing the enforced order at http://jsfiddle.net/cygnl7/6h3c2/3/

jQuery scrollTo but slow down inbetween

I'm using a simple chunk of code (based of 'ScrollTo Posts with jQuery' which allows you to click a next/previous link and it'll jump through to the top of each post.
I have my HTML structure so it goes post > image > post > image etc.
I'm wondering if it's possible that if you click the next/previous button, it scrolls to the next post as normal, but it hangs/hovers over the images/div inbetween? So it eventually completes it's scroll, but slows down over the divs inbetween.
Here's my jQuery code:
$(function () {
function a(f) {
var b, e, c = [],
d = $(window).scrollTop(),
g = $('.section-slide');
g.each(function () {
c.push(parseInt($(this).offset()['top'], 10))
});
for (e = 0; e < c.length; e++) {
if (f == 'next' && c[e] > d) {
b = g.get(e);
break
}
if (f == 'prev' && e > 0 && c[e] >= d) {
b = g.get(e - 1);
break
}
}
if (b) {
$.scrollTo(b, {
duration: 1400
})
}
return false
}
$('#next,#prev').click(function () {
return a($(this).attr('id'))
});
$('.scrolltoanchor').click(function () {
$.scrollTo($($(this).attr('href')), {
duration: 1400
});
return false
})
});
Assuming your structure will remain static: post -> image -> post -> image etc. you can accomplish this by finding the previous / next image to the post you will be scrolling to, and scrolling to it first, then use the onAfter callback/setting from the $.scrollTo plugin to fire a secondary scroll after a predefined setTimeout like this:
$(function () {
function scroll(direction) {
var scroll, scrollImage, i,
positions = [],
here = $(window).scrollTop(),
collection = $('.post');
collection.each(function () {
positions.push(parseInt($(this).offset()['top'], 10));
});
for (i = 0; i < positions.length; i++) {
if (direction == 'next' && positions[i] > here) {
scroll = collection.get(i);
// Find Image Before Post
scrollImage = $(scroll).prev('.image').get(0);
break;
}
if (direction == 'prev' && i > 0 && positions[i] >= here) {
scroll = collection.get(i - 1);
// Find Image After Post
scrollImage = $(scroll).next('.image').get(0);
break;
}
}
if (scroll) {
// Check if Scroll Image Exists
if (scrollImage){
// Scroll with Image Delay
$.scrollTo(scrollImage, {
duration: 750,
onAfter: function(){
setTimeout(function(){
$.scrollTo(scroll, {
duration: 750
});
}, 1000); // Change the Delay to Increase / Decrease the Hover
}
});
} else {
$.scrollTo(scroll, {
duration: 750
});
}
}
return false;
}
$("#next,#prev").click(function () {
return scroll($(this).attr('id'));
});
$(".scrolltoanchor").click(function () {
$.scrollTo($($(this).attr("href")), {
duration: 750
});
return false;
});
});
You can find an updated fiddle here: http://jsfiddle.net/hfg2v/2/
I hope this helps.
This is happening because you're using a parallax scrolling library (Stellar.js), which makes different elements scroll at different speeds.
A possible fix would be to scroll at a higher speed when no element is in the current viewport until the edge of the next element is just off the screen, then immediately scroll at the original scrolling speed until there are no elements in the viewport again, and keep repeating this until you reach the desired scroll offset.
Edit:
Sorry, something came up while I was writing my answer and I didn't have time to finish the code.
However, after working on it for some time I'm starting to think that my proposed solution wouldn't work. I was thinking something along those lines:
$(window).scrollTo(640, {onAfter: function () {
var scrollRatio = 3;
var distance = 855 - 640;
$(window).scrollTo(855, {
easing: 'linear',
duration: distance * scrollRatio / speed,
onAfter: function () {
var scrollRatio = 1;
var distance = 1200 - 855;
$(window).scrollTo(1200, {
easing: 'linear',
duration: distance * scrollRatio / speed,
onAfter: function () {
var scrollRatio = 3;
var distance = 1280 - 1200;
$(window).scrollTo(1280, {
easing: 'linear',
duration: distance * scrollRatio / speed
});
}
});
}
});
}});
If you paste the previous code in the website provided in the question (http://dev.du.st/field-station/), you'll be taken to the first element, and it will attempt to scroll you to the next one using the method I described. I hardcoded the offset values because I was still experimenting with it. However, I don't think this approach would work since it still feels off. This is because changing instantly speed in the middle of the animation will always be noticeable.
Right now, I think the best way you can mitigate the slow-scrolling feel that parallax scrolling is causing is by using a different easing function. After all, making the background pictures slower, is exactly what you're using parallax scrolling for.
The following code, when ran in your website, would make all animations use 'easeOutCirc' for their easing function by default, after some experimenting, I found it to be the one that makes the scrolling feel least odd:
// Add the jQuery-easing plugin, needed for the more sophisticated easing functions.
$.getScript('//cdnjs.cloudflare.com/ajax/libs/jquery-easing/1.3/jquery.easing.min.js');
// Set easeOutCirc as the default easing.
jQuery.easing.def = 'easeOutCirc';
You can find more easing functions at this website
Once you're done experimenting, if you do decide on using an easing (you can use different ones for scrolling up and down), then you should probably keep the default easing as is, and just change the easing in the scroll animation by adding {easing: EASING_NAME} to your options hash in the scrollTo function. So your code will look something like this:
$.scrollTo($($(this).attr("href")), {
duration: 750,
easing: 'easeOutCirc'
});

Randomizing effects on jQuery

To keep it small and simple, when clicking a button, all the the divs should slide away in random directions and another set of new divs should be displayed.
Basic demo of what the jQuery code is like:
$(document).ready(function() {
$("#toggle_value").click(function(){
$("#div1").show("fast");
$("#div2").show("fast");
$("#div3").show("fast");
});
});
But its all about randomizing the effects. Any solutions?
I would do something like this:
http://jsfiddle.net/8uKt3/13/
$(document).ready(function() {
var $div = $('div'),
pos = [0, 0, 0, 500, 250, 100, 50, -500, -750, -1000, -1500], // Define your numbers
mypos1,
mypos2,
$me;
$("#toggle_value").click(function(){
$div.each(function(){
$me = $(this);
// Choose a value from each array randomly
mypos1 = pos[Math.floor(Math.random() * pos.length)];
mypos2 = pos[Math.floor(Math.random() * pos.length)];
// animate in a random direction in a random quantitya
$me.animate({
'top' : mypos1,
'left': mypos2
});
});
});
});
You can do something like this, lets say you have 3 possible cases for how the divs might slide away:
var rand = Math.random();
if (rand < .33) {
// Some animation
} else if (rand < .66) {
// Some other possible animation
} else {
// Final animation possibility
}
Algorithm:
create an array of 'n' ints
shuffle the array
in the click function:
for each value 'v' in the shuffled array:
$('#div' + v).show('fast')
My guess is that you're going to have introduce a delay in the animation using the completion all back on 'effect' for this to look good, but that should be trivial to do

Categories