Search GIFs · Caption with Impact · Download the result.
A minimalistic, modern, fully client-side GIF captioning studio.
Demo · Quick Start · Features · How It Works · API Keys · Roadmap · License
|
Search. Unified Giphy + Klipy grid with hover previews and source toggle. |
Caption. Live Impact preview, top/bottom toggle, 1–300px size, one-click render. |
TextaGIF is a tiny, opinionated meme studio. Search Giphy and Klipy, pick a GIF, slap an
Impact caption on the top or bottom at any size from 1–300 pixels, and the tool burns the text
into every frame and hands you back a fresh .gif.
No uploads. No server. No tracking. The entire render pipeline — decode, composite, re-encode — runs in your browser.
| Giphy + Klipy, unified results grid with hover previews. | Impact font, auto-wrap, white fill + black stroke, live SVG preview. |
Per-frame render with gifuct-js + gifenc, 100% client-side.
|
| Multi-source search | Giphy and Klipy, toggleable in one click. |
| Live caption preview | SVG overlay scales exactly with the displayed GIF so what you see is what you get. |
| Top / Bottom positioning | Classic meme format, one tap to flip. |
| Font size 1–300 | Slider + numeric input. Use 1 for subtle signatures or 300 for screen-smashers. |
| Auto word-wrap | Captions wrap on word boundaries with padding-aware margins. |
| Impact, always | Bold Impact with black stroke — the true language of memes. |
| Fully client-side | Nothing is uploaded. The generated GIF is assembled in your browser. |
| Minimalist UI | Dark, typographic, no clutter. Sticky search header, keyboard-friendly, mobile-friendly. |
| Zero tracking | No analytics. No cookies. No accounts. |
# 1. Install
npm install
# 2. Add API keys
cp .env.example .env.local
# open .env.local and paste at least one key (see below)
# 3. Run it
npm run devOpen http://localhost:5173 and you're in.
You need at least one. Both are free.
| Provider | Get a key | Env var |
|---|---|---|
| Giphy |
developers.giphy.com → Create an App → pick API (not SDK) |
VITE_GIPHY_KEY |
| Klipy |
business.klipy.com → Create API Key |
VITE_KLIPY_KEY |
Tenor is no longer accepting new API clients as of January 2026, so TextaGIF uses Klipy as its second provider.
flowchart LR
A[Search query] --> B{Source}
B -->|Giphy| C[Giphy REST API]
B -->|Klipy| D[Klipy REST API]
C --> E[Grid of previews]
D --> E
E --> F[Select a GIF]
F --> G[Fetch original bytes]
G --> H[Decode frames<br/>gifuct-js]
H --> I[Composite + draw caption<br/>Canvas 2D + Impact]
I --> J[Quantize + encode<br/>gifenc]
J --> K[Download .gif]
The render path (src/lib/gifProcessor.ts):
- Fetch the original GIF as an
ArrayBuffer. - Parse + decompress each frame with
gifuct-js, respecting disposal modes (restore, clear, keep). - Composite the frame patch onto a running canvas.
- Overlay the caption — Impact, white fill, proportional black stroke, word-wrapped to frame width.
- Quantize frame pixels to a 256-color palette (
gifencquantize) and encode with per-frame delays. - Save via a blob download. Snapshot is restored so subsequent frames don't see baked-in text.
|
Frontend
|
GIF pipeline |
Sources |
TextaGIF/
├── src/
│ ├── components/
│ │ ├── SearchBar.tsx # search input + source toggle
│ │ ├── GifGrid.tsx # results grid + skeleton + empty states
│ │ └── Editor.tsx # preview + caption controls + download
│ ├── lib/
│ │ ├── api.ts # Giphy + Klipy clients, typed responses
│ │ └── gifProcessor.ts # decode → caption → re-encode pipeline
│ ├── App.tsx # shell, routing between grid and editor
│ ├── main.tsx # React root
│ ├── types.ts # shared types (GifItem, CaptionSettings…)
│ └── index.css # Tailwind + base styles
├── docs/
│ └── banner.svg # README header
├── index.html
├── tailwind.config.js
├── vite.config.ts
├── tsconfig.json
└── package.json
| Command | What it does |
|---|---|
npm run dev |
Start the Vite dev server on :5173 with HMR. |
npm run build |
Type-check and produce an optimized production build in dist/. |
npm run preview |
Serve the production build locally. |
Environment variables
| Variable | Required | Purpose |
|---|---|---|
VITE_GIPHY_KEY |
one-of | Giphy REST API key |
VITE_KLIPY_KEY |
one-of | Klipy REST API key |
At least one must be set. Unconfigured providers are greyed out in the source toggle.
Tweaking caption style
Open src/lib/gifProcessor.ts and edit drawCaption. The defaults:
ctx.fillStyle = "#ffffff";
ctx.strokeStyle = "#000000";
ctx.lineWidth = Math.max(2, fontSize / 14);Swap in a different color, gradient, or shadow. The live preview in Editor.tsx mirrors
these values in SVG — update both so what you see matches what you download.
Adding a third source
- Extend
GifSourceinsrc/types.ts. - Add a
searchXxxfunction insrc/lib/api.tsreturningGifItem[]. - Route it in
searchGifsand add anisXxxConfiguredexport. - Wire the toggle in
SearchBar.tsxand the setup banner inApp.tsx.
The search surface is tiny on purpose — each provider is ~30 lines.
- Giphy + Klipy search
- Impact caption (top / bottom, 1–300px)
- Live preview
- Client-side GIF re-encode
- Multi-line manual breaks
- Custom fill / stroke colors
- Drag-to-position caption (freeform)
- Trim / crop / speed adjust
- Upload-your-own-GIF source
Got an idea? Open an issue.
Pull requests are welcome. For bigger changes, open an issue first to chat about direction.
git clone <your-fork>
cd TextaGIF
npm install
npm run devPlease keep the UI minimalistic. Fewer buttons. Less chrome. More Impact.
- gifuct-js — the GIF decoder that makes frame-accurate compositing possible.
- gifenc — a wonderfully small, fast GIF encoder.
- Giphy and Klipy — for the endless supply of reactions.
- shields.io — badges above.
MIT © TextaGIF contributors.
Built with Impact · Captioned with care.