├── .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 |
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 |
--------------------------------------------------------------------------------