<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#ffffff">
<title>React App</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
</body>
</html>
* {
box-sizing: border-box;
}
body {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
min-height: 100vh;
margin: 0;
padding: 0;
font-family: sans-serif;
overflow-x: hidden;
overflow-y: auto;
}
@keyframes letterLoaderTop {
0% {
transform: scale(1, 1) rotate(45deg) skew(0deg, 0deg) translate(35%, 35%);
z-index: -1;
opacity: 0;
}
40% {
transform: scale(1, 1) rotate(45deg) skew(0deg, 0deg) translate(0, 0);
z-index: -1;
opacity: 1;
}
60% {
transform: scale(1, 1) rotate(45deg) skew(0deg, 0deg) translate(0, 0);
z-index: 1;
border-color: transparent;
opacity: 1;
}
100% {
transform: scale(1, -1) rotate(45deg) skew(0deg, 0deg) translate(-1%, -1%);
z-index: 1;
border-color: hsl(210, 50%, 95%);
opacity: 1;
}
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
@keyframes swoosh {
0% {
transform: translateX(0);
}
30% {
transform: translateX(-20%);
}
100% {
transform: translateX(150vw);
}
}
.form {
position: relative;
display: flex;
flex-wrap: wrap;
max-width: 90vw;
padding: 40px;
border: 2px solid hsl(210, 50%, 90%);
background-color: hsl(210, 50%, 95%);
}
.form__loader {
pointer-events: none;
}
.form__loader::before {
content: "";
position: absolute;
top: -32vw;
left: 15%;
width: 70%;
padding-top: 70%;
border: 3px solid transparent;
border-bottom: 0;
border-right: 0;
transform: rotate(45deg) translate(35%, 35%);
background: linear-gradient(135deg, hsl(210, 50%, 90%) 0%, hsl(210, 50%, 90%) 50%, transparent 50.1%, transparent 100%);
z-index: -1;
opacity: 0;
}
.form__loader::after {
content: "";
box-sizing: border-box;
position: absolute;
top: 35vw;
left: calc(50% - 50px);
width: 100px;
height: 100px;
border-radius: 100%;
border: 10px solid hsl(210, 50%, 85%);
border-top-color: hsl(210, 50%, 75%);
z-index: 1;
animation: spin 1.4s infinite linear;
opacity: 0;
transition: opacity 0.4s ease-out;
}
.form.is--loading,
.form.is--sent {
cursor: not-allowed;
}
.form * {
transition: opacity 0.4s ease-out;
}
.form.is--loading *:not(.form__loader),
.form.is--sent *:not(.form__loader) {
transition-delay: 0.6s;
opacity: 0;
pointer-events: none;
}
.form.is--loading .form__loader::before {
animation: letterLoaderTop 1s forwards;
}
.form.is--loading .form__loader::after {
opacity: 1;
transition-delay: 1s;
}
.form.is--sent {
animation: swoosh 1s forwards;
}
.form.is--sent .form__loader::before {
animation: letterLoaderTop 1s forwards paused;
animation-delay: -0.9s;
}
.form.is--sent .form__loader::after {
opacity: 0;
}
@media screen and (min-width: 768px) {
.form {
max-width: 690px;
}
.form__loader::before {
top: -59%;
}
.form__loader::after {
top: 65%;
}
}
.button {
display: inline-block;
margin: auto;
}
.button__element {
padding: 0.5em 1ch;
border: none;
font-size: 14px;
text-transform: uppercase;
color: hsl(210, 50%, 20%);
background: hsl(210, 50%, 90%);
cursor: pointer;
box-shadow: 0 0 0 2px hsl(210, 50%, 20%);
}
.button__element:hover,
.button__element:focus {
color: hsl(210, 50%, 90%);
background: hsl(210, 50%, 20%);
box-shadow: 0 0 0 2px hsl(210, 50%, 90%);
}
.input {
position: relative;
margin-bottom: 2em;
display: flex;
flex-wrap: wrap;
flex: 1 0 100%;
}
.input::before,
.input::after {
content: "";
position: absolute;
bottom: 0;
width: 100%;
height: 2px;
background-color: hsl(210, 50%, 20%);
transform: scaleX(0);
transform-origin: left;
transition: transform 200ms ease-in-out;
}
.input::before {
background-color: hsl(210, 50%, 90%);
transform: scaleX(1);
}
.input:focus-within::after {
transform: scaleX(1);
}
.input__label {
display: block;
margin-bottom: 2px;
font-size: 12px;
text-transform: uppercase;
color: hsl(210, 50%, 20%);
}
.input__field {
width: 100%;
padding: 0.2em 0.5ch;
border: none;
font-size: 16px;
background-color: hsl(210, 50%, 90%);
}
.input__field.is--large {
min-height: 10em;
}
.input__field:focus {
outline: none;
}
@media screen and (min-width: 768px) {
.input {
flex-wrap: nowrap;
}
.input__label {
flex: 1 1 20%;
min-width: 140px;
margin-top: 0.5em;
text-align: right;
padding-right: 1ch;
}
.input__field {
flex: 1 1 80%;
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.opts = {
'formStateDefault': null,
'formStateLoading': 'loading',
'formStateSent': 'sent'
};
this.state = {
formState: this.opts.formStateDefault
};
this.el = React.createRef();
this.nameInput = React.createRef();
this.mailInput = React.createRef();
this.messageInput = React.createRef();
}
/**
* triggers the loading animation and serializes the form
* prevents the submit event
*
* @param e
*/
evaluateForm = (e) => {
e.preventDefault();
this.setState({
formState: this.opts.formStateLoading
});
let data = new FormData(this.el.current);
this.sendForm(data);
};
/**
* Mockup function to send the data to a hypothetic API endpoint
*
* @param data
*/
sendForm = (data) => {
// mockup: send data here
window.setTimeout(() => this.onFormSent(), 2000);
};
/**
* Triggers the success animation
*/
onFormSent = () => {
this.setState({
formState: this.opts.formStateSent
});
window.setTimeout(() => this.resetForm(), 2000);
};
/**
* Resets the form to empty
*/
resetForm = () => {
this.nameInput.current.setState({value: ''});
this.mailInput.current.setState({value: ''});
this.messageInput.current.setState({value: ''});
this.setState({
formState: null
});
};
render() {
return (
<form className={'form' + (this.state.formState ? ' is--' + this.state.formState : '')}
onSubmit={this.evaluateForm}
ref={this.el}>
<Input id={'name'} label={'Your name'} required={true} ref={this.nameInput} />
<Input id={'mail'} label={'Your e-mail address'} type={'email'} required={true} ref={this.mailInput} />
<Input id={'message'} label={'Your message'} type={'textarea'} required={true} ref={this.messageInput} />
<Button type={'submit'} text={'Contact Us!'} />
<div className="form__loader"> </div>
</form>
);
}
}
class Button extends React.Component {
render() {
return (
<div className="button">
<button type={this.props.type} className="button__element">
{this.props.text}
</button>
</div>
);
}
}
class Input extends React.Component {
constructor(props) {
super(props);
this.opts = {
'elementTypeInput': 'input',
'elementTypeTextarea': 'textarea',
};
this.state = {
value: ''
};
}
/**
* Pushes inputs into the state
*
* @param event
*/
onChange = (event) => {
this.setState({value: event.target.value});
};
render() {
let InputEl = this.opts.elementTypeInput,
InputType = this.props.type;
if (this.props.type === this.opts.elementTypeTextarea) {
InputEl = this.opts.elementTypeTextarea;
InputType = null;
}
return (
<div className="input">
<label className="input__label" htmlFor={this.props.id}>{this.props.label}</label>
<InputEl
type={InputType}
id={this.props.id}
name={this.props.id}
className={"input__field" + (this.props.type === this.opts.elementTypeTextarea ? " is--large" : "")}
value={this.state.value}
required={this.props.required}
onChange={this.onChange.bind(this)}
/>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
View Compiled
This Pen doesn't use any external CSS resources.