<button
id="notifications"
class="notifications notifications--active"
type="button"
>
<span class="notifications__waves"></span>
<span class="notifications__waves"></span>
<span class="notifications__waves"></span>
<span class="notifications__waves"></span>
<span class="notifications__sr">Notifications</span>
<span class="notifications__badge" data-badge>1</span>
</button>
* {
border: 0;
box-sizing: border-box;
margin: 0;
padding: 0;
}
:root {
--hue: 223;
--bg: hsl(var(--hue), 10%, 90%);
--fg: hsl(var(--hue), 10%, 10%);
font-size: calc(40px + (60 - 40) * (100vw - 320px) / (1280 - 320));
}
body,
button {
color: var(--fg);
font: 1em/1.5 "DM Sans", sans-serif;
}
body {
background: var(--bg);
height: 100vh;
display: grid;
place-items: center;
}
.notifications {
background: transparent;
box-shadow: 0 0 0 0.1em transparent;
filter: brightness(1) grayscale(1);
position: relative;
width: 1.5em;
height: 1.5em;
transition: filter 0.15s 0.3s linear;
-webkit-appearance: none;
appearance: none;
-webkit-tap-highlight-color: transparent;
}
.notifications:focus {
outline: none;
}
.notifications:before,
.notifications:after,
.notifications__badge,
.notifications__waves,
.notifications__sr {
position: absolute;
}
.notifications:before,
.notifications:after {
content: "";
display: block;
}
.notifications:before {
background: radial-gradient(0.4em 0.4em at 50% 1.1em, hsl(38, 90%, 55%) 47%, hsla(38, 90%, 55%, 0) 50%);
top: 0.2em;
left: calc(50% - 0.2em);
width: 0.4em;
height: 1.3em;
transform-origin: 50% 0.2em;
}
.notifications:after {
background:
/* rim */
radial-gradient(0.4em 0.2em at 0.2em 1.2em, hsl(45, 90%, 55%) 46%, hsla(45, 90%, 55%, 0) 50%), linear-gradient(hsl(45, 90%, 55%), hsl(45, 90%, 55%)) 0.2em 1.1em / 1.1em 0.2em no-repeat, radial-gradient(0.4em 0.2em at 1.3em 1.2em, hsl(45, 90%, 55%) 46%, hsla(45, 90%, 55%, 0) 50%),
/* middle */
radial-gradient(1.2em 1.2em at 50% 0.75em, hsl(38, 90%, 55%) 49%, hsla(38, 90%, 55%, 0) 50%) 0 0 / 100% 75% no-repeat, linear-gradient(hsl(38, 90%, 55%), hsl(38, 90%, 55%)) 0.15em 0.75em / 1.2em 0.4em no-repeat,
/* top */
radial-gradient(0.3em 0.3em at 50% 0.15em, hsl(45, 90%, 55%) 48%, hsla(45, 90%, 55%, 0) 50%);
top: 0;
left: 0;
width: 100%;
height: 100%;
transform-origin: 50% 0.15em;
}
.notifications__badge {
background: hsl(3, 90%, 55%);
border-radius: 0.375rem;
color: hsl(0, 0%, 100%);
font-size: 0.5em;
font-weight: bold;
padding: 0 0.125rem;
top: -0.125rem;
right: -0.125rem;
min-width: 0.75rem;
height: 0.75rem;
text-align: center;
transition: transform 0.3s 0.15s ease-out;
z-index: 1;
}
.notifications__badge:empty {
transform: scale(0);
transition-delay: 0s;
}
.notifications__waves,
.notifications--active:before,
.notifications--active:after {
animation-duration: 2s;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
.notifications__waves {
animation-delay: 0.6s;
border-top: 0.1em solid transparent;
border-right: 0.1em solid hsla(var(--hue), 10%, 50%);
border-bottom: 0.1em solid transparent;
border-left: 0.1em solid hsla(var(--hue), 10%, 50%);
border-radius: 50%;
top: -50%;
left: -50%;
opacity: 0;
pointer-events: none;
width: 200%;
height: 200%;
transform: scale(0.3);
}
.notifications__waves:nth-child(2) {
animation-delay: 0.75s;
}
.notifications__waves:nth-child(3) {
animation-delay: 0.9s;
}
.notifications__waves:nth-child(4) {
animation-delay: 1.05s;
}
.notifications--active {
filter: brightness(1) grayscale(0);
transition-delay: 0s;
}
.notifications--active:before,
.notifications--active:after {
animation-delay: 0.3s;
}
.notifications--active:before {
animation-name: ringBefore;
}
.notifications--active:after {
animation-name: ringAfter;
}
.notifications--active .notifications__waves {
animation-name: waves;
}
.notifications__sr {
clip: rect(1px, 1px, 1px, 1px);
overflow: hidden;
width: 1px;
height: 1px;
}
/* Dark theme */
@media (prefers-color-scheme: dark) {
:root {
--bg: hsl(var(--hue), 10%, 10%);
--fg: hsl(var(--hue), 10%, 90%);
}
}
/* Animations */
@keyframes ringBefore {
from {
transform: rotate(0);
}
15% {
transform: rotate(-3deg);
}
20% {
transform: rotate(6deg);
}
25% {
transform: rotate(-6deg);
}
30% {
transform: rotate(18deg);
}
35% {
transform: rotate(-18deg);
}
40% {
transform: rotate(22deg);
}
45% {
transform: rotate(-20deg);
}
50% {
transform: rotate(18deg);
}
55% {
transform: rotate(-16deg);
}
60% {
transform: rotate(14deg);
}
65% {
transform: rotate(-12deg);
}
70% {
transform: rotate(10deg);
}
75% {
transform: rotate(-8deg);
}
80% {
transform: rotate(6deg);
}
85% {
transform: rotate(-4deg);
}
90% {
transform: rotate(2deg);
}
95% {
transform: rotate(-1deg);
}
}
@keyframes ringAfter {
from,
40%,
to {
transform: rotate(0);
}
5%,
35% {
transform: rotate(10deg);
}
10%,
30% {
transform: rotate(-10deg);
}
15%,
25% {
transform: rotate(20deg);
}
20% {
transform: rotate(-20deg);
}
}
@keyframes waves {
from {
opacity: 1;
transform: scale(0.3);
}
20%,
to {
opacity: 0;
transform: scale(1);
}
}
window.addEventListener("DOMContentLoaded", () => {
const n = new Notifications("#notifications");
});
class Notifications {
constructor(qs) {
this.el = document.querySelector(qs);
this.badge = null;
this.items = 0;
this.timeout = null;
this.init();
}
init() {
if (this.el) {
this.el.addEventListener("click", this.read.bind(this));
this.badge = this.el.querySelector("[data-badge]");
this.reset(1);
}
}
read() {
if (this.items > 0) {
this.items = 0;
this.el.classList.remove("notifications--active");
this.badge.innerHTML = "";
const items = this.random(1, 10, true);
clearTimeout(this.timeout);
this.timeout = setTimeout(this.reset.bind(this, items), 2e3);
}
}
reset(items) {
this.items = items;
if (this.items > 0) {
this.el.classList.add("notifications--active");
this.badge.innerHTML = this.items;
}
}
random(min, max, round = false) {
const percent = crypto.getRandomValues(new Uint32Array(1))[0] / 2 ** 32;
const relativeValue = (max - min) * percent;
return (
min +
(round === true
? Math.round(relativeValue)
: +relativeValue.toFixed(2))
);
}
}
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.