Like a real typewriter, the paper slides left one character per keystroke, then the carriage returns and feeds a new line. The paper's transform uses the same steps() as the typing.
Also known as: realistic typewriter animation, moving paper typewriter, typewriter line break css.
<div class="txt-typewriter-carriage-return" aria-label="CHAPTER ONE. It was late.">
<div class="txt-typewriter-carriage-return-paper" aria-hidden="true">
<span class="txt-typewriter-carriage-return-line">CHAPTER ONE</span>
<span class="txt-typewriter-carriage-return-line">It was late.</span>
</div>
</div>.txt-typewriter-carriage-return {
--c: var(--txt-color, #3b82f6);
font-size: var(--txt-size, 32px);
font-family: system-ui, sans-serif;
font-weight: 700;
line-height: 1.2;
position: relative;
width: 24ch;
height: 3.45em;
overflow: hidden;
font-family: "Courier New", Courier, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-weight: 700;
line-height: 1.3;
text-align: left;
-webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);}
/* the scale under the typing line: a tick every character, a long one every five */
.txt-typewriter-carriage-return::before {
content: "";
position: absolute;
left: 0;
right: 0;
top: 2.8em;
height: 0.3em;
background:
repeating-linear-gradient(90deg, color-mix(in srgb, currentColor 45%, transparent) 0 1px, transparent 1px 5ch) top / 100% 100% no-repeat,
repeating-linear-gradient(90deg, color-mix(in srgb, currentColor 22%, transparent) 0 1px, transparent 1px 1ch) top / 100% 50% no-repeat;
}
/* the typing point, which rings at the end of a line */
.txt-typewriter-carriage-return::after {
content: "";
position: absolute;
left: 18.5ch;
top: 3.12em;
width: 0.3em;
height: 0.3em;
margin-left: -0.15em;
background: var(--c);
clip-path: polygon(50% 0, 100% 100%, 0 100%);
animation: txt-typewriter-carriage-return-ding 0.4s ease-out 1.4s;
}
/* the paper: its left edge starts at the typing point */
.txt-typewriter-carriage-return-paper {
position: absolute;
top: 1.45em;
left: 18ch;
animation: txt-typewriter-carriage-return-carriage 3.7s linear forwards;
}
.txt-typewriter-carriage-return-line {
display: block;
width: 0;
overflow: hidden;
white-space: pre;
animation: txt-typewriter-carriage-return-a 1.1s steps(11) 0.3s forwards;
}
.txt-typewriter-carriage-return-line + .txt-typewriter-carriage-return-line {
color: var(--c);
animation: txt-typewriter-carriage-return-b 1.2s steps(12) 2.5s forwards;
}
@keyframes txt-typewriter-carriage-return-a {
99.9%, to { width: 11ch; }
}
@keyframes txt-typewriter-carriage-return-b {
99.9%, to { width: 12ch; }
}
/* type (paper steps left), ding, return and line feed, type again */
@keyframes txt-typewriter-carriage-return-carriage {
0%, 8.11% { transform: translate(0, 0); animation-timing-function: steps(11); }
37.84%, 47.3% { transform: translate(-11ch, 0); animation-timing-function: cubic-bezier(0.6, 0, 0.2, 1); }
60.81%, 67.57% { transform: translate(0, -1.3em); animation-timing-function: steps(12); }
99.9%, 100% { transform: translate(-12ch, -1.3em); }
}
@keyframes txt-typewriter-carriage-return-ding {
40% { transform: translateY(-0.12em) scale(1.7); }
}
@media (prefers-reduced-motion: reduce) {
.txt-typewriter-carriage-return, .txt-typewriter-carriage-return *, .txt-typewriter-carriage-return::before, .txt-typewriter-carriage-return::after, .txt-typewriter-carriage-return *::before, .txt-typewriter-carriage-return *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}--txt-color on the element or a parent.
Currently previewing #3b82f6.--txt-size. Every other dimension is in
em, so the whole thing scales together. Currently previewing 40px.transition or animation
shorthand. Staggered delays are relative, so they keep their rhythm.