DEV Community

Cover image for ASCII progress indicators
Frank Fiegel
Frank Fiegel

Posted on • Originally published at glama.ai

ASCII progress indicators

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:

ASCII progress indicators

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

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 useInterval = (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);

  useInterval(() => {
    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>
  );
};
Enter fullscreen mode Exit fullscreen mode

Heroku

Build apps, not infrastructure.

Dealing with servers, hardware, and infrastructure can take up your valuable time. Discover the benefits of Heroku, the PaaS of choice for developers since 2007.

Visit Site

Top comments (0)

The best way to debug slow web pages cover image

The best way to debug slow web pages

Tools like Page Speed Insights and Google Lighthouse are great for providing advice for front end performance issues. But what these tools can’t do, is evaluate performance across your entire stack of distributed services and applications.

Watch video