Build an Interactive File Compress Button Using HTML, CSS and JavaScript
Step-by-Step Guide to Building a File Compress Button in HTML , CSS and JavaScript
Introduction
In today's digital landscape, user engagement is key to the success of any web project. One effective way to captivate your audience is through interactive elements like buttons with dynamic animations. In this tutorial, we'll explore how to create an animated button compression effect using HTML, CSS, and JavaScript.
This project is part of Day 36 of the #100DaysOfCode Challenge, aimed at enhancing your front-end development skills. You can download the full source code here and reach out to me for any questions on bento.
Step 1: Setting Up the HTML Structure
Let's start by creating the HTML structure for our button. We'll define a button element with a unique class identifier for styling and interaction purposes.
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Meta Tags for Character Set, Compatibility, and Viewport Configuration -->
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Link to External CSS File for Styling -->
<link rel="stylesheet" href="style.css">
<!-- Document Title -->
<title>Animated Compress File Button</title>
</head>
<body>
<!-- Button with Class 'button' Containing the Compress Files Animation -->
<button class="button">
<!-- Decorative Paper Divs for Button Animation -->
<div class="paper left"></div>
<div class="paper middle"></div>
<div class="paper right"></div>
<!-- Inner Container for Button Content -->
<div class="inner">
<!-- Zipper Animation Container -->
<div class="zipper">
<!-- Zipper Line -->
<div class="line"></div>
<!-- Gradient for Zipper Effect -->
<div class="gradient"></div>
</div>
<!-- Button Text -->
<span>Compress files</span>
<!-- SVG Icon for Checkmark -->
<svg viewBox="0 0 20 16" aria-hidden="true">
<polyline points="3 8.75 7.75 13.5 17 2.5"></polyline>
</svg>
</div>
</button>
<!-- External JavaScript File for Interactive Features -->
<script src="script.js"></script>
</body>
</html>
Explanation of this HTML Code:
Meta Tags:
<meta charset="UTF-8">
: Specifies the character encoding for the HTML document, ensuring it correctly handles text.<meta http-equiv="X-UA-Compatible" content="IE=edge">
: Ensures compatibility with the latest rendering engine in IE.<meta name="viewport" content="width=device-width, initial-scale=1.0">
: Configures the viewport to make the website responsive on different devices.
Link to External CSS:
<link rel="stylesheet" href="style.css">
: Links an external CSS file for styling the HTML elements.
Title:
<title>Animated Compress File Button</title>
: Sets the title of the webpage that appears in the browser tab.
Button Element:
<button class="button">
: Creates a button with the class "button" which will be styled and animated using CSS.
Decorative Paper Divs:
<div class="paper left"></div>
,<div class="paper middle"></div>
, and<div class="paper right"></div>
: These divs are used for the visual effect of papers within the button, which can be styled using CSS.
Inner Container:
<div class="inner">
: Encapsulates the main content of the button, including the zipper and text.
Zipper Animation:
<div class="zipper">
: Container for the zipper effect.<div class="line"></div>
and<div class="gradient"></div>
: These divs create the zipper line and gradient effect for the animation.
Button Text:
<span>Compress files</span>
: The text displayed on the button.
SVG Icon:
<svg viewBox="0 0 20 16" aria-hidden="true">
: An SVG element for a checkmark icon, ensuring it scales properly.aria-hidden="true"
indicates that this element is purely decorative and should be ignored by screen readers.<polyline points="3 8.75 7.75 13.5 17 2.5"></polyline>
: Defines the points of the polyline for the checkmark icon.
External JavaScript:
<script src="script.js"></script>
: Links an external JavaScript file which may be used for adding interactivity or animations.
Step 2: Styling with CSS
Now, let's add some CSS to style our button and create the compression effect. We'll define custom properties, animations, and transitions to achieve the desired visual outcome.
/* style.css */
/* Base Styles for the Button */
.button {
--button-width: 152px;
--background: #404660;
--background-transparent: rgba(64, 70, 96, 0);
--background-hover: #3A4059;
--background-back: #1E2235;
--shadow: rgba(0, 9, 61, 0.125);
--text: #F6F8FF;
--paper: #F6F8FF;
--paper-lines: #D1D6EE;
--paper-shadow: rgba(0, 9, 61, 0.15);
--zipper: #BBC1E1;
--zipper-end: #D1D6EE;
--zipper-line: #8A91B4;
--zipper-lines: #646B8C;
--tick: #F6F8FF;
display: flex;
outline: none;
cursor: pointer;
text-align: center;
border: 0;
padding: 0;
line-height: 24px;
font-family: inherit;
font-weight: 600;
font-size: 14px;
border-radius: 3px;
color: var(--text);
background: var(--background-back);
transition: transform 0.3s;
transform: scale(var(--scale, 1)) translateZ(0);
-webkit-appearance: none;
-webkit-tap-highlight-color: transparent;
}
/* Button Pressed and Hover States */
.button:not(.compress):active {
--scale: 0.95;
}
.button:not(.compress):hover {
--b: var(--background-hover);
}
/* Decorative Papers for Animation */
.button .paper {
width: 26px;
height: 32px;
pointer-events: none;
position: absolute;
top: -2px;
left: var(--left, 50%);
margin: 0 0 0 -13px;
background: var(--paper);
border-radius: 3px;
box-shadow: 0 1px 1px var(--paper-shadow);
opacity: 0;
transform: translate(var(--x, 0), var(--y, -68px));
}
/* Paper Lines for Decoration */
.button .paper:before {
content: "";
position: absolute;
left: 4px;
top: 6px;
width: 18px;
height: 2px;
border-radius: 1px;
background: var(--paper-lines);
box-shadow: 0 6px 0 var(--paper-lines), 0 12px 0 var(--paper-lines), 0 18px 0 var(--paper-lines);
}
/* Positioning Papers Left and Right */
.button .paper.left {
--left: 25%;
--x: -12px;
--y: -52px;
}
.button .paper.right {
--left: 75%;
--x: 12px;
--y: -52px;
}
/* Inner Button Container */
.button .inner {
position: relative;
z-index: 1;
padding: 10px 0;
width: var(--button-width);
border-radius: 3px;
color: var(--text);
transform-origin: 50% 100%;
background: var(--b, var(--background));
box-shadow: 0 1px 2px var(--shadow), 0 4px 7px var(--shadow);
transition: background 0.4s;
}
/* Button Text */
.button .inner span {
display: block;
opacity: var(--span-o, 1);
transform: translateY(var(--span-y, 0)) translateZ(0);
transition: transform 0.3s ease var(--span-de, 0.5s), opacity 0.3s linear var(--span-de, 0.5s);
}
/* SVG Icon for Checkmark */
.button .inner svg {
width: 20px;
height: 16px;
display: block;
position: absolute;
top: 14px;
left: calc(var(--button-width) / 2);
margin-left: -10px;
fill: none;
stroke-width: 2px;
stroke-linecap: round;
stroke-linejoin: round;
stroke: var(--tick);
opacity: var(--tick-o, 0);
transform: scale(var(--tick-s, 0.5));
transition: transform 0.4s ease var(--tick-de, 0s), opacity 0.3s linear var(--tick-de, 0s);
}
/* Zipper Animation Container */
.button .inner .zipper {
width: calc(var(--button-width) + 12px);
position: absolute;
top: 4px;
left: 0;
height: 12px;
overflow: hidden;
opacity: 0;
}
/* Zipper Lines */
.button .inner .zipper:before,
.button .inner .zipper:after {
content: "";
position: absolute;
top: var(--top, 1px);
left: var(--left, 1px);
height: 3px;
width: 2px;
border-radius: 1px;
box-shadow: 5px 0 0 var(--zipper-lines), 10px 0 0 var(--zipper-lines), 15px 0 0 var(--zipper-lines), 20px 0 0 var(--zipper-lines), 25px 0 0 var(--zipper-lines), 30px 0 0 var(--zipper-lines), 35px 0 0 var(--zipper-lines), 40px 0 0 var(--zipper-lines), 45px 0 0 var(--zipper-lines), 50px 0 0 var(--zipper-lines), 55px 0 0 var(--zipper-lines), 60px 0 0 var(--zipper-lines), 65px 0 0 var(--zipper-lines), 70px 0 0 var(--zipper-lines), 75px 0 0 var(--zipper-lines), 80px 0 0 var(--zipper-lines), 85px 0 0 var(--zipper-lines), 90px 0 0 var(--zipper-lines), 95px 0 0 var(--zipper-lines), 100px 0 0 var(--zipper-lines), 105px 0 0 var(--zipper-lines), 110px 0 0 var(--zipper-lines), 115px 0 0 var(--zipper-lines), 120px 0 0 var(--zipper-lines), 125px 0 0 var(--zipper-lines), 130px 0 0 var(--zipper-lines), 135px 0 0 var(--zipper-lines), 140px 0 0 var(--zipper-lines), 145px 0 0 var(--zipper-lines);
background: var(--zipper-lines);
}
/* Positioning Zipper Lines */
.button .inner .zipper:after {
--top: 8px;
--left: 3px;
}
/* Zipper Gradient Effect */
.button .inner .zipper .gradient {
position: absolute;
top: 0;
bottom: 0;
width: 200%;
right: 12px;
z-index: 1;
background: linear-gradient(to right, var(--background-transparent) 0%, var(--background-transparent) 33.33%, var(--background) 66.66%, var(--background) 100%);
background-size: 300% 100%;
background-position-x: var(--gradient, 100%);
transition: background-position var(--gradient-d, 0s) ease var(--gradient-de, 0s);
}
/* Zipper Line */
.button .inner .zipper .line {
width: calc(var(--button-width) + 12);
height: 2px;
margin: 5px 0;
position: relative;
left: -12px;
z-index: 2;
background: var(--zipper-line);
transform: translateX(calc(calc(var(--button-width) * -1) + 8px));
}
/* Zipper End Decoration */
.button .inner .zipper .line:before,
.button .inner .zipper .line:after {
content: "";
position: absolute;
right: var(--right, 0);
width: var(--width, 6px);
height: var(--height, 2px);
border-radius: 1px;
}
.button .inner .zipper .line:before {
background: var(--zipper-end);
}
.button .inner .zipper .line:after {
--right: -7px;
--width: 8px;
--height: 8px;
transform: translate(0, -3px) scaleY(0.6) rotate(-45deg);
-webkit-clip-path: polygon(0 0, 100% 0, 0 100%);
clip-path: polygon(0 0, 100% 0, 0 100%);
background: var(--zipper);
}
/* Button Compression Animation States */
.button.compress {
--span-o: 0;
--span-y: 8px;
--span-de: 0s;
--tick-o: 1;
--tick-s: 1;
--tick-de: 3.2s;
--gradient: 0%;
--gradient-d: 1.8s;
--gradient-de: 1.8s;
}
.button.compress .paper {
-webkit-animation: paper 1.8s linear forwards;
animation: paper 1.8s linear forwards;
}
.button.compress .inner {
-webkit-animation: fold 1.8s linear forwards;
animation: fold 1.8s linear forwards;
}
.button.compress .inner .zipper {
-webkit-animation: zipper 3.5s linear;
animation: zipper 3.5s linear;
}
.button.compress .inner .zipper .line {
-webkit-animation: line 1.2s linear forwards 1.8s;
animation: line 1.2s linear forwards 1.8s;
}
/* Keyframes for Paper Animation */
@-webkit-keyframes paper {
0%,
5% {
transform: translate(var(--x, 0), var(--y, -68px));
}
10%,
100% {
opacity: 1;
}
30% {
transform: translate(0, 1px);
}
50%,
70% {
transform: translate(0, -3px);
}
95%,
100% {
transform: translate(0, 4px);
}
}
@keyframes paper {
0%,
5% {
transform: translate(var(--x, 0), var(--y, -68px));
}
10%,
100% {
opacity: 1;
}
30% {
transform: translate(0, 1px);
}
50%,
70% {
transform: translate(0, -3px);
}
95%,
100% {
transform: translate(0, 4px);
}
}
/* Keyframes for Line Animation */
@-webkit-keyframes line {
60% {
transform: translateX(5px);
}
80% {
transform: translateX(-2px);
}
100% {
transform: translateX(0);
}
}
@keyframes line {
60% {
transform: translateX(5px);
}
80% {
transform: translateX(-2px);
}
100% {
transform: translateX(0);
}
}
/* Keyframes for Fold Animation */
@-webkit-keyframes fold {
10%,
80% {
transform: perspective(500px) rotateX(-20deg) translateZ(16px);
}
}
@keyframes fold {
10%,
80% {
transform: perspective(500px) rotateX(-20deg) translateZ(16px);
}
}
/* Keyframes for Zipper Animation */
@-webkit-keyframes zipper {
20%,
90% {
opacity: 1;
}
}
@keyframes zipper {
20%,
90% {
opacity: 1;
}
}
/* General HTML and Body Styles */
html {
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
min-height: 100vh;
display: flex;
font-family: "Inter", Arial;
justify-content: center;
align-items: center;
background: #E1E6F9;
}
/* Additional Links (Dribbble and Twitter) */
body .dribbble {
position: fixed;
display: block;
right: 20px;
bottom: 20px;
}
body .dribbble img {
display: block;
height: 28px;
}
body .twitter {
position: fixed;
display: block;
right: 64px;
bottom: 14px;
}
body .twitter svg {
width: 32px;
height: 32px;
fill: #1da1f2;
}
Explanation of this CSS Code:
Custom Properties: The button utilizes CSS variables (
--button-width
,--background
, etc.) for flexibility and reusability.Base Styles: The main
.button
class defines general styles such asdisplay
,outline
,cursor
,text-align
, and various states for hover and active interactions.Paper Decoration: The
.paper
class and its modifiers position and style the decorative paper elements.Inner Button Container: The
.inner
class manages the main button content, including text (span
) and SVG icons.Zipper Animation: Styles for the zipper animation elements, with gradient effects and keyframes for smooth animations.
Animations: Keyframes for different parts of the button animation, including
paper
,line
,fold
, andzipper
.General Styles: Box sizing and font smoothing for a consistent look across different elements.
Step 3: Adding JavaScript Functionality
To make our button interactive, we'll use JavaScript to handle the click event and trigger the compression animation. We'll also set a timeout to revert the animation after a certain duration.
// script.js
// Select all elements with the class 'button'
document.querySelectorAll(".button").forEach((button) => {
// Add a 'click' event listener to each button
button.addEventListener(
"click",
(event) => {
// Check if the button does not have the class 'compress'
if (!button.classList.contains("compress")) {
// Add the 'compress' class to initiate the compression animation
button.classList.add("compress");
// Set a timeout to remove the 'compress' class after 4 seconds (4000 milliseconds)
setTimeout(() => {
button.classList.remove("compress");
}, 4000);
}
// Prevent the default action of the event (useful if the button is within a form)
event.preventDefault();
},
{ passive: true }
); // The event listener does not call preventDefault, so it's marked as passive for potential performance benefits
});
Explanation:
Query Selector and Loop:
document.querySelectorAll(".button")
: Selects all elements with the classbutton
..forEach((button) => {...})
: Iterates over each selected button element.
Event Listener:
button.addEventListener("click", (event) => {...}, { passive: true })
: Adds aclick
event listener to each button. The{ passive: true }
option is used because this listener does not callpreventDefault
on scroll-related events, which can improve performance.
Class Handling:
if (!button.classList.contains("compress")) {...}
: Checks if the button does not already have thecompress
class.button.classList.add("compress")
: Adds thecompress
class to the button, which triggers the compression animation.
Timeout for Class Removal:
setTimeout(() => {...}, 4000)
: Sets a timeout to remove thecompress
class after 4000 milliseconds (4 seconds). This ensures the animation runs for a specific duration.button.classList.remove("compress")
: Removes thecompress
class, resetting the button's state.
Prevent Default Action:
event.preventDefault()
: Prevents the default action associated with the button's event (e.g., if the button is within a form, it prevents form submission). This ensures the custom animation behavior takes precedence.
Conclusion
Congratulations! You've successfully created an engaging button compression animation using HTML, CSS, and JavaScript. By incorporating interactive elements like this into your web projects, you can enhance user experience and increase engagement. Keep exploring and experimenting with different animations and effects to make your website stand out.
Remember, practice makes perfect, so don't hesitate to experiment further with the code and tweak it to suit your project's requirements. Stay tuned for more exciting tutorials as we continue our #100DaysOfCode journey!
If you have any questions or feedback, feel free to reach out to me on bento. Happy coding!