Photo by Brooke Cagle on Unsplash

Taking a simple Contact Form to the next level with css animation and three.js

Today I’m going to make a boring Contact Form fun. Here is glimpse of what we will start building today :

In this post I will discuss the HTML and css part with a bit of JavaScript to make the form fully functional and in the next post I’ll discuss how I made the interactive star-field background with three.js.

Let’s Start with HTML

The HTML should be pretty strait forward, two label and two input-box with type="text"

<div class="container">
<form id="send-it" class="being-sent">
<div class="inputbox hidden" style="--delay:100ms;">
<label class="placeholder">name :</label>
<input class="input-text" type="text" minlength="3" required />
</div>
<div class="inputbox hidden" style="--delay:200ms;">
<label class="placeholder">Email :</label>
<input class="input-email" type="email" required />
</div>
<button class="btn hidden" type="submit" style="--delay:400ms;">Send It</button>
</form>
<span id="roket"><span>
</div>
<span id="msg">Sent !</span>

Two things to notice here,

1 ) first notice I wrapped both the label and input element with divs because I wanted to make the labels to look like permanent placeholders.

2) The required and minlength in text input-boxes. require enforces that those input fields needs to be filled before submitting the form and minlength=3 enforces the input to be at least 3 character long.

You might be wondering why bother with it tho? —I promise it will make sense when we will style them.

CSS to Make it awesome

It could be nice I were to explain my css line by line but that will make this post too long which I definitely want to avoid, but I will walk you through most of important bits and pieces.

Styling the form

I made the form a flex container with flex-direction:column; and centered them horizontally centered with align-items:center; also I made the form centered in the middel of the page with display:grid; and place-items:center; and DON’T forget to use min-height:100vh on the grid-container.

To make the label to look like a placeholder made the position:relative; and style the label with following properties:

.placeholder {
text-transform: capitalize;
color: var(--clr-light-gray);
font-weight: 600;
position: absolute;
top: 50%;
left: 0.5em;
transform: translateY(-50%);
z-index: 2;
pointer-events: none;
}

First three properties will make it all capital with bold font and a grayish color, 2nd set of properties will place the label vertically centered and horizontally left aligned inside the input-box. The last two will bring it on top of the input element and remove all pointer events, i.e. it will not interact with any pointing device.

Now for placeholder to work properly the input element will need a padding-left:6ch; I also added additional styling to the input element with :focus and :valid . “focus” pseudo-class is simple with someone clicks it it gains focus and activates the set of styling, in my case I added a deep-orange colored left border to it.

The “valid” is triggered when the form becomes valid, for my case the form is valid when name is more than 3 character long and correctly formatted email address is entered. Now that should explain why I added those attributes( required & minlength) 😉. I also added border transition to make the transition between border-colors smoother.

.input-text:focus,
.input-email:focus {
outline: none;
border-left: 2px solid var(--clr-primary);
}
.input-text:valid,
.input-email:valid {
outline: none;
border-left: 2px solid var(--clr-green);
}

Animating the Loading-in animation

To do that I will use that hidden class to hide elements with clip-path. To make it work properly we need to add clip-path property to the elements too which have the hidden class and make it smooth we’ll add in a transition too.

.inputbox,
.btn{
clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
transition: clip-path 500ms ease-in-out var(--delay);
}
.hidden {
clip-path: polygon(0 0, 100% 0, 100% 0, 0 0);
}

Notice I have put var(--delay) as the delay of the transition, well where does it come from ?

It came form the css custom properties I defined within the style attribute of the elements. it is replaced with thouse defined values (100ms, 200ms & 400ms). It will make the transition start at different times.

Now we will use JS to remove the hidden class to complete the process.

const hiddens = document.querySelectorAll(".hidden");document.addEventListener("DOMContentLoaded", () => {
hiddens.forEach((hid) => hid.classList.remove("hidden"));
});

Making the 🚀 launch

lets first style the rocket we will add in animation later.

#roket {
z-index: 1;
position: absolute;
width: 2px;
height: 0px;
background-image: linear-gradient(
var(--clr-white) 60%,
var(--clr-primary),
transparent
);

top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}

The heightis zero so we wont be able to see it for now. Here the important bit is the linear-gradient which will make it look like a space ship with burning fuel form the bottom. and it is centered in the middle right over the form with display:absolute and

top:50%;
left:50%;
transform:translate(-50%,-50%):

Now we should focus on the form animation where I decided to squeeze it down to a line with again clip-path then bringing the opacity to 0. This keyframes animation will do just that :

@keyframes clipit {
from {
clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
opacity: 1;
}
75% {
clip-path: polygon(49.5% 0, 50.5% 0, 50.5% 100%, 49.5% 100%);
opacity: 0.90;
}
to {
clip-path: polygon(49.5% 0, 50.5% 0, 50.5% 100%, 49.5% 100%);
opacity: 0;
}
}

To add the animation to the form we need to include the following form element.

animation-name: clipit;
animation-delay: 150ms;
animation-duration: 1s;
animation-fill-mode: forwards;
animation-play-state: paused;

and the rocket keyframe animation will be :

@keyframes sending {
from {
height: 0;
transform: translate(-50%, -50%);
}
15% {
transform: translate(-50%, -50%);
}
25% {
height: 200px;
}
to {
height: 200px;
transform: translate(-50%, -110vh);
}
}

It will make the height to 200px within 25% of the animation-duration so it will become visible and it will maintain the height throughout the animation. The transform will make it shoot out of the screen but it will not start immediately after the animation starts, it will delayed by 15% of the animation-duration.Now lets attach it to the #roket with following properties to the style of it:

animation-name: sending;
animation-delay: 750ms;
animation-duration: 1.25s;
animation-fill-mode: forwards;
animation-play-state: paused;

the 750ms delay will make the room for the previous “form-closing” animation and start this at the right time. Both the animation are in paused state, with the help of JS we will change the animation-play-state to running to play the animation and it will play the the animation once as the animation-fill-mode is set to forwards.

const form = document.getElementById("send-it");
const roket = document.getElementById("roket");
const msg = document.getElementById("msg");
form.addEventListener("submit", (e) => {
e.preventDefault();
e.target.style.animationPlayState = "running";
roket.style.animationPlayState = "running";
msg.style.zIndex = 2;
msg.style.opacity = 1;
});

The last three lines will make the span with the msg class appear from behind.

That’s it we have done it. The HMTL css part is over, now the contact form should look like this, but without the interactive background, which I’ve discussed in my next post Adding a Custom Star-Field Background with threejs .

I have used clippy to make my clip-path which makes it lot easier to use clip-path. This made for Scrimba’s Weekly Web Dev Challenge 3rd week of April, 2021.

who loves to learn new things

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store