Load a Javascript progress bar after scrolling down to it - javascript

I have used jQuery 1.12.2 with animate.css which populates a progress bar successfully. Now I.m trying to only make the progress bar load after I scroll down to it as is currently loads straight away. What's the point in having this awesome feature when it has already been loaded when it is somewhere down the bottom of the page?
I have created a JSFiddle here. I know the JavaScript is a little messy and can be written in a more cleaner way but please excuse me as I am new to JS and trying to make this work first. Can anyone help?
$('#myid').goalProgress({
goalAmount: 100,
currentAmount: 75,
textBefore: 'myid ',
textAfter: ''
});
});
goalProgress is a whole different class:
!function($){
$.fn.extend({
goalProgress: function(options) {
var defaults = {
goalAmount: 100,
currentAmount: 50,
speed: 1000,
textBefore: '',
textAfter: '',
milestoneNumber: 70,
milestoneClass: 'almost-full',
callback: function() {}
}
var options = $.extend(defaults, options);
return this.each(function(){
var obj = $(this);
// Collect and sanitize user input
var goalAmountParsed = parseInt(defaults.goalAmount);
var currentAmountParsed = parseInt(defaults.currentAmount);
// Calculate size of the progress bar
var percentage = (currentAmountParsed / goalAmountParsed) * 100;
var milestoneNumberClass = (percentage > defaults.milestoneNumber) ? ' ' + defaults.milestoneClass : ''
// Generate the HTML
var progressBar = '<div class="progressBar">' + defaults.textBefore + defaults.textAfter + '</div>';
var progressBarWrapped = '<div class="goalProgress' + milestoneNumberClass + '">' + progressBar + '</div>';
// Append to the target
obj.append(progressBarWrapped);
// Ready
var rendered = obj.find('div.progressBar');
// Remove Spaces
rendered.each(function() {
$(this).html($(this).text().replace(/\s/g, ' '));
});
// Animate!
rendered.animate({width: percentage +'%'}, defaults.speed, defaults.callback);
if(typeof callback == 'function') {
callback.call(this)
}
});
}
});
}(window.jQuery);

Instead of running this
$('#myid').goalProgress({
goalAmount: 100,
currentAmount: 75,
textBefore: 'myid ',
textAfter: ''
});
});
on .ready event
Try to run it on scroll event.
For example make an algorithm to check if this #mydiv is in your viewport and then trigger this function.
Or use some ready plugin. For example : http://scrollmagic.io/

Related

Using Velocity.js to animate progressbar

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

Javascript .on function runs for all values a string variable has had, instead of only its current value

I'm using Packery JS to create a card lay-out. In this lay-out I move things around using the fit method. When the moving is finished I then scroll to the moved div; I wait for the fit complete event and then use jQuery's scrolltop to scroll to the div in question. This is the code I currently use for the scrolling part:
$container.packery( 'on', 'fitComplete', function () {
$('html,body').animate({
scrollTop: $("#" + moveItem).offset().top - 40
});
console.log("scroll to: " + moveItem + ", position: " +($("#"+moveItem).offset().top - 40))
});
On the first click this works as intended, this is the output of moving div#vision:
scroll to: vision, position: 69
But on the second click (moving div#project-6) it goes wrong:
scroll to: project-6, position: 616
scroll to: vision, position: 69
It first scrolls to project-6, but then immediately goes to the previous value of moveItem, vision. On subsequent clicks the list only gets longer.
So my question: Why is it remembering the previous values of the moveItem variable? And how can I stop it from doing that?
For context, this is the website I'm using it on. The code in question can be found by searching for "//Move function".
Thanks in advance!
This happens when you execute $container.packery( 'on', ...); several times. My guess is that you attach another event handler every time you click the button instead of attaching it once in the setup of the page and then make sure the event is triggered when the button is clicked.
I will be more specific. Change this function to this:
//Move Function
var moveThis = function(moveItem, moveTarget){
if (moveTarget == "stamp" && $("#" + moveItem).hasClass("w2") && 720 < window.innerWidth && window.innerWidth < 992) {
var targetX = 0;
var targetY = $("#stamp").height() + 40;
$container.packery('fit', document.getElementById(moveItem), targetX, targetY);
} else if (moveTarget == "stamp") {
var arrayList = $container.packery('getItemElements')
var targetX = $(arrayList[0]).position().left
var targetY = $(arrayList[0]).position().top
$container.packery('fit', document.getElementById(moveItem), targetX, targetY);
} else {
var arrayList = $container.packery('getItemElements')
positionList = arrayList.indexOf(document.getElementById(moveTarget))
var targetX = $(arrayList[positionList+1]).position().left
var targetY = $(arrayList[positionList+1]).position().top
$container.packery('fit', document.getElementById(moveItem), targetX, targetY);
}
};
this code must be out of function, somewhere globally defined:
$container.packery( 'on', 'fitComplete', function ( moveItem ) {
$('html,body').animate({
scrollTop: $("#" + $(moveItem.element).attr('id')).offset().top - 40
});
console.log("scroll to: " + $(moveItem.element).attr('id') + ", position: " +($("#"+moveItem).offset().top - 40))
});
I'm not sure about $(moveItem.element).attr('id') - for this look at console what it has

Using links within a parallax ScrollMagic site

I'm making a vertical parallax scrolling site with ScrollMagic which includes a navigation menu at the top to link within the site.
The menu itself works correctly when no parallax animation is applied to the scroll but when the parallax is added (ie the 2nd section moves up over the intro section), it seems unable to take the reduction in overall height into account when moving to the section, so it overshoots.
Here is some code:
var site = {
smController : {},
init : function () {
site.setupScroll();
site.setupMainNavigation();
site.setupAnimation();
},
setupScroll : function () {
// init the smController
var controller = new ScrollMagic({
globalSceneOptions: {
triggerHook: "onLeave"
}
});
site.smController = controller;
},
setupMainNavigation : function () {
$('.menuclick').on('click', function (event) {
event.preventDefault();
var anchor = $(this),
sectionId = $(anchor.attr('href'));
site.scrollToSection(sectionId);
});
},
/**
* uses tweenlite and scrolltoplugin from greensock
* #param {string} sectionId id of section to scroll to
* #return {void}
*/
scrollToSection : function (sectionId) {
var scrollYPos = $(sectionId).offset().top;
TweenLite.to(window, 0.5, { scrollTo:{ y: scrollYPos } });
},
setupAnimation : function () {
// parallax animation - move marginTop back by 100%
var tween = new TimelineMax()
.to('#section1', 2, { marginTop: '-100%', ease:Linear.easeNone });
var controller = site.smController,
scene = new ScrollScene({ duration: 500 })
.setTween(tween)
.addTo(controller);
// show indicators (requires debug extension)
scene.addIndicators();
}
};
$(document).ready(function () {
site.init();
});
Does anyone have a strategy to deal with moving (parallax) sections like this please?
Thanks
In ScrollMagic 1.1 you can now provide custom scroll functions AND scroll to the beginning of a specific scene.
Read more here:
http://janpaepke.github.io/ScrollMagic/docs/ScrollMagic.html#scrollTo
I would also strongly suggest not to use animated elements as scroll targets, because their position might be different before and after initiating scroll.
If you have elements that influence the DOM height, try to take them out of the DOM flow.
You can do this for example by adding an element as a placeholder and setting your element as positioned absolutely.
hope this helps.
J
I did something like this, using a similar setup to the demo page
new ScrollMagic.Scene(settings)
.setPin(slides[i])
.on('enter', function () {
var $trigger = $(this.triggerElement()),
$nextSlide = $trigger.parent().next().find('.slide');
/*
* If there's a next slide,
* update the href of the button
* to target the next slide
* otherwise, we're at the end;
* toggle the button state so it targets
* the top of the page
*/
if ($nextSlide.length) {
$('.btn-scroll').attr('href', '#' + $nextSlide.attr('id'));
} else {
$('.btn-scroll').attr('href', '#').addClass('up');
}
})
.on('leave', function (event) {
var $trigger = $(this.triggerElement()),
$firstSlide = $('.slide:first');
/*
* If we're going back up and we pass
* the first slide, update the button
* so it targets the first slide
*/
if (event.scrollDirection === 'REVERSE' && ($trigger.offset().top === $firstSlide.offset().top)) {
$('.btn-scroll').attr('href', originalTarget).removeClass('up');
}
})
.addTo(controller);
It just needs an anchor link with the href set to the first slide.
and something like this to handle the scroll:
var scrollToContent = function (target, speed) {
if (target === '#') {
target = $('body');
} else {
target = $(target);
}
speed = typeof speed !== 'undefined' ? speed : 'slow';
$('html, body').animate({
scrollTop: target.offset().top
}, speed);
}

Fluid sliding panel : make it work on load and after resize with computed values?

I'm trying to put together a fluid sliding panel which should take into account the inner width of the window on load, as well as on resizes : depending on the actual window size, the panel should be moved left / right a fourth of the window width.
So far i managed to bypass the multiple resize events happening when the user resizes the window, thx to this thread.
var waitForFinalEvent = (function () {
var timers = {};
return function (callback, ms, uniqueId) {
if (!uniqueId) {
uniqueId = "Don't call this twice without a uniqueId";
}
if (timers[uniqueId]) {
clearTimeout (timers[uniqueId]);
}
timers[uniqueId] = setTimeout(callback, ms);
};
})();
var slidinNav = function(rtr){
document.getElementById('navPanel').style.left = -rtr + "px";
document.getElementById('navPanel').style.width = rtr + "px";
$('.showMenu').click(function(){
$('#navPanel').animate({left: '+=' + rtr +'px'}, 400);
});
$('.hideMenu').click(function(){
$('#navPanel').animate({left: '-=' + rtr + 'px'}, 400);
});
}
$(document).ready(function(){
var winW = window.innerWidth;
var navPosLeft=winW/4;
slidinNav(navPosLeft);
});
$(window).resize(function () {
waitForFinalEvent(function(){
var winW = window.innerWidth;
var navPosLeft=winW/4;
slidinNav(navPosLeft);
console.log(" Left / Width : " + navPosLeft);
}, 200, "un identifiant unique ?");
});
But being a complete javascript newbie i haven't found the solution to prevent the variables i use to store the window width value and offset to take all the successive values computed.
Better than a long and unclear explanation see jsfiddle here.
Here's my question : Should i reset variables (how and when) or rather try and get the last value (and again : how and when) ?
Thx for any help on this one ; - )
Correct me if I am not understanding exactly what you are looking for here but it looks to me like you may be making it more complicated than it needs to be.
Looks like you could simply just stay with using % and just have these functions:
$('.showMenu').click(function(){
$('#navPanel').animate({left: 0}, 400);
});
$('.hideMenu').click(function(){
$('#navPanel').animate({left: "-20%"}, 400);
});
As demonstrated here: http://jsfiddle.net/X9Jrc/3/

Multiple functions and callbacks in a custom jquery plugin?

Okay I'm not sure if I'm going about this in the right way or not, but here goes...
I'm writing a custom jQuery plugin to provide drop menu functionality with animation (and as a learning exercise so please no "Why not just use superduperwonderplugin-x").
I want to be able to animate the menu in different ways depending on user options (i.e fade, slide, drop etc.). At the moment each different animation is handled by a separate function within the plugin file but I'm not sure how to handle the callback functions (passing this back)!
-- i.e. The animation is only happening on the last element in the object's stack.
Here's my code:
/**
* Grizzly's Menuifier
*
* #author Chris.Leaper
* #version a1.0
*/
(function($){
$.fn.gmenu = function(options) {
/* Transitions:
- fade >> fadeIn / fadeOut
- slide >> slideDown / slideUp
- drop >> (different to above?)
- bounce >> (custom config transition=? and easing=bounce)
- stretch >> (custom config transition=? and easing=elastic)
- fold >> (custom) drop menu # half width then 'fold' out to full menu width
*/
// Set the plugin default options:
var defaults = {
levels: '1',
fit: 'auto',
easing: 'linear',
transition: 'slide',
speed: 500
};
options = $.extend(defaults, options); // Merge the user options with the plugin defaults
return this.each(function() {
var $this = $(this);
var opt = options;
var container;
var ul;
// Setup the container elements (parent DIV/NAV and UL)!
if( $this.is('ul') ) container = $(this).parent();
else container = $(this);
console.log('Container: ' + container.get(0).tagName + ' id=#' + container.attr('id') + ' class=' + container.attr('class'));
ul = container.children('ul:first-child');
console.log('UL: ' + ul);
// Set the UL's position to relative:
if($(ul).css('position') != 'relative') $(ul).css('position', 'relative');
var offset;
var menus = ul.children('li:has(ul)');
console.log('List Item: ' + menus);
menus.each(function(index, menu) {
$menu = $(menu);
console.log('Menu: ' + $menu);
// Set the menu LI's position to relative (contains the absolutely positioned child UL!)
if($menu.css('position') != 'relative') $menu.css('position', 'relative');
// Get the menu LI's position relative to the document (it's offset)
// -- This is only needed when positioning non-child elements
// (i.e. ID linked menu>submenu relationships as may be used for a separated DIV based menu!!)
// offset = menu.offest();
// Position the submenu according to it's parent
var submenu = $menu.children('ul');
console.log('Submenu: ' + submenu.get(0).tagName + ' id=#' + submenu.attr('id') + ' class=' + submenu.attr('class'));
setPosition(submenu, $menu.height());
switch(opt.transition) {
case 'bounce':
setSMBounce(menu, opt);
break;
case 'fade':
setSMFade(menu, opt);
break;
case 'fold':
setSMFold(menu, opt);
break;
case 'stretch':
setSMStretch(menu, opt);
break;
case 'slide':
default:
menu = setSMSlide(menu, opt);
}
});
debug(this);
});
};
})(jQuery);
function setPosition(submenu, height) {
$(submenu).css({
left: 0,
position: 'absolute',
top: height
}).hide();
}
function setSMSlide(menu, opt) {
$menu = $(menu);
console.log('SM Slide: ' + $menu.get(0));
$menu.first('a').mouseenter(function() {
console.log('Start SlideDown');
$menu.stop(true, true).slideDown(opt.speed, opt.easing);
console.log('Stop SlideDown');
});
$menu.first('a').mouseleave(function() {
console.log('Start SlideUp');
$menu.stop(true, true).slideUp(opt.speed, opt.easing);
console.log('Stop SlideUp');
});
}
I think that I should be using a (same) namespace based approach to defining my separate functions (object literal or something?) but I wasn't sure what this meant or how to do it.
Can anyone help me please?
To encapsulate your functions setPosition and setSMSlide (in your example) just define them inside your plugin function (the good place would be after definition of default variable. It would look something like that:
var defaults = {
levels: '1',
fit: 'auto',
easing: 'linear',
transition: 'slide',
speed: 500
},
setPosition = function(submenu, height) {...},
setSMSlide = function(menu, opt) {...};
Because of the way the scoping in Javascript works your setPosition and setSMSlide functions will be still accessible from inside of your gmenu declaration.

Categories