Wi-Fi Fail Animation using CSS & JavaScript


A simple Wi-Fi Fail Animation made using CSS & JavaScript. It has a Connect button and animation. Made by Jon Kantner.

The codes are separated in three parts, so copy all three parts combine them and after that run the code. You can use three separate files for HTML, CSS & Js or you can just use one index.html file.

Html Code

Here is the Html Code.

<!DOCTYPE html>
<html lang="en" >
<head>
  <meta charset="UTF-8">
  <title>Wi-Fi Fail Animation</title>
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
  <link rel='stylesheet' href='https://fonts.googleapis.com/css2?family=DM+Sans&amp;display=swap'> 
</head>
<body>
  <main>
	<svg class="wifi" viewBox="0 0 48 48" width="48px" height="48px" aria-hidden="true">
		<defs>
			<clipPath id="clip-path">
				<polygon points="-2,-2 50,-2 24,28" />
			</clipPath>
		</defs>
		<g transform="translate(0,10)">
			<g>
				<g clip-path="url(#clip-path)">
					<g fill="none" stroke="hsl(223,90%,85%)" stroke-width="4">
						<circle class="wifi__wave" r="28" cx="24" cy="28" />
						<circle class="wifi__wave" r="19" cx="24" cy="28" />
						<circle class="wifi__wave" r="10" cx="24" cy="28" />
					</g>
				</g>
				<circle class="wifi__dot" r="3" cx="24" cy="28" fill="hsl(223,90%,85%)" />
			</g>
			<g class="wifi__fg" opacity="0" data-anim>
				<g clip-path="url(#clip-path)">
					<g class="wifi__waves-pivot" fill="none" stroke="hsl(223,90%,15%)" stroke-width="4">
						<circle class="wifi__wave" r="28" cx="24" cy="28" stroke-dasharray="14.66 161.27" />
						<circle class="wifi__wave" r="19" cx="24" cy="28" stroke-dasharray="9.95 109.43" />
						<circle class="wifi__wave" r="10" cx="24" cy="28" stroke-dasharray="5.24 57.5" />
					</g>
				</g>
				<circle class="wifi__dot" r="3" cx="24" cy="28" fill="hsl(223,90%,15%)" />
				<g class="wifi__stick-pivot" fill="hsl(223,90%,15%)">
					<g class="wifi__stick-bounce">
						<rect class="wifi__stick" x="22" y="-2" width="4" height="28" />
						<polygon class="wifi__stick-left" points="22,-2 26,-2 24,26" />
						<polygon class="wifi__stick-right" points="22,-2 26,-2 24,26" />
					</g>
				</g>
			</g>
		</g>
	</svg>
	<button id="connect" class="btn" type="button">Connect</button>
</main>
</body>
</html>

CSS Code

Here is the CSS Code.

* {
	border: 0;
	box-sizing: border-box;
	margin: 0;
	padding: 0;
}
:root {
	--hue: 223;
	--primary1: hsl(var(--hue),90%,95%);
	--primary3: hsl(var(--hue),90%,85%);
	--primary9: hsl(var(--hue),90%,50%);
	--primary11: hsl(var(--hue),90%,40%);
	--primary15: hsl(var(--hue),90%,25%);
	--primary17: hsl(var(--hue),90%,15%);
	--primary19: hsl(var(--hue),90%,5%);
	--trans-dur: 0.3s;
	font-size: calc(16px + (20 - 16) * (100vw - 320px) / (1280 - 320));
}
body,
button {
	font: 1em/1.5 "DM Sans", sans-serif;
}
body {
	background-color: var(--primary1);
	color: var(--primary19);
	height: 100vh;
	display: grid;
	place-items: center;
	transition:
		background-color var(--trans-dur),
		color var(--trans-dur);
}
main {
	padding: 3em 0;
	text-align: center;
}
.wifi {
	display: block;
	margin: 0 auto 3em auto;
	width: 10em;
	height: 10em;
}
.wifi__dot,
.wifi__stick,
.wifi__stick-left,
.wifi__stick-right {
	transition: fill var(--trans-dur);
}
.wifi__fg,
.wifi__stick-bounce,
.wifi__stick-left,
.wifi__stick-right,
.wifi__stick-pivot,
.wifi__waves-pivot {
	animation-duration: 4s;
	animation-timing-function: ease-in-out;
	transform-origin: 24px 28px;
}
.wifi__stick,
.wifi__stick-left,
.wifi__stick-right {
	fill: var(--primary17);
}
.wifi__stick-bounce {
	transform-origin: 24px 26px;
}
.wifi__wave {
	transition: stroke var(--trans-dur);
}
.wifi__fg .wifi__dot {
	fill: var(--primary17);
}
.wifi__fg .wifi__wave {
	stroke: var(--primary17);
}
.wifi--animated .wifi__fg {
	animation-name: foregroundFade;
}
.wifi--animated .wifi__stick-bounce {
	animation-name: stickBounce;
}
.wifi--animated .wifi__stick-left {
	animation-name: stickLeft;
}
.wifi--animated .wifi__stick-right {
	animation-name: stickRight;
}
.wifi--animated .wifi__stick-pivot {
	animation-name: stickPivot;
}
.wifi--animated .wifi__waves-pivot {
	animation-name: wavesPivot;
}
.btn {
	background-color: var(--primary9);
	border-radius: 0.2em;
	color: var(--primary1);
	padding: 0.75em 1.5em;
	transition:
		background-color 0.15s linear,
		opacity 0.15s linear;
}
.btn:focus {
	outline: transparent;
}
.btn:disabled {
	cursor: not-allowed;
	opacity: 0.5;
}
.btn:focus,
.btn:hover {
	background-color: var(--primary11);
}
.btn:disabled:focus,
.btn:disabled:hover {
	background-color: var(--primary9);
}

/* `:focus-visible` support */
@supports selector(:focus-visible) {
	.btn:focus {
		background-color: var(--primary9);
	}
	.btn:focus-visible,
	.btn:hover {
		background-color: var(--primary11);
	}
}

/* Dark theme */
@media (prefers-color-scheme: dark) {
	body {
		background-color: var(--primary19);
		color: var(--primary1);
	}
	.wifi__dot {
		fill: var(--primary15);
	}
	.wifi__stick,
	.wifi__stick-left,
	.wifi__stick-right {
		fill: var(--primary3);
	}
	.wifi__wave {
		stroke: var(--primary15);
	}
	.wifi__fg .wifi__dot {
		fill: var(--primary3);
	}
	.wifi__fg .wifi__wave {
		stroke: var(--primary3);
	}
}

/* Animations */
@keyframes foregroundFade {
	from,
	to {
		opacity: 0;
	}
	5%,
	95% {
		opacity: 1;
	}
}
@keyframes stickBounce {
	from,
	75% {
		transform: translate(0,0) scale(1,1);
	}
	78% {
		transform: translate(0,0) scale(1.25,0.75);
	}
	81% {
		transform: translate(0,-8px) scale(1,1);
	}
	84% {
		transform: translate(0,-6px) scale(1.125,0.625);
	}
	87%,
	to {
		transform: translate(0,-6px) scale(1,0.775);
	}
}
@keyframes stickPivot {
	from,
	5% {
		transform: rotate(-37deg);
	}
	20%,
	25% {
		transform: rotate(37deg);
	}
	40%,
	45% {
		transform: rotate(-37deg);
	}
	60%,
	65% {
		animation-timing-function: ease-in-out;
		transform: rotate(37deg);
	}
	72.5%,
	100% {
		transform: rotate(0);
	}
}
@keyframes stickLeft {
	from,
	81% {
		transform: translate(0,0);
	}
	84%,
	100% {
		transform: translate(-2px,0);
	}
}
@keyframes stickRight {
	from,
	81% {
		transform: translate(0,0);
	}
	84%,
	100% {
		transform: translate(2px,0);
	}
}
@keyframes wavesPivot {
	from,
	5% {
		transform: rotate(-162deg);
	}
	24%,
	25% {
		transform: rotate(-48deg);
	}
	44%,
	45% {
		transform: rotate(-162deg);
	}
	64%,
	100% {
		transform: rotate(-48deg);
	}
}

JavaScript Code

Here is the JavaScript Code.

window.addEventListener("DOMContentLoaded", () => {
  const wifi = new WiFi(".wifi");
  const connectBtn = document.getElementById("connect");
  let timeout = null;

  connectBtn?.addEventListener("click", () => {
    wifi.connect();
    connectBtn.disabled = true;
    connectBtn.innerText = "Connecting…";

    // button reset
    clearTimeout(timeout);
    timeout = setTimeout(() => {
      connectBtn.disabled = false;
      connectBtn.innerText = "Connect";
    }, 4100);
  });
});

class WiFi {
  constructor(sel) {
    this.el = document.querySelector(sel);
    this.isConnecting = false;

    const anim = this.el?.querySelector("[data-anim]");
    anim?.addEventListener("animationend", this.reset.bind(this));
  }
  connect() {
    if (!this.isConnecting) {
      this.isConnecting = true;
      this.el.classList.add("wifi--animated");
    }
  }
  reset() {
    if (this.isConnecting) {
      this.isConnecting = false;
      this.el.classList.remove("wifi--animated");
    }
  }}

Complete Code

Here is the complete code for one single index.html file.

<!DOCTYPE html>
<html lang="en" >
<head>
  <meta charset="UTF-8">
  <title>Wi-Fi Fail Animation</title>
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
  <link rel='stylesheet' href='https://fonts.googleapis.com/css2?family=DM+Sans&amp;display=swap'>  
<style>
* {
	border: 0;
	box-sizing: border-box;
	margin: 0;
	padding: 0;
}
:root {
	--hue: 223;
	--primary1: hsl(var(--hue),90%,95%);
	--primary3: hsl(var(--hue),90%,85%);
	--primary9: hsl(var(--hue),90%,50%);
	--primary11: hsl(var(--hue),90%,40%);
	--primary15: hsl(var(--hue),90%,25%);
	--primary17: hsl(var(--hue),90%,15%);
	--primary19: hsl(var(--hue),90%,5%);
	--trans-dur: 0.3s;
	font-size: calc(16px + (20 - 16) * (100vw - 320px) / (1280 - 320));
}
body,
button {
	font: 1em/1.5 "DM Sans", sans-serif;
}
body {
	background-color: var(--primary1);
	color: var(--primary19);
	height: 100vh;
	display: grid;
	place-items: center;
	transition:
		background-color var(--trans-dur),
		color var(--trans-dur);
}
main {
	padding: 3em 0;
	text-align: center;
}
.wifi {
	display: block;
	margin: 0 auto 3em auto;
	width: 10em;
	height: 10em;
}
.wifi__dot,
.wifi__stick,
.wifi__stick-left,
.wifi__stick-right {
	transition: fill var(--trans-dur);
}
.wifi__fg,
.wifi__stick-bounce,
.wifi__stick-left,
.wifi__stick-right,
.wifi__stick-pivot,
.wifi__waves-pivot {
	animation-duration: 4s;
	animation-timing-function: ease-in-out;
	transform-origin: 24px 28px;
}
.wifi__stick,
.wifi__stick-left,
.wifi__stick-right {
	fill: var(--primary17);
}
.wifi__stick-bounce {
	transform-origin: 24px 26px;
}
.wifi__wave {
	transition: stroke var(--trans-dur);
}
.wifi__fg .wifi__dot {
	fill: var(--primary17);
}
.wifi__fg .wifi__wave {
	stroke: var(--primary17);
}
.wifi--animated .wifi__fg {
	animation-name: foregroundFade;
}
.wifi--animated .wifi__stick-bounce {
	animation-name: stickBounce;
}
.wifi--animated .wifi__stick-left {
	animation-name: stickLeft;
}
.wifi--animated .wifi__stick-right {
	animation-name: stickRight;
}
.wifi--animated .wifi__stick-pivot {
	animation-name: stickPivot;
}
.wifi--animated .wifi__waves-pivot {
	animation-name: wavesPivot;
}
.btn {
	background-color: var(--primary9);
	border-radius: 0.2em;
	color: var(--primary1);
	padding: 0.75em 1.5em;
	transition:
		background-color 0.15s linear,
		opacity 0.15s linear;
}
.btn:focus {
	outline: transparent;
}
.btn:disabled {
	cursor: not-allowed;
	opacity: 0.5;
}
.btn:focus,
.btn:hover {
	background-color: var(--primary11);
}
.btn:disabled:focus,
.btn:disabled:hover {
	background-color: var(--primary9);
}

/* `:focus-visible` support */
@supports selector(:focus-visible) {
	.btn:focus {
		background-color: var(--primary9);
	}
	.btn:focus-visible,
	.btn:hover {
		background-color: var(--primary11);
	}
}

/* Dark theme */
@media (prefers-color-scheme: dark) {
	body {
		background-color: var(--primary19);
		color: var(--primary1);
	}
	.wifi__dot {
		fill: var(--primary15);
	}
	.wifi__stick,
	.wifi__stick-left,
	.wifi__stick-right {
		fill: var(--primary3);
	}
	.wifi__wave {
		stroke: var(--primary15);
	}
	.wifi__fg .wifi__dot {
		fill: var(--primary3);
	}
	.wifi__fg .wifi__wave {
		stroke: var(--primary3);
	}
}

/* Animations */
@keyframes foregroundFade {
	from,
	to {
		opacity: 0;
	}
	5%,
	95% {
		opacity: 1;
	}
}
@keyframes stickBounce {
	from,
	75% {
		transform: translate(0,0) scale(1,1);
	}
	78% {
		transform: translate(0,0) scale(1.25,0.75);
	}
	81% {
		transform: translate(0,-8px) scale(1,1);
	}
	84% {
		transform: translate(0,-6px) scale(1.125,0.625);
	}
	87%,
	to {
		transform: translate(0,-6px) scale(1,0.775);
	}
}
@keyframes stickPivot {
	from,
	5% {
		transform: rotate(-37deg);
	}
	20%,
	25% {
		transform: rotate(37deg);
	}
	40%,
	45% {
		transform: rotate(-37deg);
	}
	60%,
	65% {
		animation-timing-function: ease-in-out;
		transform: rotate(37deg);
	}
	72.5%,
	100% {
		transform: rotate(0);
	}
}
@keyframes stickLeft {
	from,
	81% {
		transform: translate(0,0);
	}
	84%,
	100% {
		transform: translate(-2px,0);
	}
}
@keyframes stickRight {
	from,
	81% {
		transform: translate(0,0);
	}
	84%,
	100% {
		transform: translate(2px,0);
	}
}
@keyframes wavesPivot {
	from,
	5% {
		transform: rotate(-162deg);
	}
	24%,
	25% {
		transform: rotate(-48deg);
	}
	44%,
	45% {
		transform: rotate(-162deg);
	}
	64%,
	100% {
		transform: rotate(-48deg);
	}
}
</style>
</head>
<body>
  <main>
	<svg class="wifi" viewBox="0 0 48 48" width="48px" height="48px" aria-hidden="true">
		<defs>
			<clipPath id="clip-path">
				<polygon points="-2,-2 50,-2 24,28" />
			</clipPath>
		</defs>
		<g transform="translate(0,10)">
			<g>
				<g clip-path="url(#clip-path)">
					<g fill="none" stroke="hsl(223,90%,85%)" stroke-width="4">
						<circle class="wifi__wave" r="28" cx="24" cy="28" />
						<circle class="wifi__wave" r="19" cx="24" cy="28" />
						<circle class="wifi__wave" r="10" cx="24" cy="28" />
					</g>
				</g>
				<circle class="wifi__dot" r="3" cx="24" cy="28" fill="hsl(223,90%,85%)" />
			</g>
			<g class="wifi__fg" opacity="0" data-anim>
				<g clip-path="url(#clip-path)">
					<g class="wifi__waves-pivot" fill="none" stroke="hsl(223,90%,15%)" stroke-width="4">
						<circle class="wifi__wave" r="28" cx="24" cy="28" stroke-dasharray="14.66 161.27" />
						<circle class="wifi__wave" r="19" cx="24" cy="28" stroke-dasharray="9.95 109.43" />
						<circle class="wifi__wave" r="10" cx="24" cy="28" stroke-dasharray="5.24 57.5" />
					</g>
				</g>
				<circle class="wifi__dot" r="3" cx="24" cy="28" fill="hsl(223,90%,15%)" />
				<g class="wifi__stick-pivot" fill="hsl(223,90%,15%)">
					<g class="wifi__stick-bounce">
						<rect class="wifi__stick" x="22" y="-2" width="4" height="28" />
						<polygon class="wifi__stick-left" points="22,-2 26,-2 24,26" />
						<polygon class="wifi__stick-right" points="22,-2 26,-2 24,26" />
					</g>
				</g>
			</g>
		</g>
	</svg>
	<button id="connect" class="btn" type="button">Connect</button>
</main>
      <script>
window.addEventListener("DOMContentLoaded", () => {
  const wifi = new WiFi(".wifi");
  const connectBtn = document.getElementById("connect");
  let timeout = null;

  connectBtn?.addEventListener("click", () => {
    wifi.connect();
    connectBtn.disabled = true;
    connectBtn.innerText = "Connecting…";

    // button reset
    clearTimeout(timeout);
    timeout = setTimeout(() => {
      connectBtn.disabled = false;
      connectBtn.innerText = "Connect";
    }, 4100);
  });
});

class WiFi {
  constructor(sel) {
    this.el = document.querySelector(sel);
    this.isConnecting = false;

    const anim = this.el?.querySelector("[data-anim]");
    anim?.addEventListener("animationend", this.reset.bind(this));
  }
  connect() {
    if (!this.isConnecting) {
      this.isConnecting = true;
      this.el.classList.add("wifi--animated");
    }
  }
  reset() {
    if (this.isConnecting) {
      this.isConnecting = false;
      this.el.classList.remove("wifi--animated");
    }
  }}
    </script>
</body>
</html>

Recent Content