Multi Step Bootstrap Form with animations (responsive)
A Pen by Natalia Davydova on CodePen.
//PEN HEADER | |
header.header | |
h1.header__title Multi Step Form with animations | |
.header__btns.btns | |
a.header__btn.btn(href="https://github.com/nat-davydova/multisteps-form" title="Check on Github" target="_blank") Check on Github | |
//PEN CONTENT | |
.content | |
//content inner | |
.content__inner | |
.container | |
//content title | |
h2.content__title.content__title--m-sm Pick animation type | |
//animations form | |
form.pick-animation.my-4 | |
.form-row | |
.col-5.m-auto | |
select.pick-animation__select.form-control | |
option(value="scaleIn" selected) ScaleIn | |
option(value="scaleOut") ScaleOut | |
option(value="slideHorz") SlideHorz | |
option(value="slideVert") SlideVert | |
option(value="fadeIn") FadeIn | |
//content title | |
h2.content__title Click on steps or 'Prev' and 'Next' buttons | |
.container.overflow-hidden | |
//multisteps-form | |
.multisteps-form | |
//progress bar | |
.row | |
.col-12.col-lg-8.ml-auto.mr-auto.mb-4 | |
.multisteps-form__progress | |
button.multisteps-form__progress-btn.js-active(type="button" title="User Info") User Info | |
button.multisteps-form__progress-btn(type="button" title="Address") Address | |
button.multisteps-form__progress-btn(type="button" title="Order Info") Order Info | |
button.multisteps-form__progress-btn(type="button" title="Comments") Comments | |
//form panels | |
.row | |
.col-12.col-lg-8.m-auto | |
form.multisteps-form__form | |
//single form panel | |
.multisteps-form__panel.shadow.p-4.rounded.bg-white.js-active(data-animation="scaleIn") | |
h3.multisteps-form__title Your User Info | |
.multisteps-form__content | |
.form-row.mt-4 | |
.col-12.col-sm-6 | |
input.multisteps-form__input.form-control(type="text" placeholder="First Name") | |
.col-12.col-sm-6.mt-4.mt-sm-0 | |
input.multisteps-form__input.form-control(type="text" placeholder="Last Name") | |
.form-row.mt-4 | |
.col-12.col-sm-6 | |
input.multisteps-form__input.form-control(type="text" placeholder="Login") | |
.col-12.col-sm-6.mt-4.mt-sm-0 | |
input.multisteps-form__input.form-control(type="email" placeholder="Email") | |
.form-row.mt-4 | |
.col-12.col-sm-6 | |
input.multisteps-form__input.form-control(type="password" placeholder="Password") | |
.col-12.col-sm-6.mt-4.mt-sm-0 | |
input.multisteps-form__input.form-control(type="password" placeholder="Repeat Password") | |
.button-row.d-flex.mt-4 | |
button.btn.btn-primary.ml-auto.js-btn-next(type="button" title="Next") Next | |
//single form panel | |
.multisteps-form__panel.shadow.p-4.rounded.bg-white(data-animation="scaleIn") | |
h3.multisteps-form__title Your Address | |
.multisteps-form__content | |
.form-row.mt-4 | |
.col | |
input.multisteps-form__input.form-control(type="text" placeholder="Address 1") | |
.form-row.mt-4 | |
.col | |
input.multisteps-form__input.form-control(type="text" placeholder="Address 2") | |
.form-row.mt-4 | |
.col-12.col-sm-6 | |
input.multisteps-form__input.form-control(type="text" placeholder="City") | |
.col-6.col-sm-3.mt-4.mt-sm-0 | |
select.multisteps-form__select.form-control | |
option(selected) State... | |
option ... | |
.col-6.col-sm-3.mt-4.mt-sm-0 | |
input.multisteps-form__input.form-control(type="text" placeholder="Zip") | |
.button-row.d-flex.mt-4 | |
button.btn.btn-primary.js-btn-prev(type="button" title="Prev") Prev | |
button.btn.btn-primary.ml-auto.js-btn-next(type="button" title="Next") Next | |
//single form panel | |
.multisteps-form__panel.shadow.p-4.rounded.bg-white(data-animation="scaleIn") | |
h3.multisteps-form__title Your Order Info | |
.multisteps-form__content | |
.row | |
.col-12.col-md-6.mt-4 | |
.card.shadow-sm | |
.card-body | |
h5.card-title Item Title | |
p.card-text Small and nice item description | |
a.btn.btn-primary(href="#" title="Item Link") Item Link | |
.col-12.col-md-6.mt-4 | |
.card.shadow-sm | |
.card-body | |
h5.card-title Item Title | |
p.card-text Small and nice item description | |
a.btn.btn-primary(href="#" title="Item Link") Item Link | |
.row | |
.button-row.d-flex.mt-4.col-12 | |
button.btn.btn-primary.js-btn-prev(type="button" title="Prev") Prev | |
button.btn.btn-primary.ml-auto.js-btn-next(type="button" title="Next") Next | |
//single form panel | |
.multisteps-form__panel.shadow.p-4.rounded.bg-white(data-animation="scaleIn") | |
h3.multisteps-form__title Additional Comments | |
.multisteps-form__content | |
.form-row.mt-4 | |
textarea.multisteps-form__textarea.form-control(placeholder="Additional Comments and Requirements") | |
.button-row.d-flex.mt-4 | |
button.btn.btn-primary.js-btn-prev(type="button" title="Prev") Prev | |
button.btn.btn-success.ml-auto(type="button" title="Send") Send |
Multi Step Bootstrap Form with animations (responsive)
A Pen by Natalia Davydova on CodePen.
//DOM elements | |
const DOMstrings = { | |
stepsBtnClass: 'multisteps-form__progress-btn', | |
stepsBtns: document.querySelectorAll(`.multisteps-form__progress-btn`), | |
stepsBar: document.querySelector('.multisteps-form__progress'), | |
stepsForm: document.querySelector('.multisteps-form__form'), | |
stepsFormTextareas: document.querySelectorAll('.multisteps-form__textarea'), | |
stepFormPanelClass: 'multisteps-form__panel', | |
stepFormPanels: document.querySelectorAll('.multisteps-form__panel'), | |
stepPrevBtnClass: 'js-btn-prev', | |
stepNextBtnClass: 'js-btn-next' | |
}; | |
//remove class from a set of items | |
const removeClasses = (elemSet, className) => { | |
elemSet.forEach(elem => { | |
elem.classList.remove(className); | |
}); | |
}; | |
//return exect parent node of the element | |
const findParent = (elem, parentClass) => { | |
let currentNode = elem; | |
while(! (currentNode.classList.contains(parentClass))) { | |
currentNode = currentNode.parentNode; | |
} | |
return currentNode; | |
}; | |
//get active button step number | |
const getActiveStep = elem => { | |
return Array.from(DOMstrings.stepsBtns).indexOf(elem); | |
}; | |
//set all steps before clicked (and clicked too) to active | |
const setActiveStep = (activeStepNum) => { | |
//remove active state from all the state | |
removeClasses(DOMstrings.stepsBtns, 'js-active'); | |
//set picked items to active | |
DOMstrings.stepsBtns.forEach((elem, index) => { | |
if(index <= (activeStepNum) ) { | |
elem.classList.add('js-active'); | |
} | |
}); | |
}; | |
//get active panel | |
const getActivePanel = () => { | |
let activePanel; | |
DOMstrings.stepFormPanels.forEach(elem => { | |
if(elem.classList.contains('js-active')) { | |
activePanel = elem; | |
} | |
}); | |
return activePanel; | |
}; | |
//open active panel (and close unactive panels) | |
const setActivePanel = activePanelNum => { | |
//remove active class from all the panels | |
removeClasses(DOMstrings.stepFormPanels, 'js-active'); | |
//show active panel | |
DOMstrings.stepFormPanels.forEach((elem, index) => { | |
if(index === (activePanelNum)) { | |
elem.classList.add('js-active'); | |
setFormHeight(elem); | |
} | |
}) | |
}; | |
//set form height equal to current panel height | |
const formHeight = (activePanel) => { | |
const activePanelHeight = activePanel.offsetHeight; | |
DOMstrings.stepsForm.style.height = `${activePanelHeight}px`; | |
}; | |
const setFormHeight = () => { | |
const activePanel = getActivePanel(); | |
formHeight(activePanel); | |
} | |
//STEPS BAR CLICK FUNCTION | |
DOMstrings.stepsBar.addEventListener('click', e => { | |
//check if click target is a step button | |
const eventTarget = e.target; | |
if(!eventTarget.classList.contains(`${DOMstrings.stepsBtnClass}`)) { | |
return; | |
} | |
//get active button step number | |
const activeStep = getActiveStep(eventTarget); | |
//set all steps before clicked (and clicked too) to active | |
setActiveStep(activeStep); | |
//open active panel | |
setActivePanel(activeStep); | |
}); | |
//PREV/NEXT BTNS CLICK | |
DOMstrings.stepsForm.addEventListener('click', e => { | |
const eventTarget = e.target; | |
//check if we clicked on `PREV` or NEXT` buttons | |
if(! ( (eventTarget.classList.contains(`${DOMstrings.stepPrevBtnClass}`)) || (eventTarget.classList.contains(`${DOMstrings.stepNextBtnClass}`)) ) ) | |
{ | |
return; | |
} | |
//find active panel | |
const activePanel = findParent(eventTarget, `${DOMstrings.stepFormPanelClass}`); | |
let activePanelNum = Array.from(DOMstrings.stepFormPanels).indexOf(activePanel); | |
//set active step and active panel onclick | |
if(eventTarget.classList.contains(`${DOMstrings.stepPrevBtnClass}`)) { | |
activePanelNum--; | |
} else { | |
activePanelNum++; | |
} | |
setActiveStep(activePanelNum); | |
setActivePanel(activePanelNum); | |
}); | |
//SETTING PROPER FORM HEIGHT ONLOAD | |
window.addEventListener('load', setFormHeight, false); | |
//SETTING PROPER FORM HEIGHT ONRESIZE | |
window.addEventListener('resize', setFormHeight, false); | |
//changing animation via animation select !!!YOU DON'T NEED THIS CODE (if you want to change animation type, just change form panels data-attr) | |
const setAnimationType = (newType) => { | |
DOMstrings.stepFormPanels.forEach(elem => { | |
elem.dataset.animation = newType; | |
}) | |
}; | |
//selector onchange - changing animation | |
const animationSelect = document.querySelector('.pick-animation__select'); | |
animationSelect.addEventListener('change', () => { | |
const newAnimationType = animationSelect.value; | |
setAnimationType(newAnimationType); | |
}); |
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.min.css"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script> |
//mixins | |
@mixin transition-mix ($property: all, $duration: 0.2s, $timing: linear, $delay: 0s) { | |
transition-property: $property; | |
transition-duration: $duration; | |
transition-timing-function: $timing; | |
transition-delay: $delay; | |
} | |
@mixin position-absolute ($top: null, $left: null, $right: null, $bottom: null) { | |
position: absolute; | |
top: $top; | |
left: $left; | |
right: $right; | |
bottom: $bottom; | |
} | |
//basic variables | |
$theme-font-color: #2c2c2c; | |
//common styles | |
* { | |
margin: 0; | |
padding: 0; | |
box-sizing: border-box; | |
} | |
body { | |
font: { | |
family: 'Poppins', sans-serif; | |
size: 16px; | |
} | |
color: $theme-font-color; | |
a { | |
color: inherit; | |
text-decoration: none; | |
} | |
} | |
.header__btn { | |
@include transition-mix; | |
padding: 10px 20px; | |
display: inline-block; | |
margin-right: 10px; | |
background-color: #fff; | |
border: 1px solid $theme-font-color; | |
border-radius: 3px; | |
cursor: pointer; | |
outline: none; | |
&:last-child { | |
margin-right: 0; | |
} | |
&:hover, | |
&.js-active{ | |
color: #fff; | |
background-color: $theme-font-color; | |
} | |
} | |
//header styles | |
.header { | |
max-width: 600px; | |
margin: 50px auto; | |
text-align: center; | |
} | |
.header__title { | |
margin-bottom: 30px; | |
font: { | |
size: 2.1rem; | |
} | |
} | |
//content styles | |
.content { | |
width: 95%; | |
margin: 0 auto 50px; | |
} | |
.content__title { | |
margin-bottom: 40px; | |
font: { | |
size: 20px; | |
} | |
text-align: center; | |
} | |
.content__title--m-sm { | |
margin-bottom: 10px; | |
} | |
//multisteps variables | |
$color-secondary: #6c757d; | |
$color-primary: #007bff; | |
$btn-offset-vert: 20px; | |
$btn-circle-decor-dimensions: 13px; | |
//multisteps progress styles | |
.multisteps-form__progress { | |
display: grid; | |
grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); | |
} | |
.multisteps-form__progress-btn { | |
@include transition-mix($duration: .15s); | |
position: relative; | |
padding-top: $btn-offset-vert; | |
color: rgba($color-secondary, .7); | |
text-indent: -9999px; | |
border: none; | |
background-color: transparent; | |
outline: none !important; | |
cursor: pointer; | |
@media (min-width: 500px) { | |
text-indent: 0; | |
} | |
//circle decoration | |
&:before { | |
@include position-absolute($top: 0, $left: 50%); | |
display: block; | |
width: $btn-circle-decor-dimensions; | |
height: $btn-circle-decor-dimensions; | |
content: ''; | |
transform: translateX(-50%); | |
transition: all .15s linear 0s, | |
transform .15s cubic-bezier(0.05, 1.09, 0.16, 1.4) 0s; | |
border: 2px solid currentColor; | |
border-radius: 50%; | |
background-color: #fff; | |
box-sizing: border-box; | |
z-index: 3; | |
} | |
//line decoration | |
&:after { | |
@include position-absolute($top: $btn-offset-vert/4, $left: calc(-50% - #{$btn-circle-decor-dimensions} / 2)); | |
@include transition-mix($duration: .15s); | |
display: block; | |
width: 100%; | |
height: 2px; | |
content: ''; | |
background-color: currentColor; | |
z-index: 1; | |
} | |
//last child - without line decoration | |
&:first-child { | |
&:after { | |
display: none; | |
} | |
} | |
//active styles | |
&.js-active { | |
color: $color-primary; | |
&:before { | |
transform: translateX(-50%) scale(1.2); | |
background-color: currentColor; | |
} | |
} | |
} | |
//multisteps form styles | |
.multisteps-form__form { | |
position: relative; | |
} | |
//multisteps panels styles | |
.multisteps-form__panel { | |
@include position-absolute($top: 0, $left: 0); | |
width: 100%; | |
height: 0; | |
opacity: 0; | |
visibility: hidden; | |
//active panels | |
&.js-active { | |
height: auto; | |
opacity: 1; | |
visibility: visible; | |
} | |
//scaleOut animation | |
&[data-animation="scaleOut"] { | |
transform: scale(1.1); | |
&.js-active { | |
@include transition-mix; | |
transform: scale(1); | |
} | |
} | |
//slideHorz animation | |
&[data-animation="slideHorz"] { | |
left: 50px; | |
&.js-active { | |
@include transition-mix($duration: .25s, $timing: cubic-bezier(0.2, 1.13, 0.38, 1.43)); | |
left: 0; | |
} | |
} | |
//slideVert animation | |
&[data-animation="slideVert"] { | |
top: 30px; | |
&.js-active { | |
@include transition-mix(); | |
top: 0; | |
} | |
} | |
//fadeIn animation | |
&[data-animation="fadeIn"] { | |
&.js-active { | |
@include transition-mix($duration: .3s); | |
} | |
} | |
//scaleOut | |
&[data-animation="scaleIn"] { | |
transform: scale(.9); | |
&.js-active { | |
@include transition-mix; | |
transform: scale(1); | |
} | |
} | |
} |
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet" /> |