AI CSS Animations

Basketball Hoop Loader

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.

HTML

<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>

CSS

.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;
  }
}

Customizing this loader

  • Color: set --ldr-color on the element or a parent. Currently previewing #3b82f6.
  • Size: set --ldr-size. Every dimension in the snippet is derived from it. Currently previewing 64px.
  • Speed: change the duration on the animation shorthand. Delays on the children are relative, so they keep their rhythm.
  • Accessibility: the root carries 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.

Similar loaders