Skip to content

Instantly share code, notes, and snippets.

@version-control
Created February 16, 2022 14:26
Show Gist options
  • Save version-control/1e4c5b40a94fc8caf55d51b371197c44 to your computer and use it in GitHub Desktop.
Save version-control/1e4c5b40a94fc8caf55d51b371197c44 to your computer and use it in GitHub Desktop.
Multi Step Bootstrap Form with animations
//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
//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" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment