AI CSS Animations

Pencil Drawing Loader

A pencil writes a wavy line across the card, lifts, and goes back to start while the line fades. The tip tracks the SVG stroke exactly, because both run on the same linear timeline.

Also known as: pencil writing animation, drawing loader css, sketch loader.

HTML

<div class="ldr-pencil" role="status" aria-label="Loading">
  <svg viewBox="0 0 60 40" aria-hidden="true">
    <path pathLength="1" d="M6 30c2-4 4-4 6 0s4 4 6 0 4-4 6 0 4 4 6 0 4-4 6 0 4 4 6 0"/>
  </svg>
  <span class="ldr-pencil-move"><span class="ldr-pencil-bob"><span class="ldr-pencil-body"></span></span></span>
</div>

CSS

.ldr-pencil {
  --s: var(--ldr-size, 48px);
  --c: var(--ldr-color, #3b82f6);
  --wave: calc(var(--s) * 0.075);
  position: relative;
  width: calc(var(--s) * 1.5);
  height: var(--s);
}
.ldr-pencil svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  fill: none;
  stroke: var(--c);
  stroke-width: 2.2;
  stroke-linecap: round;
}
.ldr-pencil path {
  stroke-dasharray: 1 2;
  stroke-dashoffset: 1;
  animation: ldr-pencil-draw 3s linear infinite;
}
.ldr-pencil-move {
  position: absolute;
  left: calc(var(--s) * 0.15);
  top: calc(var(--s) * 0.75);
  animation: ldr-pencil-move 3s infinite;
}
.ldr-pencil-bob {
  position: absolute;
  animation: ldr-pencil-bob 3s infinite;
}
.ldr-pencil-body {
  position: absolute;
  left: calc(var(--s) / -16);
  bottom: 0;
  width: calc(var(--s) / 8);
  height: calc(var(--s) * 0.52);
  background: linear-gradient(to top,
    color-mix(in srgb, var(--c) 40%, #000) 0 8%,
    color-mix(in srgb, var(--c) 22%, #fff) 8% 23%,
    var(--c) 23% 80%,
    color-mix(in srgb, var(--c) 45%, #fff) 80% 86%,
    color-mix(in srgb, var(--c) 60%, #fff) 86%);
  clip-path: polygon(0 0, 100% 0, 100% 77%, 50% 100%, 0 77%);
  transform: rotate(40deg);
  transform-origin: 50% 100%;
}
@keyframes ldr-pencil-draw {
  0%        { stroke-dashoffset: 1; opacity: 1; }
  50%, 70%  { stroke-dashoffset: 0; opacity: 1; }
  84%, 100% { stroke-dashoffset: 0; opacity: 0; }
}
@keyframes ldr-pencil-move {
  0%   { transform: translate(0, 0); animation-timing-function: linear; }
  50%  { transform: translate(calc(var(--s) * 0.9), 0); animation-timing-function: cubic-bezier(0.2, 0.7, 0.3, 1); }
  56%  { transform: translate(calc(var(--s) * 0.92), calc(var(--s) * -0.12)); animation-timing-function: cubic-bezier(0.6, 0, 0.3, 1); }
  86%  { transform: translate(0, calc(var(--s) * -0.12)); animation-timing-function: cubic-bezier(0.5, 0, 0.5, 1); }
  94%, 100% { transform: translate(0, 0); }
}
@keyframes ldr-pencil-bob {
  0%, 8.333%, 16.667%, 25%, 33.333%, 41.667% {
    transform: translateY(0);
    animation-timing-function: cubic-bezier(0.33, 0.66, 0.66, 1);
  }
  4.167%, 20.833%, 37.5% {
    transform: translateY(calc(var(--wave) * -1));
    animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.34);
  }
  12.5%, 29.167%, 45.833% {
    transform: translateY(var(--wave));
    animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.34);
  }
  50%, 100% { transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  .ldr-pencil, .ldr-pencil *, .ldr-pencil::before, .ldr-pencil::after, .ldr-pencil *::before, .ldr-pencil *::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