A laser line sweeps up and down a barcode framed by viewfinder corners. Good for scan, lookup and checkout states. The stripes are one hard-stop gradient and the corners are eight more.
Also known as: barcode scan animation, scanning loader, laser scan css.
<div class="ldr-barcode-scanner" role="status" aria-label="Scanning"> <span class="ldr-barcode-scanner-laser"></span> </div>
.ldr-barcode-scanner {
--s: var(--ldr-size, 48px);
--c: var(--ldr-color, #3b82f6);
--k: color-mix(in srgb, currentColor 72%, transparent);
--l: calc(var(--s) / 4);
--t: calc(var(--s) / 16);
position: relative;
width: calc(var(--s) * 1.5);
height: var(--s);
}
.ldr-barcode-scanner::before {
content: "";
position: absolute;
inset: calc(var(--s) / 6) calc(var(--s) / 5);
background: linear-gradient(90deg,
var(--k) 0 4%, transparent 0 7%, var(--k) 0 9%, transparent 0 13%,
var(--k) 0 19%, transparent 0 22%, var(--k) 0 24%, transparent 0 29%,
var(--k) 0 31%, transparent 0 33%, var(--k) 0 39%, transparent 0 43%,
var(--k) 0 45%, transparent 0 48%, var(--k) 0 54%, transparent 0 56%,
var(--k) 0 58%, transparent 0 63%, var(--k) 0 67%, transparent 0 69%,
var(--k) 0 71%, transparent 0 75%, var(--k) 0 81%, transparent 0 84%,
var(--k) 0 86%, transparent 0 90%, var(--k) 0 92%, transparent 0 95%,
var(--k) 0 100%);
}
.ldr-barcode-scanner::after {
content: "";
position: absolute;
inset: 0;
background:
linear-gradient(var(--c) 0 0) 0 0 / var(--l) var(--t),
linear-gradient(var(--c) 0 0) 0 0 / var(--t) var(--l),
linear-gradient(var(--c) 0 0) 100% 0 / var(--l) var(--t),
linear-gradient(var(--c) 0 0) 100% 0 / var(--t) var(--l),
linear-gradient(var(--c) 0 0) 0 100% / var(--l) var(--t),
linear-gradient(var(--c) 0 0) 0 100% / var(--t) var(--l),
linear-gradient(var(--c) 0 0) 100% 100% / var(--l) var(--t),
linear-gradient(var(--c) 0 0) 100% 100% / var(--t) var(--l);
background-repeat: no-repeat;
}
.ldr-barcode-scanner-laser {
position: absolute;
z-index: 1;
top: 0;
left: calc(var(--s) / 10);
right: calc(var(--s) / 10);
height: calc(var(--s) / 18);
border-radius: var(--s);
background: var(--c);
box-shadow: 0 0 calc(var(--s) / 6) calc(var(--s) / 40) var(--c);
animation: ldr-barcode-scanner-sweep 1.6s cubic-bezier(0.45, 0, 0.55, 1) infinite;
}
@keyframes ldr-barcode-scanner-sweep {
0%, 100% { transform: translateY(calc(var(--s) * 0.14)); }
50% { transform: translateY(calc(var(--s) * 0.8)); }
}
@media (prefers-reduced-motion: reduce) {
.ldr-barcode-scanner, .ldr-barcode-scanner *, .ldr-barcode-scanner::before, .ldr-barcode-scanner::after, .ldr-barcode-scanner *::before, .ldr-barcode-scanner *::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.