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" /> |