ASCII progress indicators

Written by on .

react
ascii
open source

  1. Throbbers

    I have always been fascinated by old terminal interfaces and wanted to bring some of their charm to the Glama chat UI. To indicate the progress of AI responses, I use ASCII progress indicators.

    The animations are inspired by these two articles:

    I transformed the concept into a React component.

    Below is a collection of progress indicators:

    𓃉𓃉𓃉
    ×
    🕛
    🌍
    🌑
    😐
    💣

    Each animation is a sequence of characters that rotate at a set interval. For example, the characters ⣾, ⣽, ⣻, ⢿, ⡿, ⣟, ⣯, ⣷ become

    .

    Here is the component code:

    import { useEffect, useRef, useState } from 'react'; type ProgressIndicatorStyle = { frames: string[]; interval: number; }; const styles = { arrow: { frames: ['↑', '↗', '→', '↘', '↓', '↙', '←', '↖'], interval: 100, }, ball_wave: { frames: ['𓃉𓃉𓃉', '𓃉𓃉∘', '𓃉∘°', '∘°∘', '°∘𓃉', '∘𓃉𓃉'], interval: 100, }, blocks1: { frames: ['░', '▒', '▓', '█'], interval: 100, }, blocks2: { frames: ['▛','▜','▟','▙'], interval: 100, }, cym: { frames: ['⊏', '⊓', '⊐', '⊔'], interval: 100, }, dots1: { frames: ['⣾', '⣽', '⣻', '⢿', '⡿', '⣟', '⣯', '⣷'], interval: 50, }, dots2: { frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'], interval: 50, }, dots3: { frames: ['⠋', '⠙', '⠚', '⠞', '⠖', '⠦', '⠴', '⠲', '⠳', '⠓'], interval: 50, }, dots4: { frames: [ '⠄', '⠆', '⠇', '⠋', '⠙', '⠸', '⠰', '⠠', '⠰', '⠸', '⠙', '⠋', '⠇', '⠆', ], interval: 50, }, dots5: { frames: [ '⠋', '⠙', '⠚', '⠒', '⠂', '⠂', '⠒', '⠲', '⠴', '⠦', '⠖', '⠒', '⠐', '⠐', '⠒', '⠓', '⠋', ], interval: 50, }, dots6: { frames: [ '⠁', '⠉', '⠙', '⠚', '⠒', '⠂', '⠂', '⠒', '⠲', '⠴', '⠤', '⠄', '⠄', '⠤', '⠴', '⠲', '⠒', '⠂', '⠂', '⠒', '⠚', '⠙', '⠉', '⠁', ], interval: 50, }, dots7: { frames: [ '⠈', '⠉', '⠋', '⠓', '⠒', '⠐', '⠐', '⠒', '⠖', '⠦', '⠤', '⠠', '⠠', '⠤', '⠦', '⠖', '⠒', '⠐', '⠐', '⠒', '⠓', '⠋', '⠉', '⠈', ], interval: 50, }, dots8: { frames: [ '⠁', '⠁', '⠉', '⠙', '⠚', '⠒', '⠂', '⠂', '⠒', '⠲', '⠴', '⠤', '⠄', '⠄', '⠤', '⠠', '⠠', '⠤', '⠦', '⠖', '⠒', '⠐', '⠐', '⠒', '⠓', '⠋', '⠉', '⠈', '⠈', ], interval: 50, }, dots9: { frames: ['⢹', '⢺', '⢼', '⣸', '⣇', '⡧', '⡗', '⡏'], interval: 50, }, dots10: { frames: ['⢄', '⢂', '⢁', '⡁', '⡈', '⡐', '⡠'], interval: 50, }, dots11: { frames: ['⠁', '⠂', '⠄', '⡀', '⢀', '⠠', '⠐', '⠈'], interval: 50, }, emoji_blink: { frames: ['😐', '😐', '😐', '😐', '😐', '😐', '😐', '😐', '😐', '😐', '😑'], interval: 100, }, emoji_bomb: { frames: [ '💣 ', ' 💣 ', ' 💣 ', ' 💣', ' 💣', ' 💣', ' 💣', ' 💣', ' 💥', ' ', ' ', ], interval: 100, }, emoji_earth: { frames: ['🌍', '🌎', '🌏'], interval: 200, }, emoji_hour: { frames: [ '🕛', '🕐', '🕑', '🕒', '🕓', '🕔', '🕕', '🕖', '🕗', '🕘', '🕙', '🕚', ], interval: 100, }, emoji_moon: { frames: ['🌑', '🌒', '🌓', '🌔', '🌕', '🌖', '🌗', '🌘'], interval: 200, }, line: { frames: ['☰', '☱', '☳', '☷', '☶', '☴'], interval: 100, }, old: { frames: ['—', '\\', '|', '/'], interval: 100, }, x_plus: { frames: ['×', '+'], interval: 100, }, } satisfies Record<string, ProgressIndicatorStyle>; const useRafInterval = (callback: () => void, delay: null | number) => { const savedCallback = useRef(callback); useEffect(() => { savedCallback.current = callback; }, [callback]); useEffect(() => { if (delay === null) { return undefined; } const id = setInterval(() => savedCallback.current(), delay); return () => { clearInterval(id); }; }, [delay]); }; export const ProgressIndicator = ({ style, }: { style: keyof typeof styles; }) => { const { frames, interval } = styles[style]; if (!style) { throw new Error('Invalid style index'); } const [index, setIndex] = useState<number>(0); useRafInterval(() => { setIndex((index + 1) % frames.length); }, interval); return ( <div style={{ color: '#00d992', fontFamily: 'monospace', pointerEvents: 'none', textAlign: 'center', userSelect: 'none', whiteSpace: 'pre', width: '24px', }} > {frames[index]} </div> ); };

    Throbbers

    A user on Reddit pointed out that these in fact are called "throbbers".

    https://en.wikipedia.org/wiki/Throbber

    Written by Frank Fiegel (@punkpeye)