CODEPEN LINK =
HTML
<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>
<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>
CSS
* {
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);
}
}
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);
}
}
JSS
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");
}
}
}
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");
}
}
}