<!--
Inspired by @Dimosthenis dribbble shot, check this out >
https://dribbble.com/shots/3449822-Notifiy-Animation
-->
// ---- CONSTS
$primaryColor: #57bafc
$animationDuration: 1s
$inputWidth: 500px
$inputHeight: $inputWidth / 3
$buttonWidth: $inputWidth / 1.5
$buttonHeight: $inputWidth / 3.5
// ---- GLOBAL
html, body, main
height: 100%
background: $primaryColor
*
box-sizing: border-box
// ---- FORM
input, button
font-family: 'Nunito', sans-serif
main, form
display: flex
align-items: center
justify-content: center
input
width: $inputWidth
height: $inputWidth / 3
background: #fff
border: none
border-radius: $inputWidth / 3.75
color: $primaryColor
font-size: $inputWidth / 12
text-transform: uppercase
font-weight: 600
padding: 10px ($inputWidth / 5)
outline: none
cursor: pointer
transition: all 250ms
position: relative
&[readonly]
cursor: not-allowed
&:invalid
box-shadow: none
&::placeholder
color: $primaryColor
text-align: center
opacity: 1
&:not(.active):not(:read-only):hover
opacity: 0.9
&.active
cursor: initial
width: $inputWidth * 2.23
left: $buttonWidth / 2
padding: 10px ($buttonWidth + ($inputHeight - $buttonHeight)) 10px ($inputWidth / 5)
&::placeholder
text-align: left
+ button
display: block
font-weight: 100
&.expand
animation: expand $animationDuration forwards ease-out
@keyframes expand
0%
width: $inputWidth
10%
width: $inputWidth * 2.4
35%
width: $inputWidth * 2.2
50%
width: $inputWidth * 2.24
100%
width: $inputWidth * 2.23
&.collapse
animation: collapse $animationDuration forwards ease-out
@keyframes collapse
0%
width: $inputWidth * 2.23
10%
width: $inputWidth / 1.3
35%
width: $inputWidth * 1.03
50%
width: $inputWidth / 1.02
100%
width: $inputWidth
+ button
display: none
button
width: $buttonWidth
height: $buttonHeight
background: $primaryColor
border: 0
border-radius: $inputWidth / 3.75
position: relative
left: -#{($buttonWidth / 2) + (($inputHeight - $buttonHeight) / 2)}
color: #fff
font-size: $inputWidth / 12
text-transform: uppercase
cursor: pointer
outline: none
z-index: 2
/**
* Element class
* > generate a node
**/
class Element {
/**
* global properties of the node
* @type {{}}
*/
private node: any = {};
/**
* The location where we should insert the generated node
* @type {{}}
*/
private where: any = {};
/**
* Events binbed to the node
* @type {{}}
*/
private events: any = {};
/**
* The created element
*/
private element: HTMLElement = null;
/**
Class constructor
**/
constructor(options: any = {}) {
for (const key in options) {
if (this.hasOwnProperty(key)) {
this[key] = options[key];
}
}
this.createNode();
return this.element;
}
/**
Bind events to the node
**/
private bindEventsListeners() {
for (const key in this.events) {
this.element.addEventListener(key, this.events[key]);
}
}
/**
Create the node
**/
private createNode() {
const node: HTMLElement = document.createElement(this.node.name);
if (this.node.content) node.innerHTML = this.node.content;
for (const key in this.node.attrs) {
node.setAttribute(key, this.node.attrs[key]);
}
this.where.destination.insertAdjacentElement(this.where.position, node);
this.element = node;
if (this.element && this.events) {
this.bindEventsListeners();
}
}
}
// ---------------
// FUNCTIONS
// ---------------
/**
* On input click
* @param e
*/
function inputClick(e: Event) {
const target: HTMLElement = e.currentTarget,
classes: DOMTokenList = target.classList;
if (!classes.contains('collapse') && !classes.contains('expand') && !classes.contains('active') && !target.getAttribute('readonly')) {
const animationEnd: EventListenerOrEventListenerObject = () => {
target.removeEventListener('webkitAnimationEnd', animationEnd);
target.removeEventListener('animationend', animationEnd);
classes.remove('expand');
target.focus();
};
classes.add('active', 'expand');
target.addEventListener('webkitAnimationEnd', animationEnd);
target.addEventListener('animationend', animationEnd);
target.setAttribute('placeholder', 'EMAIL');
}
}
/**
* On input blur
* @param e
*/
function inputBlur(e: Event) {
// Do not hesitate to tell me if you have a cleaner solution to get the active element inside the blur event ! :)
setTimeout(() => {
const input: HTMLElement = e.target,
button: HTMLElement = input.nextSibling,
target: Element = document.activeElement,
classes: DOMTokenList = input.classList;
if (!classes.contains('expand') && classes.contains('active') && target !== button) {
const placeholder: string = input.dataset.placeholder,
animationEnd: EventListenerOrEventListenerObject = () => {
input.removeEventListener('webkitAnimationEnd', animationEnd);
input.removeEventListener('animationend', animationEnd);
classes.remove('collapse');
input.blur();
};
classes.add('collapse');
classes.remove('active');
input.addEventListener('webkitAnimationEnd,', animationEnd);
input.addEventListener('animationend', animationEnd);
input.value = '';
input.setAttribute('placeholder', placeholder);
}
}, 10);
}
/**
* On form submit
*/
function formSubmit(e: Event) {
e.preventDefault();
const input: Node = e.target.querySelector('input[type=email]');
input.dataset.placeholder = 'THANK YOU!';
input.setAttribute('readonly', true);
input.focus();
input.blur();
}
// ---------------
// NODES CREATION
// ---------------
const main = new Element({
'node': {
'name': 'main'
},
'where': {
'destination': document.querySelector('body'),
'position': 'beforeEnd'
}
}),
form = new Element({
'node': {
'name': 'form'
},
'where': {
'destination': main,
'position': 'beforeEnd'
},
'events': {
'submit': formSubmit
}
}),
input = new Element({
'node': {
'name': 'input',
'attrs': {
'type': 'email',
'placeholder': 'NOTIFY ME',
'data-placeholder': 'NOTIFY ME',
'required': 'required'
}
},
'where': {
'destination': form,
'position': 'beforeEnd'
},
'events': {
'click': inputClick,
'blur': inputBlur
}
}),
button = new Element({
'node': {
'name': 'button',
'content': 'SEND',
'attrs': {
'type': 'submit',
'placeholder': 'NOTIFY ME',
'data-placeholder': 'NOTIFY ME'
}
},
'where': {
'destination': input,
'position': 'afterEnd'
}
});
Also see: Tab Triggers