<section class="section">
<h1>So you think you can create an ID?</h1>
<div class="form-progress">
<progress class="form-progress-bar" min="0" max="100" value="0" step="33" aria-labelledby="form-progress-completion"></progress>
<div class="form-progress-indicator one active"></div>
<div class="form-progress-indicator two"></div>
<div class="form-progress-indicator three"></div>
<div class="form-progress-indicator four"></div>
<p id="form-progress-completion" class="js-form-progress-completion sr-only" aria-live="polite">0% complete</p>
</div>
<div class="animation-container">
<!-- Step one -->
<div class="form-step js-form-step" data-step="1">
<p class="form-instructions"><strong>Click the continue button to show form progression.</strong><br>
Please fill out the fields below so we can learn some information about you. We promise to store these on Post-It<sup><small>TM</small></sup> notes around the office.</p>
<form action="" name="form-step-1">
<div class="fieldgroup">
<input type="text" name="firstName" id="firstName" />
<label for="firstName">First name</label>
</div>
<div class="fieldgroup">
<input type="text" name="lastName" id="lastName" />
<label for="lastName">Last name</label>
</div>
<div class="fieldgroup">
<input type="text" name="email" id="email" />
<label for="email">Email</label>
</div>
<div class="fieldgroup">
<input type="text" name="postalCode" id="postalCode" />
<label for="postalCode">Postal code</label>
</div>
<div class="buttons">
<button type="button" class="btn btn-alt js-reset">Reset</button>
<button type="submit" class="btn">Continue</button>
</div>
</form>
</div>
<!-- Step two -->
<div class="form-step js-form-step waiting hidden" data-step="2">
<p class="form-instructions"><strong>Click the continue button to show form progression.</strong>
<br> Please fill in the token below with yesterday's date plus the timestamp from the day of your birth (expressed as an integer) to the power of fourteen divided by two.</p>
<form action="" name="form-step-2">
<div class="fieldgroup">
<input type="text" name="token" id="token" />
<label for="token">Token</label>
</div>
<div class="buttons">
<button type="button" class="btn btn-alt js-reset">Reset</button>
<button type="submit" class="btn">Continue</button>
</div>
</form>
</div>
<!-- Step three -->
<div class="form-step js-form-step waiting hidden" data-step="3">
<p class="form-instructions"><strong>Click the continue button to show form progression.</strong>
<br> Please tell us your credit card information. We promise not to store it or use it to buy a new iPhone in the App Store.</p>
<form action="" name="form-step-3">
<div class="fieldgroup">
<input type="text" name="credit-card" id="credit-card" />
<label for="credit-card">Credit card number</label>
</div>
<div class="fieldgroup">
<input type="text" name="credit-card-expiry" id="credit-card-expiry" />
<label for="credit-card-expiry">Credit card expiry</label>
</div>
<div class="buttons">
<button type="button" class="btn btn-alt js-reset">Reset</button>
<button type="submit" class="btn">Continue</button>
</div>
</form>
</div>
<!-- Step four -->
<div class="form-step js-form-step waiting hidden" data-step="4">
<p class="form-instructions"><strong>Click the continue button to show form progression.</strong>
<br> Finally, please just write the words "I Agree" in the field below to consent to us using the previous data you entered to buy a new iPhone.</p>
<form action="" name="form-step-4">
<div class="fieldgroup">
<input type="text" name="agreen" id="agreen" />
<label for="agreen">I agree</label>
</div>
<div class="buttons">
<button type="button" class="btn btn-alt js-reset">Reset</button>
</div>
</form>
</div>
</div>
</section>
$green: rgb(11,204,108);
$page-animDuration: .2s;
// =============================
// Universal styles
// =============================
body {
background: rgba(0,0,0,.1);
line-height: 1.45rem;
color: #444;
&.freeze { pointer-events: none; }
}
h1 {
margin: 0;
margin-bottom: 2rem;
text-align: center;
font-weight: normal;
line-height: 2.2rem;
}
// =============================
// Form styles
// =============================
.section {
max-width: 500px;
padding: 4rem;
margin: 5vh auto 0 auto;
background: white;
box-shadow: 0 1px 2px rgba(0,0,0,.3);
&:before {
content: "";
width: 100%;
background: lighten($green, 5%);
height: 170px;
position: absolute;
top: 0;
left: 0;
z-index: -1;
border-bottom: 1px solid rgba(0,0,0,.2);
}
}
.form-instructions {
text-align: center;
}
form {
margin: 2rem auto;
width: 100%;
max-width: 330px;
will-change: transform;
}
// =============================
// Input styles
// =============================
.fieldgroup {
margin: 1.5rem 0;
position: relative;
}
label {
position: absolute;
top: .8rem;
left: 0;
display: block;
font-size: 1rem;
transition: $page-animDuration ease-out;
opacity: .5;
will-change: top, font-size;
&:hover {
cursor: text;
}
}
input {
border: 1px solid #fff;
font-size: 1.2rem;
padding: .6rem;
padding-left: 0;
background:transparent;
border: none;
border-bottom: 2px solid #444;
transition: $page-animDuration;
width: calc(100% - .6rem);
max-width: 350px;
border-radius: 0;
&:focus {
outline: none;
}
&:valid {
border-color: #444;
}
&:focus + label,
&.hasInput + label {
top: -.8rem;
font-size: .7rem;
}
}
// =============================
// Buttons
// =============================
.btn {
color: #fff;
background-color: $green;
padding: .8rem;
font-size: 1.2rem;
line-height: 1.2rem;
border-radius: 5px;
border: 2px solid transparent;
min-width: 45px !important;
&:hover,
&.hover {
color: #fff;
text-shadow: 0 1px 3px rgba(0,0,0,.3);
transition: .2s;
}
&:active,
&.active {
color: #fff;
background-color: darken($green, 20%);
box-shadow: inset 0 2px 10px rgba(0,0,0,.3);
outline: 2px solid $green;
}
&:focus,
&.focus {
color: #fff;
outline: 2px solid $green;
outline-offset: 2px;
}
&:active:focus,
&.active.focus {
outline: 4px solid $green;
}
//Reset outlines on button with frozen states
&.hover,
&.active { outline: none; }
}
//Alternate action
.btn-alt {
background-color: transparent;
color: $green;
border: 2px solid $green;
&:hover,
&.hover {
background-color: transparent;
color: darken($green, 40%);
border-color: darken($green, 40%);
text-shadow: none;
}
&:focus,
&.focus {
color: darken($green, 20%);
}
&:active,
&.active {
color: #fff;
background-color: $green;
text-shadow: 0 -1px 0 rgba(255,255,255,.2);
}
//Reset outlines on button with frozen states
&.hover,
&.active { outline: none; }
}
.buttons {
display: flex;
.btn { margin-right: 15px; }
}
form .btn {
display: inline-block;
width: 100%;
max-width: 220px;
margin: 4rem auto 0 auto;
}
[data-step="4"] button.btn {
display: block;
margin: 0 auto;
}
// =============================
// Progress bar
// =============================
.form-progress {
position: relative;
display: block;
margin: 3rem auto;
width: 100%;
max-width: 400px;
}
progress {
display: block;
position: relative;
top: 5px;
left: 5px;
appearance: none;
appearance: none;
background: $green;
width: 100%;
height: 5px;
background: none;
transition: 1s;
will-change: contents;
&::progress-bar {
background-color: #ddd;
}
&::progress-value {
background-color: $green;
transition: all 0.5s ease-in-out;
}
}
.form-progress-indicator {
position: absolute;
top: -6px;
left: 0;
display: inline-block;
width: 20px;
height: 20px;
background: white;
border: 3px solid #ddd;
border-radius: 50%;
transition: all .2s ease-in-out;
transition-delay: .3s;
will-change: transform;
&.one { left: 0; }
&.two { left: 33%; }
&.three { left: 66%; }
&.four { left: 100%; }
&.active {
animation: bounce .5s forwards;
animation-delay: .5s;
border-color: $green;
}
}
// =============================
// Form animations
// =============================
.animation-container {
position: relative;
width: 100%;
transition: .3s;
will-change: padding;
overflow: hidden;
}
.form-step {
position: absolute;
transition: 1s ease-in-out;
transition-timing-function: ease-in-out;
will-change: transform, opacity;
}
.form-step.leaving {
animation: left-and-out .5s forwards;
}
.form-step.waiting {
transform: translateX(400px);
}
.form-step.coming {
animation: right-and-in .5s forwards;
}
// =============================
// Animation definitions
// =============================
@keyframes left-and-out {
100% {
opacity: 0;
transform: translateX(-400px);
}
}
@keyframes right-and-in {
100% {
opacity: 1;
transform: translateX(0);
}
}
@keyframes bounce {
50% {
transform: scale(1.5);
}
100% {
transform: scale(1);
}
}
// =============================
// Helpers
// =============================
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0,0,0,0);
border: 0;
}
.hidden {
display: none;
}
View Compiled
var $body = $('body');
var $progressBar = $('progress');
var $animContainer = $('.animation-container');
var value = 0;
var transitionEnd = 'webkitTransitionEnd transitionend';
/**
* Resets the form back to the default state.
* ==========================================
*/
function formReset() {
value = 0;
$progressBar.val(value);
$('form input').not('button').val('').removeClass('hasInput');
$('.js-form-step').removeClass('left leaving');
$('.js-form-step').not('.js-form-step[data-step="1"]').addClass('hidden waiting');
$('.js-form-step[data-step="1"]').removeClass('hidden');
$('.form-progress-indicator').not('.one').removeClass('active');
$animContainer.css({
'paddingBottom': $('.js-form-step[data-step="1"]').height() + 'px'
});
console.warn('Form reset.');
return false;
}
/**
* Sets up the click handlers on the form. Next/reset.
* ===================================================
*/
function setupClickHandlers() {
// Show next form on continue click
$('button[type="submit"]').on('click', function(event) {
event.preventDefault();
var $currentForm = $(this).parents('.js-form-step');
showNextForm($currentForm);
});
// Reset form on reset button click
$('.js-reset').on('click', function() {
formReset();
});
return false;
}
/**
* Shows the next form.
* @param - Node - The current form.
* ======================================
*/
function showNextForm($currentForm) {
var currentFormStep = parseInt($currentForm.attr('data-step')) || false;
var $nextForm = $('.js-form-step[data-step="' + (currentFormStep + 1) + '"]');
console.log('Current step is ' + currentFormStep);
console.log('The next form is # ' + $nextForm.attr('data-step'));
$body.addClass('freeze');
// Ensure top of form is in view
$('html, body').animate({
scrollTop : $progressBar.offset().top
}, 'fast');
// Hide current form fields
$currentForm.addClass('leaving');
setTimeout(function() {
$currentForm.addClass('hidden');
}, 500);
// Animate container to height of form
$animContainer.css({
'paddingBottom' : $nextForm.height() + 'px'
});
// Show next form fields
$nextForm.removeClass('hidden')
.addClass('coming')
.one(transitionEnd, function() {
$nextForm.removeClass('coming waiting');
});
// Increment value (based on 4 steps 0 - 100)
value += 33;
// Reset if we've reached the end
if (value >= 100) {
formReset();
} else {
$('.form-progress')
.find('.form-progress-indicator.active')
.next('.form-progress-indicator')
.addClass('active');
// Set progress bar to the next value
$progressBar.val(value);
}
// Update hidden progress descriptor (for a11y)
$('.js-form-progress-completion').html($progressBar.val() + '% complete');
$body.removeClass('freeze');
return false;
}
/**
* Sets up and handles the float labels on the inputs.
=====================================================
*/
function setupFloatLabels() {
// Check the inputs to see if we should keep the label floating or not
$('form input').not('button').on('blur', function() {
// Different validation for different inputs
switch (this.tagName) {
case 'SELECT':
if (this.value > 0) {
this.className = 'hasInput';
} else {
this.className = '';
}
break;
case 'INPUT':
if (this.value !== '') {
this.className = 'hasInput';
} else {
this.className = '';
}
break;
default:
break;
}
});
return false;
}
/**
* Gets the party started.
* =======================
*/
function init() {
formReset();
setupFloatLabels();
setupClickHandlers();
}
init();
This Pen doesn't use any external CSS resources.