A spinning basketball arcs into the hoop, swishes the net, bounces once on the floor and fades before the next shot. The arc is two nested elements, one moving across and one up and down.
Also known as: basketball animation css, basketball shot loader, sports loading animation.
<div class="ldr-basketball" role="status" aria-label="Loading"> <span class="ldr-basketball-x"><span class="ldr-basketball-y"><span class="ldr-basketball-ball"></span></span></span> <span class="ldr-basketball-rim"><span class="ldr-basketball-net"></span></span> </div>
.ldr-basketball {
--s: var(--ldr-size, 48px);
--c: var(--ldr-color, #3b82f6);
position: relative;
width: calc(var(--s) * 1.5);
height: var(--s);
}
.ldr-basketball::before {
content: "";
position: absolute;
right: 4%;
top: 6%;
width: calc(var(--s) / 16);
height: calc(var(--s) * 0.5);
border-radius: var(--s);
background: var(--c);
}
.ldr-basketball::after {
content: "";
position: absolute;
left: 0;
right: 0;
bottom: 2%;
height: calc(var(--s) / 30);
border-radius: var(--s);
background: color-mix(in srgb, var(--ldr-color, #3b82f6) 25%, transparent);
}
.ldr-basketball-rim {
position: absolute;
z-index: 2;
right: calc(4% + var(--s) / 16);
top: 38%;
width: calc(var(--s) * 0.36);
height: calc(var(--s) / 18);
border-radius: var(--s);
background: var(--c);
}
.ldr-basketball-net {
position: absolute;
top: 100%;
left: 6%;
right: 6%;
height: calc(var(--s) * 0.24);
background:
repeating-linear-gradient(60deg, color-mix(in srgb, var(--ldr-color, #3b82f6) 70%, transparent) 0 calc(var(--s) / 36), transparent 0 calc(var(--s) * 0.07)),
repeating-linear-gradient(-60deg, color-mix(in srgb, var(--ldr-color, #3b82f6) 70%, transparent) 0 calc(var(--s) / 36), transparent 0 calc(var(--s) * 0.07));
clip-path: polygon(0 0, 100% 0, 82% 100%, 18% 100%);
transform-origin: 50% 0;
animation: ldr-basketball-swish 2.4s infinite;
}
.ldr-basketball-x {
position: absolute;
z-index: 1;
left: calc(var(--s) * 0.06);
bottom: calc(var(--s) * 0.04);
width: calc(var(--s) * 0.28);
height: calc(var(--s) * 0.28);
animation: ldr-basketball-x 2.4s infinite;
}
.ldr-basketball-y {
position: absolute;
inset: 0;
animation: ldr-basketball-y 2.4s infinite;
}
.ldr-basketball-ball {
position: absolute;
inset: 0;
border-radius: 50%;
background:
linear-gradient(90deg, transparent calc(50% - var(--s) / 70), var(--line) 0 calc(50% + var(--s) / 70), transparent 0),
radial-gradient(circle at -30% 50%, transparent calc(var(--s) * 0.132), var(--line) calc(var(--s) * 0.137) calc(var(--s) * 0.152), transparent calc(var(--s) * 0.157)),
radial-gradient(circle at 130% 50%, transparent calc(var(--s) * 0.132), var(--line) calc(var(--s) * 0.137) calc(var(--s) * 0.152), transparent calc(var(--s) * 0.157)),
var(--c);
--line: color-mix(in srgb, var(--c) 40%, #000);
animation: ldr-basketball-spin 1.2s linear infinite;
}
@keyframes ldr-basketball-x {
0% { transform: translateX(0); opacity: 0; animation-timing-function: cubic-bezier(0.3, 0.1, 0.6, 1); }
5% { opacity: 1; }
38%, 84% { transform: translateX(calc(var(--s) * 1)); opacity: 1; }
92% { transform: translateX(calc(var(--s) * 1)); opacity: 0; }
100% { transform: translateX(0); opacity: 0; }
}
@keyframes ldr-basketball-y {
0% { transform: translateY(0); animation-timing-function: cubic-bezier(0.2, 0.6, 0.35, 1); }
21% { transform: translateY(calc(var(--s) * -0.72)); animation-timing-function: cubic-bezier(0.55, 0, 0.9, 0.5); }
38% { transform: translateY(calc(var(--s) * -0.57)); animation-timing-function: cubic-bezier(0.4, 0.3, 0.85, 0.6); }
52% { transform: translateY(0); animation-timing-function: cubic-bezier(0.2, 0.6, 0.35, 1); }
61% { transform: translateY(calc(var(--s) * -0.16)); animation-timing-function: cubic-bezier(0.6, 0, 0.8, 0.4); }
70% { transform: translateY(0); animation-timing-function: cubic-bezier(0.2, 0.6, 0.35, 1); }
75% { transform: translateY(calc(var(--s) * -0.04)); animation-timing-function: cubic-bezier(0.6, 0, 0.8, 0.4); }
80%, 100% { transform: translateY(0); }
}
@keyframes ldr-basketball-spin {
to { transform: rotate(-360deg); }
}
@keyframes ldr-basketball-swish {
0%, 38% { transform: scale(1, 1); }
45% { transform: scale(0.9, 1.3); }
54% { transform: scale(1.04, 0.92); }
62%, 100% { transform: scale(1, 1); }
}
@media (prefers-reduced-motion: reduce) {
.ldr-basketball, .ldr-basketball *, .ldr-basketball::before, .ldr-basketball::after, .ldr-basketball *::before, .ldr-basketball *::after {
animation-duration: 3s;
}
}--ldr-color on the element or a parent.
Currently previewing #3b82f6.--ldr-size. Every dimension in the snippet is
derived from it. Currently previewing 64px.animation shorthand.
Delays on the children are relative, so they keep their rhythm.role="status" and an
aria-label so screen readers announce the wait. The reduced-motion rule
at the end slows the animation for users who asked for less motion.