├── .gitignore ├── package.json ├── README.md ├── index.html ├── css └── main.css ├── scss └── main.scss └── js └── main.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fancy_form", 3 | "version": "1.0.0", 4 | "description": "Fancy Form UI with CSS transitions & progress bar", 5 | "main": "index.js", 6 | "scripts": { 7 | "sass": "node-sass -w scss/ -o css/" 8 | }, 9 | "author": "Brad Traversy", 10 | "license": "MIT", 11 | "dependencies": { 12 | "node-sass": "^4.9.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fancy Form: 2 | 3 | > Form UI with progress bar. Uses HTML-5, CSS-3 Transitions with Sass & JavaScript. 4 | 5 | ## Quick Start: 6 | 7 | ``` bash 8 | # Install dependencies (node-sass) 9 | npm install 10 | 11 | # Watch & Compile Sass 12 | npm run sass 13 | ``` 14 | 15 | ## App Info 16 | 17 | ### Author 18 | 19 | Brad Traversy 20 | [Traversy Media](http://www.traversymedia.com) 21 | 22 | ### Version 23 | 24 | 1.0.0 25 | 26 | ### License 27 | 28 | This project is licensed under the MIT License 29 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | Fancy Form 12 | 13 | 14 | 15 |
16 |

Fancy Form

17 |
18 | 19 | 20 |
21 | 22 | 23 |
24 |
25 |
26 |
27 |
28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /css/main.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css?family=Pacifico|Roboto"); 2 | body { 3 | background: #428bca; 4 | font-family: 'Roboto', sans-serif; 5 | margin: 0; } 6 | 7 | h1.logo { 8 | color: #fff; 9 | font-family: 'Pacifico', cursive; 10 | font-size: 4em; } 11 | 12 | h1.end { 13 | position: relative; 14 | color: #fff; 15 | opacity: 0; 16 | transition: 0.8s ease-in-out; } 17 | 18 | #container { 19 | height: 100vh; 20 | display: flex; 21 | flex-direction: column; 22 | justify-content: top; 23 | align-items: center; } 24 | 25 | #form-box { 26 | background: #fff; 27 | position: relative; 28 | width: 600px; 29 | border-top-right-radius: 5px; 30 | border-top-left-radius: 5px; 31 | box-shadow: 0 16px 24px 2px rgba(0, 0, 0, 0.1), 0 6px 10px 5px rgba(0, 0, 0, 0.1), 0 8px 10px -5px rgba(0, 0, 0, 0.2); 32 | transition: transform 0.1s ease-in-out; } 33 | 34 | #form-box.close { 35 | width: 0; 36 | padding: 0; 37 | overflow: hidden; 38 | transition: 0.8s ease-in-out; 39 | box-shadow: 0 16px 24px 2px rgba(0, 0, 0, 0); } 40 | 41 | #next-btn { 42 | position: absolute; 43 | right: 20px; 44 | bottom: 10px; 45 | font-size: 40px; 46 | color: #428bca; 47 | float: right; 48 | cursor: pointer; 49 | z-index: 2; } 50 | #next-btn:hover { 51 | color: #b9d4ec; } 52 | 53 | #prev-btn { 54 | position: absolute; 55 | font-size: 18px; 56 | left: 30px; 57 | top: 12px; 58 | z-index: 2; 59 | color: #9e9e9e; 60 | float: right; 61 | cursor: pointer; } 62 | #prev-btn:hover { 63 | color: #b9d4ec; } 64 | 65 | #input-group { 66 | position: relative; 67 | padding: 30px 20px 20px 20px; 68 | margin: 10px 60px 10px 10px; 69 | opacity: 0; 70 | transition: opacity 0.3s ease-in-out; } 71 | #input-group input { 72 | position: relative; 73 | width: 100%; 74 | border: none; 75 | font-size: 20px; 76 | font-weight: bold; 77 | outline: 0; 78 | background: transparent; 79 | box-shadow: none; } 80 | #input-group #input-label { 81 | position: absolute; 82 | pointer-events: none; 83 | top: 32px; 84 | left: 20px; 85 | font-size: 20px; 86 | font-weight: bold; 87 | transition: 0.2s ease-in-out; } 88 | #input-group input:valid + #input-label { 89 | top: 6px; 90 | left: 42px; 91 | margin-left: 0 !important; 92 | font-size: 11px; 93 | font-weight: normal; 94 | color: #9e9e9e; } 95 | 96 | #input-progress { 97 | border-bottom: 3px solid #428bca; 98 | width: 0; 99 | transition: width 0.6s ease-in-out; } 100 | 101 | #progress-bar { 102 | position: absolute; 103 | background: #b9d4ec; 104 | height: 10px; 105 | width: 0; 106 | transition: width 0.5s ease-in-out; } 107 | 108 | .close #next-btn, 109 | .close #prev-btn { 110 | color: #fff; } 111 | 112 | .error #input-progress { 113 | border-color: #ff2d26; } 114 | 115 | .error #next-btn { 116 | color: #ff2d26; } 117 | 118 | @media (max-width: 600px) { 119 | #form-box { 120 | width: 80%; } } 121 | -------------------------------------------------------------------------------- /scss/main.scss: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Pacifico|Roboto'); 2 | 3 | $primary: #428bca; 4 | $secondary: lighten($primary, 30%); 5 | $light: #9e9e9e; 6 | $error: #ff2d26; 7 | $progress-height: 10px; 8 | 9 | body { 10 | background: $primary; 11 | font-family: 'Roboto', sans-serif; 12 | margin: 0; 13 | } 14 | 15 | h1.logo { 16 | color: #fff; 17 | font-family: 'Pacifico', cursive; 18 | font-size: 4em; 19 | } 20 | 21 | h1.end { 22 | position: relative; 23 | color: #fff; 24 | opacity: 0; 25 | transition: 0.8s ease-in-out; 26 | } 27 | 28 | #container { 29 | height: 100vh; 30 | display: flex; 31 | flex-direction: column; 32 | justify-content: top; 33 | align-items: center; 34 | } 35 | 36 | #form-box { 37 | background: #fff; 38 | position: relative; 39 | width: 600px; 40 | border-top-right-radius: 5px; 41 | border-top-left-radius: 5px; 42 | box-shadow: 0 16px 24px 2px rgba(0, 0, 0, 0.1), 43 | 0 6px 10px 5px rgba(0, 0, 0, 0.1), 0 8px 10px -5px rgba(0, 0, 0, 0.2); 44 | transition: transform 0.1s ease-in-out; 45 | 46 | &:hover { 47 | // transform: translate(0px, 10px); 48 | } 49 | } 50 | 51 | #form-box.close { 52 | width: 0; 53 | padding: 0; 54 | overflow: hidden; 55 | transition: 0.8s ease-in-out; 56 | box-shadow: 0 16px 24px 2px rgba(0, 0, 0, 0); 57 | } 58 | 59 | #next-btn { 60 | position: absolute; 61 | right: 20px; 62 | bottom: 10px; 63 | font-size: 40px; 64 | color: $primary; 65 | float: right; 66 | cursor: pointer; 67 | z-index: 2; 68 | 69 | &:hover { 70 | color: $secondary; 71 | } 72 | } 73 | 74 | #prev-btn { 75 | position: absolute; 76 | font-size: 18px; 77 | left: 30px; 78 | top: 12px; 79 | z-index: 2; 80 | color: $light; 81 | float: right; 82 | cursor: pointer; 83 | 84 | &:hover { 85 | color: $secondary; 86 | } 87 | } 88 | 89 | #input-group { 90 | position: relative; 91 | padding: 30px 20px 20px 20px; 92 | margin: 10px 60px 10px 10px; 93 | opacity: 0; 94 | transition: opacity 0.3s ease-in-out; 95 | 96 | input { 97 | position: relative; 98 | width: 100%; 99 | border: none; 100 | font-size: 20px; 101 | font-weight: bold; 102 | outline: 0; 103 | background: transparent; 104 | box-shadow: none; 105 | } 106 | 107 | #input-label { 108 | position: absolute; 109 | pointer-events: none; 110 | top: 32px; 111 | left: 20px; 112 | font-size: 20px; 113 | font-weight: bold; 114 | transition: 0.2s ease-in-out; 115 | } 116 | 117 | input:valid + #input-label { 118 | top: 6px; 119 | left: 42px; 120 | margin-left: 0 !important; 121 | font-size: 11px; 122 | font-weight: normal; 123 | color: $light; 124 | } 125 | } 126 | 127 | #input-progress { 128 | border-bottom: 3px solid $primary; 129 | width: 0; 130 | transition: width 0.6s ease-in-out; 131 | } 132 | 133 | #progress-bar { 134 | position: absolute; 135 | background: $secondary; 136 | height: $progress-height; 137 | width: 0; 138 | transition: width 0.5s ease-in-out; 139 | } 140 | 141 | .close { 142 | #next-btn, 143 | #prev-btn { 144 | color: #fff; 145 | } 146 | } 147 | 148 | .error { 149 | #input-progress { 150 | border-color: $error; 151 | } 152 | 153 | #next-btn { 154 | color: $error; 155 | } 156 | } 157 | 158 | @media (max-width: 600px) { 159 | #form-box { 160 | width: 80%; 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /js/main.js: -------------------------------------------------------------------------------- 1 | // Questions Array 2 | const questions = [ 3 | { question: 'Enter Your First Name' }, 4 | { question: 'Enter Your Last Name' }, 5 | { question: 'Enter Your Email', pattern: /\S+@\S+\.\S+/ }, 6 | { question: 'Create A Password', type: 'password' } 7 | ]; 8 | 9 | // Transition Times 10 | const shakeTime = 100; // Shake Transition Time 11 | const switchTime = 200; // Transition Between Questions 12 | 13 | // Init Position At First Question 14 | let position = 0; 15 | 16 | // Init DOM Elements 17 | const formBox = document.querySelector('#form-box'); 18 | const nextBtn = document.querySelector('#next-btn'); 19 | const prevBtn = document.querySelector('#prev-btn'); 20 | const inputGroup = document.querySelector('#input-group'); 21 | const inputField = document.querySelector('#input-field'); 22 | const inputLabel = document.querySelector('#input-label'); 23 | const inputProgress = document.querySelector('#input-progress'); 24 | const progress = document.querySelector('#progress-bar'); 25 | 26 | // EVENTS 27 | 28 | // Get Question On DOM Load 29 | document.addEventListener('DOMContentLoaded', getQuestion); 30 | 31 | // Next Button Click 32 | nextBtn.addEventListener('click', validate); 33 | 34 | // Input Field Enter Click 35 | inputField.addEventListener('keyup', e => { 36 | if (e.keyCode == 13) { 37 | validate(); 38 | } 39 | }); 40 | 41 | // FUNCTIONS 42 | 43 | // Get Question From Array & Add To Markup 44 | function getQuestion() { 45 | // Get Current Question 46 | inputLabel.innerHTML = questions[position].question; 47 | // Get Current Type 48 | inputField.type = questions[position].type || 'text'; 49 | // Get Current Answer 50 | inputField.value = questions[position].answer || ''; 51 | // Focus On Element 52 | inputField.focus(); 53 | 54 | // Set Progress Bar Width - Variable to the questions length 55 | progress.style.width = (position * 100) / questions.length + '%'; 56 | 57 | // Add User Icon OR Back Arrow Depending On Question 58 | prevBtn.className = position ? 'fas fa-arrow-left' : 'fas fa-user'; 59 | 60 | showQuestion(); 61 | } 62 | 63 | // Display Question To User 64 | function showQuestion() { 65 | inputGroup.style.opacity = 1; 66 | inputProgress.style.transition = ''; 67 | inputProgress.style.width = '100%'; 68 | } 69 | 70 | // Hide Question From User 71 | function hideQuestion() { 72 | inputGroup.style.opacity = 0; 73 | inputLabel.style.marginLeft = 0; 74 | inputProgress.style.width = 0; 75 | inputProgress.style.transition = 'none'; 76 | inputGroup.style.border = null; 77 | } 78 | 79 | // Transform To Create Shake Motion 80 | function transform(x, y) { 81 | formBox.style.transform = `translate(${x}px, ${y}px)`; 82 | } 83 | 84 | // Validate Field 85 | function validate() { 86 | // Make Sure Pattern Matches If There Is One 87 | if (!inputField.value.match(questions[position].pattern || /.+/)) { 88 | inputFail(); 89 | } else { 90 | inputPass(); 91 | } 92 | } 93 | 94 | // Field Input Fail 95 | function inputFail() { 96 | formBox.className = 'error'; 97 | // Repeat Shake Motion - Set i to number of shakes 98 | for (let i = 0; i < 6; i++) { 99 | setTimeout(transform, shakeTime * i, ((i % 2) * 2 - 1) * 20, 0); 100 | setTimeout(transform, shakeTime * 6, 0, 0); 101 | inputField.focus(); 102 | } 103 | } 104 | 105 | // Field Input Passed 106 | function inputPass() { 107 | formBox.className = ''; 108 | setTimeout(transform, shakeTime * 0, 0, 10); 109 | setTimeout(transform, shakeTime * 1, 0, 0); 110 | 111 | // Store Answer In Array 112 | questions[position].answer = inputField.value; 113 | 114 | // Increment Position 115 | position++; 116 | 117 | // If New Question, Hide Current and Get Next 118 | if (questions[position]) { 119 | hideQuestion(); 120 | getQuestion(); 121 | } else { 122 | // Remove If No More Questions 123 | hideQuestion(); 124 | formBox.className = 'close'; 125 | progress.style.width = '100%'; 126 | 127 | // Form Complete 128 | formComplete(); 129 | } 130 | } 131 | 132 | // All Fields Complete - Show h1 end 133 | function formComplete() { 134 | const h1 = document.createElement('h1'); 135 | h1.classList.add('end'); 136 | h1.appendChild( 137 | document.createTextNode( 138 | `Thanks ${ 139 | questions[0].answer 140 | } You are registered and will get an email shortly` 141 | ) 142 | ); 143 | setTimeout(() => { 144 | formBox.parentElement.appendChild(h1); 145 | setTimeout(() => (h1.style.opacity = 1), 50); 146 | }, 1000); 147 | } 148 | --------------------------------------------------------------------------------