Elementals.js

Latest Version: 2.0 RC 3 14 March 2017

eProgress.js

View the Demo live here

eProgress is a elementals.js implementation of the nProgress library. The original is based on jQuery and uses it for the animations, while this version leverages CSS3 to do the 'grunt work' resulting in a smaller codebase despite the addition of more functions to it.

The demo uses the elementals library to build all the user callable properties of eProgress. I kept that scripting separate from the eProgress script so as to make it easier for you to use eProgress in your own projects.

Usage

To include the eProgress library, you simply load the eProgress.js file right after you load elementals.js before the </body> tag thus:

<script src="../scripts/elementals.js"></script>
<script src="eProgress.js"></script>

The script will automatically add the LINK for the default eProgress.screen.css inside the document HEAD.

The core of using eProgress is the eProgress object, which is initilized when the library loads so it's ready to use at any time.

The eProgress object has the following user callable methods.

eProgress.addPercent(amount);
Adds a percentage to the progress bar. For example eProgress.addPercent(50); will increase the progress bar 50%.
eProgress.addFractional(amount);
Adds a fractional floating point value to the progress bar. For example eProgress.addFractional(0.5); is the same as eProgress.addPercent(50);
eProgress.auto(amount [, interval]);
Increases the percentage bar at a regular interval by percent. The interval option is optional, and if omitted 290ms is used. This is slightly less than the CSS animation delay the default CSS uses, resulting in a smoother animation.
eProgress.done();
Sets the progress bar to 100%, the lets the CSS fade it out. Once the fade is complete the progress bar's outer wrapper is hidden.
eProgress.setPercent(at);
Sets the progress bar to at
*NOTE* there is the internal noshow parameter that we suggest you not use.
eProgress.setFractional(at);
Sets the progress bar to a floating point at. For example eProgress.setFractional(0.5); is the same as eProgress.setPercent(50);
eProgress.start( [ at ] );
Sets the progress bar to at if present, otherwise it will set it to 1% -- it then starts all the animations.
*NOTE* If you call any other function without calling .start() first, .start() will be run automatically!
eProgress.stopAuto();
Stops the auto-update interval.

Should any of the above operations result in a value of 100% or more, the progress bar will 'end' and be hidden.

Code explanation

The object itself starts out declaring a few properties like the current percentage value, the minimum percentage for the start method, the timer handler, how much to increment by when the auto-update is enabled, and the wrapper element which we use _.make to create. Again, we want to create our scripting only elements in the scripting, so we aren't wasting uncached markup that might not even be run/used on the page.

var eProgress = {

	value : 0,
	minPercent : 1,
	timerHandler : false,
	timerInc : 0,
	wrapper : _.make('.eProgress.eProgressHide', { last : _d.body }),

Next is the start function -- which laughably actually calls .setPercent selecting either 'at' or the minimum default percentage.

	start : function(at) {
		this.setPercent(at ? at : this.minPercent);
	},

The setPercent function is actually the one that does all the real work. Every other function for setting values calls it. First it checks if the optional noShow parameter doesn't exist, or if it does exist that it's false -- if so, it switches the progress bar from being hidden to being shown.

It then sets the value to at, and then performs a range check. If the result would be less than zero, it sets it to zero. If it's more than zero, we call done(). Otherwise we set the width on the progress bar to our current value percentage.

	setPercent : function(at, noShow) {
		if (!noShow) _.classSwap(this.wrapper, 'eProgressHide', 'eProgressShow');
		this.value = at;
		if (this.value < 0) this.value = 0;
		if (this.value >= 100) this.done();
			else this.bar.style.width = this.value + '%';
	},

Then the various smaller functions to let people pass fractional percentages and/or floating point values. There's nothing too fancy about these.

	setFractional : function(at) {
		this.setPercent(at * 100);
	},
	
	addPercent : function(amount) {
		this.setPercent(this.value + amount);
	},
	
	addFractional : function(amount) {
		this.setPercent(this.value + amount * 100);
	},

The auto functions aren't too complex either. To start the auto-update we add the class to say "yes it's on automatic" to our CSS, and set up our eProgress.timerInc value from the amount specified as amount. It then checks to see if the timerHandler is already pointing at a interval handler. If it isn't, we call setInterval to create one. The interval function itself is really simple, we just addPercent our stored timerInc.

	auto : function(amount, interval) {
		_.classAdd(this.wrapper, 'eProgressAuto');
		this.timerInc = amount;
		if (!this.timerHandler) this.timerHandler = setInterval(function() {
			eProgress.addPercent(eProgress.timerInc);
		}, interval ? interval : 290);
	},

Stopping it is simple too... we rip the class off the outer container, then if the timer exists we clear it... finally setting the timerHandler to false... (since clearInterval can't actually do that).

	autoStop : function() {
		_.classRemove(this.wrapper, 'eProgressAuto');
		if (this.timerHandler) {
			clearInterval(this.timerHandler);
			this.timerHandler = false;
		}
	},

The done method is the most fragile part of this. We set our value to 100%, the CSS style to same, we make sure the auto interval is killed, and then we set a 333ms timeout that will perform our class swap to start the fade-out animation, which in turn sets a timeout to reset the progress bar to zero while it's hidden, using that internal noShow parameter to make sure our setPercent method doesn't try to make the progress bar visible again. This part might seem unneccessarily complex, but it seems the best way to make sure the ending fade-out animation runs smoothly, particularly if you're going to call the progress bar more than once.

	done : function() {
		this.value = 100;
		this.bar.style.width = '100%';
		this.autoStop();
		setTimeout(function() {
			_.classSwap(eProgress.wrapper, 'eProgressShow', 'eProgressHide');
			setTimeout(function() { eProgress.setPercent(0, true); }, 500);
		}, 333);
	}
	
};

Finally we have the code to actually create the progress bar in the markup, create the 'spinner' object that will be animated by CSS3, and load the CSS. We have to make the spinner as a markup element because only Chrome seems to be able to frame-animate a generated content element; otherwise I wouldn't even have that in the scripting.

// you can't reference itself when using instancing
eProgress.bar = _.make('.eProgressBar', { last : eProgress.wrapper});

// generated content doesn't frame-animate properly :(
_.make('.eProgressSpinner', { last : eProgress.wrapper});

_.cssLoad('eProgress.screen.css', 'screen,projection,tv');

Nothing too complex so far as scripting goes. Like most of my scripts mostly all it needs from elementals is the ability to create elements with _.make and the class manipulation methods.

CSS explanation

The style for this is really simple. First we have the outer container which is set to position:fixed. For some reason in some browsers the 100% width on a fixed element can 'grow' bigger than the display area when scrollbars are enabled on the page. Setting overflow:hidden seems to fix this, no clue why. Other than that we depth-sort it over things with z-index and set it to the top-left corner, and set up our fade-out transitions.

.eProgress {
	overflow:hidden; /* prevent scrollbars */
	position:fixed;
	top:0;
	left:0;
	width:100%;
	z-index:9999;
	-webkit-transition:opacity 0.5s;
	-moz-transition:opacity 0.5s;
	-ms-transition:opacity 0.5s;
	transition:opacity 0.5s;
}

We can then set the display and hidden opacities.

.eProgressHide {
	opacity:0;
}

.eProgressShow {
	opacity:1;
}

The .eProgressBar just gets relative positioning so we can absolute position a :after element inside it to create the shadowed/offset 'widened' point element on it. We use the font-size:1px; so that Legacy IE will actually let us set the element to 4px tall, and set our nice blue background color.

.eProgressBar {
	position:relative;
	width:0;
	height:4px;
	font-size:1px; /* fix IE height bug */
	background:#29D;
	border:0;
	-webkit-transition:width ease 0.3s;
	-moz-transition:width ease 0.3s;
	-ms-transition:width ease 0.3s;
	transition:width ease 0.3s;
	-webkit-border-radius:0 0 1px 0;
	-moz-border-radius:0 0 1px 0;
	border-radius:0 0 2px 0;
}

I then switch the .progressBar transition from ease to linear when the auto-increment timer is active. While the ease transition looks best when you set major changes, it looks 'jerky' compared to linear when called at an exact timeout.

.eProgressAuto .eProgressBar {
	-webkit-transition:width linear 0.3s;
	-moz-transition:width linear 0.3s;
	-ms-transition:width linear 0.3s;
	transition:width linear 0.3s;
}

We then use generated content to create the little drop-shadow widening at the tend of the element. Simply rotating this element, sliding it's position around and setting a nice big box-shadow pulls off a very nice effect. Kudo's to the creator of nProgress for the concept!

.eProgressBar:after {
	content:" ";
	position:absolute;
	right:-1px;
	top:-6px;
	width:144px;
	height:6px;
	-webkit-box-shadow:0 2px 8px #29D;
	-moz-box-shadow:0 2px 8px #29D;
	box-shadow:0 2px 8px #29D;
	-webkit-border-radius:0 0 4px 0;
	-moz-border-radius:0 0 4px 0;
	border-radius:0 0 4px 0;
	-webkit-transform:rotate(2deg);
	-moz-transform:rotate(2deg);
	-ms-transform:rotate(2deg);
	transform:rotate(2deg);
}

The spinner gets simply float and margin positioning. Using a a combination of border, border-radius and box-shadow we can create a 'crescent' from the CSS that can then be rotated.

.eProgressSpinner {
	float:right;
	margin:16px 16px 4px 0;
	right:12px;
	top:12px;
	width:22px;
	height:22px;
	background:#FFF;
	border:solid #29D;
	border-width:1px 1px 0 0;
	-webkit-border-radius:12px;
	-moz-border-radius:12px;
	border-radius:12px;
	-webkit-box-shadow:1px -1px 1px #29D;
	-moz-box-shadow:1px -1px 1px #29D;
	box-shadow:1px -1px 1px #29D;
}

We then set up the animations. Pain in the ass thanks to the idiotic halfwit 'browser prefix' crap, but not so massive as to have a significant impact on the page. It's still cleaner to let CSS handle these animations via keyframes than to try and have JavaScript handle it.

.eProgressShow .eProgressSpinner {
	-webkit-animation-name:epRotate;
	-webkit-animation-duration:0.66s;
	-webkit-animation-iteration-count:infinite;
	-webkit-animation-play-state:running;
	-webkit-animation-timing-function:linear;
	-moz-animation-name:epRotate;
	-moz-animation-duration:0.66s;
	-moz-animation-iteration-count:infinite;
	-moz-animation-play-state:running;
	-moz-animation-timing-function:linear;
	animation-name:epRotate;
	animation-duration:0.66s;
	animation-iteration-count:infinite;
	animation-play-state:running;
	animation-timing-function:linear;
}

@-webkit-keyframes epRotate {
	from { -webkit-transform:rotate(0deg); }
	to { -webkit-transform:rotate(360deg); }
}

@-moz-keyframes epRotate {
	from { -moz-transform:rotate(0deg); }
	to { -moz-transform:rotate(360deg); }
}

@keyframes epRotate {
	from { transform:rotate(0deg); }
	to { transform:rotate(360deg); }
}

Demo Code Explanation

I generated the buttons and scripting only content using a script for a reason -- they have no business in the markup if scripting is off, and it's easier to attach onclick handlers to the DIV if you are making them scripting-side anyways. Normally 'click' would be considered a unsafe property compared to attachEvent, however since we JUST CREATED the element it's not like there are going to be any default handlers attached.

The main function here is called addControl which makes an element with _.make that is the outer div with the click handler attached to a SPAN made with _.make as well. It then appends a CODE tag for the example code, into which a VAR tag is made containing the method text. Finally a P tag is appeneded containing the desc text.

	var controls = _d.getElementById('controls');
	function addControl(method, desc, handler) {
		var
			e = _.make('.eControl', {
				content : _.make('button.eControlButton', {
					content : _.make('span')
				}),
				onclick : handler,
				last : controls
			});
		_.make('var', {
			content : method,
			last : _.make('code', { content : 'eProgress.', last : e })
		});
		_.make('p', { content : desc, last : e });
	};

Which the demo can then call multiple times thus:

	addControl(
		'setPercent(40)',
		'Set a percentage',
		function() { eProgress.setPercent(40); }
	);

Pretty simple, no? Naturally when using it for your own routines you'd just call eProgress' methods directly.

Downloads

Advertisement