Introduction: As part of my journey through the #100DaysOfCode Challenge, I recently worked on an exciting project for Day 25. In this project, I created an animated paper plane button using HTML, CSS, JavaScript, and GSAP (GreenSock Animation Platform). This interactive button adds a fun and engaging element to web pages, making it perfect for various applications such as form submissions or call-to-action buttons.
Step 1: Setting Up the Project To begin, download the full source code from the provided link: Download Source Code. Once downloaded, extract the files to your project directory.
Step 2: HTML Structure Open the HTML file and set up the basic structure. This includes defining the button element and its various spans for text and SVG icons. Ensure to link the external CSS and JavaScript files.
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Define character encoding and viewport for responsive design -->
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- Link to external stylesheet -->
<link rel="stylesheet" href="style.css" />
<!-- Title of the webpage -->
<title>Animated Send Button With Paper Plane Animation</title>
</head>
<body>
<!-- Button element -->
<button class="button">
<!-- Default button text -->
<span class="default">Send Now</span>
<!-- Success button text with SVG icon -->
<span class="success">
<svg viewBox="0 0 16 16">
<polyline points="3.75 9 7 12 13 5"></polyline>
</svg>Sent
</span>
<!-- SVG for animation trails -->
<svg class="trails" viewBox="0 0 33 64">
<!-- Path for animation trails -->
<path d="M26,4 C28,13.3333333 29,22.6666667 29,32 C29,41.3333333 28,50.6666667 26,60"></path>
<path d="M6,4 C8,13.3333333 9,22.6666667 9,32 C9,41.3333333 8,50.6666667 6,60"></path>
</svg>
<!-- Paper plane animation div -->
<div class="plane">
<!-- Left wing of the paper plane -->
<div class="left"></div>
<!-- Right wing of the paper plane -->
<div class="right"></div>
</div>
</button>
</body>
<!-- Link to external JavaScript files -->
<script src="script.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"
integrity="sha512-16esztaSRplJROstbIIdwX3N97V1+pZvV33ABoG1H2OyTttBxEGkTsoIVsiP1iaTtM8b3+hu2kB6pQ4Clr5yug=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
</html>
Step 3: CSS Styling Open the style.css
file and define the styles for the button and its animations. This includes setting custom properties for various button states and animation effects.
/* Define custom properties for button styling */
.button {
--primary: #f6f8ff;
--primary-dark: #d1d6ee;
--primary-darkest: #8a91b4;
--shadow: rgba(0, 0, 0, 0.3);
--text: #362a89;
--text-opacity: 1;
--success: #eeecff;
--success-x: -12;
--success-stroke: 14;
--success-opacity: 0;
--border-radius: 7;
--overflow: hidden;
--x: 0;
--y: 0;
--rotate: 0;
--plane-x: 0;
--plane-y: 0;
--plane-opacity: 1;
--trails: rgba(255, 255, 255, 0.15);
--trails-stroke: 57;
--left-wing-background: var(--primary);
--left-wing-first-x: 0;
--left-wing-first-y: 0;
--left-wing-second-x: 50;
--left-wing-second-y: 0;
--left-wing-third-x: 0;
--left-wing-third-y: 100;
--left-body-background: var(--primary);
--left-body-first-x: 51;
--left-body-first-y: 0;
--left-body-second-x: 51;
--left-body-second-y: 100;
--left-body-third-x: 0;
--left-body-third-y: 100;
--right-wing-background: var(--primary);
--right-wing-first-x: 49;
--right-wing-first-y: 0;
--right-wing-second-x: 100;
--right-wing-second-y: 0;
--right-wing-third-x: 100;
--right-wing-third-y: 100;
--right-body-background: var(--primary);
--right-body-first-x: 49;
--right-body-first-y: 0;
--right-body-second-x: 49;
--right-body-second-y: 100;
--right-body-third-x: 100;
--right-body-third-y: 100;
/* Button base styles */
display: block;
cursor: pointer;
position: relative;
border: 0;
padding: 8px 0;
min-width: 100px;
text-align: center;
margin: 0;
line-height: 24px;
font-family: sans-serif;
font-weight: 600;
font-size: 14px;
background: none;
outline: none;
color: var(--text);
-webkit-appearance: none;
-webkit-tap-highlight-color: transparent;
}
/* Styles for paper plane animation and trails */
.button .plane,
.button .trails {
pointer-events: none;
position: absolute;
}
/* Styles for paper plane animation */
.button .plane {
left: 0;
top: 0;
right: 0;
bottom: 0;
filter: drop-shadow(0 3px 6px var(--shadow));
transform: translate(calc(var(--x) * 1px), calc(var(--y) * 1px)) rotate(calc(var(--rotate) * 1deg)) translateZ(0);
}
/* Styles for paper plane wings */
.button .plane .left,
.button .plane .right {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
opacity: var(--plane-opacity);
transform: translate(calc(var(--plane-x) * 1px), calc(var(--plane-y) * 1px)) translateZ(0);
}
/* Styles for paper plane wing elements */
.button .plane .left:before,
.button .plane .left:after,
.button .plane .right:before,
.button .plane .right:after {
content: "";
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
border-radius: calc(var(--border-radius) * 1px);
transform: translate(var(--part-x, 0.4%), var(--part-y, 0)) translateZ(0);
z-index: var(--z-index, 2);
background: var(--background, var(--left-wing-background));
-webkit-clip-path: polygon(calc(var(--first-x, var(--left-wing-first-x)) * 1%) calc(var(--first-y, var(--left-wing-first-y)) * 1%), calc(var(--second-x, var(--left-wing-second-x)) * 1%) calc(var(--second-y, var(--left-wing-second-y)) * 1%), calc(var(--third-x, var(--left-wing-third-x)) * 1%) calc(var(--third-y, var(--left-wing-third-y)) * 1%));
clip-path: polygon(calc(var(--first-x, var(--left-wing-first-x)) * 1%) calc(var(--first-y, var(--left-wing-first-y)) * 1%), calc(var(--second-x, var(--left-wing-second-x)) * 1%) calc(var(--second-y, var(--left-wing-second-y)) * 1%), calc(var(--third-x, var(--left-wing-third-x)) * 1%) calc(var(--third-y, var(--left-wing-third-y)) * 1%));
}
/* Styles for left wing */
.button .plane .left:after {
--part-x: -1%;
--z-index: 1;
--background: var(--left-body-background);
--first-x: var(--left-body-first-x);
--first-y: var(--left-body-first-y);
--second-x: var(--left-body-second-x);
--second-y: var(--left-body-second-y);
--third-x: var(--left-body-third-x);
--third-y: var(--left-body-third-y);
}
/* Styles for left wing */
.button .plane .right:before {
--part-x: -1%;
--z-index: 2;
--background: var(--right-wing-background);
--first-x: var(--right-wing-first-x);
--first-y: var(--right-wing-first-y);
--second-x: var(--right-wing-second-x);
--second-y: var(--right-wing-second-y);
--third-x: var(--right-wing-third-x);
--third-y: var(--right-wing-third-y);
}
/* Styles for right wing element */
.button .plane .right:after {
--part-x: 0;
--z-index: 1;
--background: var(--right-body-background);
--first-x: var(--right-body-first-x);
--first-y: var(--right-body-first-y);
--second-x: var(--right-body-second-x);
--second-y: var(--right-body-second-y);
--third-x: var(--right-body-third-x);
--third-y: var(--right-body-third-y);
}
/* Styles for animation trails */
.button .trails {
display: block;
width: 33px;
height: 64px;
top: -4px;
left: 16px;
fill: none;
stroke: var(--trails);
stroke-linecap: round;
stroke-width: 2;
stroke-dasharray: 57px;
stroke-dashoffset: calc(var(--trails-stroke) * 1px);
transform: rotate(68deg) translateZ(0);
}
/* Styles for button text */
.button span {
display: block;
position: relative;
z-index: 4;
opacity: var(--text-opacity);
}
/* Styles for success message */
.button span.success {
z-index: 0;
position: absolute;
left: 0;
right: 0;
top: 8px;
transform: translateX(calc(var(--success-x) * 1px)) translateZ(0);
opacity: var(--success-opacity);
color: var(--success);
}
/* Styles for success icon */
.button span.success svg {
display: inline-block;
vertical-align: top;
width: 16px;
height: 16px;
margin: 4px 8px 0 0;
fill: none;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
stroke-dasharray: 14px;
stroke: var(--success);
stroke-dashoffset: calc(var(--success-stroke) * 1px);
}
/* Global styles */
html {
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
}
* {
box-sizing: inherit;
}
*:before,
*:after {
box-sizing: inherit;
}
body {
overflow: hidden;
min-height: 100vh;
font-family: sans-serif;
display: flex;
justify-content: center;
align-items: center;
background-image: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
Step 4: JavaScript Logic Open the script.js
file and write the JavaScript logic to handle button clicks and initiate animations using GSAP. Adding Animation Effects Implement animation effects using GSAP to create the paper plane animation, button transformation, and other interactive elements.
// Select all elements with the class "button" and apply event listeners and animations
document.querySelectorAll(".button").forEach((button) => {
// Function to get computed CSS variable values
let getVar = (variable) =>
getComputedStyle(button).getPropertyValue(variable);
// Add click event listener to each button
button.addEventListener("click", (e) => {
// Check if the button is not already active
if (!button.classList.contains("active")) {
// Add the "active" class to the button
button.classList.add("active");
// Animation using GSAP library
gsap.to(button, {
// Keyframes for animation
keyframes: [
{
"--left-wing-first-x": 50,
"--left-wing-first-y": 100,
"--right-wing-second-x": 50,
"--right-wing-second-y": 100,
duration: 0.2,
// Function executed after completing this keyframe
onComplete() {
// Set new CSS variables for paper plane transformation
gsap.set(button, {
"--left-wing-first-y": 0,
"--left-wing-second-x": 40,
"--left-wing-second-y": 100,
"--left-wing-third-x": 0,
"--left-wing-third-y": 100,
"--left-body-third-x": 40,
"--right-wing-first-x": 50,
"--right-wing-first-y": 0,
"--right-wing-second-x": 60,
"--right-wing-second-y": 100,
"--right-wing-third-x": 100,
"--right-wing-third-y": 100,
"--right-body-third-x": 60,
});
},
},
{
"--left-wing-third-x": 20,
"--left-wing-third-y": 90,
"--left-wing-second-y": 90,
"--left-body-third-y": 90,
"--right-wing-third-x": 80,
"--right-wing-third-y": 90,
"--right-body-third-y": 90,
"--right-wing-second-y": 90,
duration: 0.2,
},
{
"--rotate": 50,
"--left-wing-third-y": 95,
"--left-wing-third-x": 27,
"--right-body-third-x": 45,
"--right-wing-second-x": 45,
"--right-wing-third-x": 60,
"--right-wing-third-y": 83,
duration: 0.25,
},
{
"--rotate": 60,
"--plane-x": -8,
"--plane-y": 40,
duration: 0.2,
},
{
"--rotate": 40,
"--plane-x": 45,
"--plane-y": -300,
"--plane-opacity": 0,
duration: 0.375,
// Function executed after completing this keyframe
onComplete() {
// Remove inline styles added during animation
setTimeout(() => {
button.removeAttribute("style");
// Fade in button after animation completes
gsap.fromTo(
button,
{
opacity: 0,
y: -8,
},
{
opacity: 1,
y: 0,
clearProps: true,
duration: 0.3,
// Function executed after completing this animation
onComplete() {
// Remove the "active" class from the button
button.classList.remove("active");
},
}
);
}, 1800);
},
},
],
});
// Additional animation for button appearance change
gsap.to(button, {
// Keyframes for animation
keyframes: [
{
"--text-opacity": 0,
"--border-radius": 0,
"--left-wing-background": getVar("--primary-dark"),
"--right-wing-background": getVar("--primary-dark"),
duration: 0.11,
},
{
"--left-wing-background": getVar("--primary"),
"--right-wing-background": getVar("--primary"),
duration: 0.14,
},
{
"--left-body-background": getVar("--primary-dark"),
"--right-body-background": getVar("--primary-darkest"),
duration: 0.25,
delay: 0.1,
},
{
"--trails-stroke": 171,
duration: 0.22,
delay: 0.22,
},
{
"--success-opacity": 1,
"--success-x": 0,
duration: 0.2,
delay: 0.15,
},
{
"--success-stroke": 0,
duration: 0.15,
},
],
});
}
});
});
Conclusion: In this project, we successfully created an animated paper plane button using HTML, CSS, JavaScript, and GSAP. This project adds an interactive and visually appealing element to web pages, enhancing user experience and engagement. By following these step-by-step instructions, you can easily implement similar animations in your web projects. Stay tuned for more exciting projects as I continue my #100DaysOfCode Challenge!
Connect with Me: If you have any questions or want to discuss this project further, feel free to connect with me on Bento. Keep coding and stay inspired!