DEV Community

Cover image for The Annoyance That Started This
Sonny kk
Sonny kk

Posted on

The Annoyance That Started This

The Annoyance That Started This

Someone in a Discord I'm in asked for a way to spam "Thank You" a hundred times into a graduation card thread — one of those goofy walls of repeated text people paste into chats. I figured this would take thirty seconds to find online.

It took ten minutes, and every result I clicked was the same story: three ad networks loading before the input box, a "generate" button that triggered a full page reload, and one site that flat out froze my phone's tab when I pushed the count past a couple thousand. For a tool whose entire job is repeating a string, that's embarrassing.

So instead of complaining in the Discord, I built the thing myself. No framework, no build step, no backend. Just static HTML, CSS, and JS that does one job well.

Letting CSS Do the Layout Work

I wanted two display modes — a centered block for short messages, and a grid layout for scanning big counts. It's tempting to reach for JS to calculate column counts and positions, but that's exactly the kind of work the browser already does better:

.grid-view {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
  gap: 6px;
}
Enter fullscreen mode Exit fullscreen mode

auto-fill plus minmax means the grid reflows itself at any viewport width without a single resize listener. I poked at this quite a bit while dialing in the responsive behavior on the live build — you can see the grid mode in action on the Thank you 100 times if you want to try breaking it on a narrow screen. It holds up down to phone width without any JS recalculating positions.

Don't Let the Loop Be the Bottleneck

My first instinct, like most people's, was a plain for loop with string concatenation:

let out = '';
for (let i = 0; i < count; i++) out += text + '\n';
Enter fullscreen mode Exit fullscreen mode

This is fine until it isn't. Strings in JS are immutable, so every += allocates a new string and copies everything that came before it into that new string. At a hundred repeats, nobody notices. At ten thousand, you're doing a lot of redundant copying, and the garbage collector has to clean up every discarded intermediate string along the way.

Swapping to an array build and a single join fixes this outright:

const lines = Array.from({ length: count }, (_, i) =>
  numbered ? `${i + 1}. ${text}` : text
);
const output = lines.join(separator);
Enter fullscreen mode Exit fullscreen mode

One allocation for the array, one pass to join it. On a throwaway benchmark on my machine, ten thousand iterations went from a visible hitch to something that finishes before the next frame paints.

Export Without Touching a Server

Two things people actually want from a tool like this: copy to clipboard, and a downloadable file. Both are pure browser APIs.

Clipboard write has to happen synchronously inside the click handler, or some browsers (Safari especially) will quietly refuse it since it's no longer tied to a direct user gesture:

button.addEventListener('click', () => {
  navigator.clipboard.writeText(output);
});
Enter fullscreen mode Exit fullscreen mode

For the file download, Blob plus an object URL does the whole job with no server involved:

const blob = new Blob([output], { type: 'text/plain' });
const link = Object.assign(document.createElement('a'), {
  href: URL.createObjectURL(blob),
  download: 'repeated-text.txt'
});
link.click();
URL.revokeObjectURL(link.href);

Enter fullscreen mode Exit fullscreen mode

That's the entire export pipeline. No route, no storage bucket, no file ever leaving the user's machine.

What I Took Away From This

The finished tool is a handful of KB, loads instantly, and scores 100 on Lighthouse without trying particularly hard, mostly because there's nothing on the page to slow it down. There's no lesson here about some clever new API — it's array-based string building over concatenation, native CSS Grid over manual positioning, Blob URLs over a server round-trip. All old techniques. The only reason they stood out is that so few of the existing tools bothered using them.

If anyone's dealt with a nastier edge case scaling client-side string generation, I'd genuinely like to hear it — curious what I'm missing.

Top comments (0)