<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Vijay Kanna</title>
    <description>The latest articles on DEV Community by Vijay Kanna (@vijay_kanna_56).</description>
    <link>https://dev.to/vijay_kanna_56</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4018837%2F6d97b197-f3dc-4d79-a451-6ad25ebc8746.jpg</url>
      <title>DEV Community: Vijay Kanna</title>
      <link>https://dev.to/vijay_kanna_56</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vijay_kanna_56"/>
    <language>en</language>
    <item>
      <title>Why Your SVGs Are Bigger Than They Need to Be (And What's Actually Bloating Them)</title>
      <dc:creator>Vijay Kanna</dc:creator>
      <pubDate>Tue, 28 Jul 2026 02:29:34 +0000</pubDate>
      <link>https://dev.to/vijay_kanna_56/why-your-svgs-are-bigger-than-they-need-to-be-and-whats-actually-bloating-them-34aa</link>
      <guid>https://dev.to/vijay_kanna_56/why-your-svgs-are-bigger-than-they-need-to-be-and-whats-actually-bloating-them-34aa</guid>
      <description>&lt;p&gt;Export an icon from Figma or Illustrator and you'll usually get an SVG that's 3–5x larger than it needs to be. Not because SVG is inherently bloated — it's a genuinely efficient format — but because design tools leave a trail of editor metadata, redundant precision, and unused structure that never gets cleaned up before it ships to production.&lt;/p&gt;

&lt;p&gt;Here's what's actually taking up the space, and why "just minify it" is doing more specific work than that phrase suggests.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Actually in a "Bloated" SVG
&lt;/h2&gt;

&lt;p&gt;Take a simple two-circle icon — the kind of thing that should realistically be under 200 bytes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;svg&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2000/svg"&lt;/span&gt; &lt;span class="na"&gt;viewBox=&lt;/span&gt;&lt;span class="s"&gt;"0 0 100 100"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"100"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"100"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- Author: Design Tool Export --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;g&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"Background"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;rect&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"100"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"100"&lt;/span&gt; &lt;span class="na"&gt;fill=&lt;/span&gt;&lt;span class="s"&gt;"#f5f3ff"&lt;/span&gt; &lt;span class="na"&gt;rx=&lt;/span&gt;&lt;span class="s"&gt;"15"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/g&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;g&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"AccentCircle"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;circle&lt;/span&gt; &lt;span class="na"&gt;cx=&lt;/span&gt;&lt;span class="s"&gt;"50"&lt;/span&gt; &lt;span class="na"&gt;cy=&lt;/span&gt;&lt;span class="s"&gt;"50"&lt;/span&gt; &lt;span class="na"&gt;r=&lt;/span&gt;&lt;span class="s"&gt;"30"&lt;/span&gt; &lt;span class="na"&gt;fill=&lt;/span&gt;&lt;span class="s"&gt;"#6366f1"&lt;/span&gt; &lt;span class="na"&gt;opacity=&lt;/span&gt;&lt;span class="s"&gt;"0.15"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;circle&lt;/span&gt; &lt;span class="na"&gt;cx=&lt;/span&gt;&lt;span class="s"&gt;"50"&lt;/span&gt; &lt;span class="na"&gt;cy=&lt;/span&gt;&lt;span class="s"&gt;"50"&lt;/span&gt; &lt;span class="na"&gt;r=&lt;/span&gt;&lt;span class="s"&gt;"20"&lt;/span&gt; &lt;span class="na"&gt;fill=&lt;/span&gt;&lt;span class="s"&gt;"#4f46e5"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/g&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exported directly from a design tool, this file often comes out at 350–400+ bytes before any cleanup. Here's where that goes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. XML comments.&lt;/strong&gt; &lt;code&gt;&amp;lt;!-- Author: Design Tool Export --&amp;gt;&lt;/code&gt; and similar auto-generated comments are pure dead weight — they exist for the editor's internal bookkeeping, not for rendering, and browsers ignore them entirely at runtime while still paying the download cost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Editor metadata and redundant namespaces.&lt;/strong&gt; Design tools often emit extra namespace declarations (&lt;code&gt;xmlns:xlink&lt;/code&gt;, editor-specific attributes) that aren't used anywhere in the actual markup — vestigial from the export process, not required by the SVG spec for this content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Unnecessary IDs and grouping.&lt;/strong&gt; &lt;code&gt;id="Background"&lt;/code&gt; and &lt;code&gt;id="AccentCircle"&lt;/code&gt; are meaningful to a human editing the file in a design tool, completely irrelevant to a browser rendering it, unless something in your code actually references those IDs (via CSS, JS, or &lt;code&gt;&amp;lt;use&amp;gt;&lt;/code&gt;). If nothing does, they're pure overhead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Whitespace and formatting.&lt;/strong&gt; Indentation, line breaks, and spacing make the file readable for humans and add literally zero rendering value — this is the most "free" savings available, since it changes nothing about how the SVG looks or behaves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Excessive path precision.&lt;/strong&gt; Not visible in this example, but extremely common in path-heavy icons: coordinates like &lt;code&gt;d="M12.000123 8.499987..."&lt;/code&gt; where 6+ decimal places of precision exist for a shape that renders identically at 2 decimal places, because that's just what the export tool defaulted to.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Safe Minification Actually Removes
&lt;/h2&gt;

&lt;p&gt;The key word is &lt;em&gt;safe&lt;/em&gt; — some "optimizations" genuinely change rendering (color space conversion, aggressive path simplification that alters shape), and some are risk-free because they touch nothing a renderer uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Strip comments — zero risk, comments are never rendered&lt;/li&gt;
&lt;li&gt;Remove unused XML headers and editor-specific metadata namespaces — zero risk if nothing references them&lt;/li&gt;
&lt;li&gt;Minify whitespace (collapse indentation and line breaks) — zero risk, purely cosmetic in the source&lt;/li&gt;
&lt;li&gt;Remove IDs &lt;em&gt;only if nothing in your codebase references them&lt;/em&gt; — this one requires actually checking, not assuming&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the example above, comments + editor metadata + whitespace minification alone gets that file from ~375 bytes down to roughly 300 — an 18–20% reduction with zero visual risk, before even touching path precision or considering whether those IDs are actually used anywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where This Actually Matters
&lt;/h2&gt;

&lt;p&gt;For a single icon, 75 bytes doesn't matter. It matters when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You're inlining dozens of SVG icons directly in HTML/JSX (a common pattern for icon systems) — the savings compound across every instance&lt;/li&gt;
&lt;li&gt;You're building an icon sprite sheet referenced across an entire app&lt;/li&gt;
&lt;li&gt;SVGs are part of your critical rendering path (above-the-fold logos, hero graphics) where every KB affects Largest Contentful Paint&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A Note on React/JSX Specifically
&lt;/h2&gt;

&lt;p&gt;If you're inlining SVGs as React components, there's a second layer worth knowing about: SVG attribute names don't match JSX conventions directly — &lt;code&gt;stroke-width&lt;/code&gt; needs to become &lt;code&gt;strokeWidth&lt;/code&gt;, &lt;code&gt;xlink:href&lt;/code&gt; needs handling as a namespaced prop, and self-closing tags need to actually be self-closed for JSX to parse them. A tool that exports both optimized SVG markup &lt;em&gt;and&lt;/em&gt; a JSX-ready version saves that manual conversion step, which is easy to get wrong by hand on a complex path-heavy icon.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;I added an SVG optimization tool to &lt;a href="https://resizehub.in/tools/svg-optimizer" rel="noopener noreferrer"&gt;ResizeHub&lt;/a&gt; that runs this cleanup entirely client-side — paste or upload an SVG, toggle which minification rules to apply, and export either the cleaned markup or a JSX-ready version directly. No upload, processed in your browser.&lt;/p&gt;

&lt;p&gt;What's the worst SVG bloat you've run into — design-tool exports, or something else entirely (icon font migrations, hand-written SVGs with copy-pasted boilerplate)?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>css</category>
      <category>performance</category>
    </item>
    <item>
      <title>The 50KB Problem: Why Government Forms Keep Rejecting Your Photo</title>
      <dc:creator>Vijay Kanna</dc:creator>
      <pubDate>Sun, 26 Jul 2026 15:36:18 +0000</pubDate>
      <link>https://dev.to/vijay_kanna_56/the-50kb-problem-why-government-forms-keep-rejecting-your-photo-1hfa</link>
      <guid>https://dev.to/vijay_kanna_56/the-50kb-problem-why-government-forms-keep-rejecting-your-photo-1hfa</guid>
      <description>&lt;p&gt;There's a deceptively simple bug hiding in plain sight on almost every government form, university portal, and job application site: "Upload a photo under 50KB." No API, no error message explaining why, no tolerance — just silent rejection if you're 2KB over.&lt;/p&gt;

&lt;p&gt;It sounds like a trivial constraint until you actually try to satisfy it programmatically. File size in bytes isn't a variable you can set directly; it's a &lt;em&gt;derived&lt;/em&gt; value — a function of pixel dimensions, image entropy, and compression quality — which makes "resize this to exactly 51,200 bytes" a surprisingly nontrivial optimization problem, not a one-line &lt;code&gt;canvas.toBlob()&lt;/code&gt; call.&lt;/p&gt;

&lt;p&gt;A few months ago, my cousin ran into this on a state exam portal that capped passport photos at 50KB. She spent two hours bouncing between random "photo compressor" sites, most of which just apply a fixed compression ratio and let you deal with whatever number comes out. None of them actually solve for a target size. By the time she landed on something that worked, the registration window had closed for the day.&lt;/p&gt;

&lt;p&gt;So here's the actual technical problem underneath this UX annoyance — and how to solve it properly instead of guessing quality percentages by hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's Not You. File Size Is Genuinely Unpredictable.
&lt;/h2&gt;

&lt;p&gt;Here's the thing nobody tells you: file size in kilobytes isn't something you can just "set." It's the &lt;em&gt;result&lt;/em&gt; of several things happening at once — how detailed the image is, what dimensions it's saved at, and how aggressively it's compressed. Change any one of those, and the final number shifts unpredictably.&lt;/p&gt;

&lt;p&gt;A plain white background compresses down to almost nothing. A busy, detailed photo — a face with visible texture, a signature with lots of fine ink strokes — resists compression much harder, because there's more actual information in the pixels. Two photos that look similarly sized on your screen can land at wildly different file sizes once compressed, simply because of what's &lt;em&gt;in&lt;/em&gt; them.&lt;/p&gt;

&lt;p&gt;Then there's the format problem, which trips up almost everyone. If your image is a PNG (which is what a lot of screenshot tools and scanning apps default to), you're fighting an uphill battle. PNG is a &lt;em&gt;lossless&lt;/em&gt; format — it never throws away any pixel detail, no matter how much you compress it. That's exactly why it's terrible for hitting a tiny KB target. JPEG, on the other hand, is built to selectively discard detail the human eye barely notices, which is precisely the kind of flexible compression you need to land on an exact number. This is also why almost every exam portal explicitly asks for a JPG, not a PNG — they know PNG can't reliably hit their limit.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Numbers Aren't Random Either
&lt;/h2&gt;

&lt;p&gt;Once you start looking at these limits across different portals, a pattern shows up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;20KB&lt;/strong&gt; shows up almost exclusively for signature scans.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;30KB&lt;/strong&gt; is usually a thumb impression or biometric scan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;50KB&lt;/strong&gt; is the classic passport-style photo limit — SSC, UPSC, IBPS, most competitive exams.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;100KB&lt;/strong&gt; covers ID document scans like Aadhaar cards, where legible text matters more than photographic detail.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;150–200KB&lt;/strong&gt; shows up for visa photos and certificate scans, which need sharper detail or more content per image.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are arbitrary. They exist because these systems were built to handle enormous submission volumes — millions of applicants uploading files during a narrow registration window — and tiny file sizes keep the servers from falling over.&lt;/p&gt;

&lt;p&gt;Knowing &lt;em&gt;why&lt;/em&gt; the number exists doesn't make hitting it easier, though. That still comes down to process.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Actual Fix: Work Backwards From the Number
&lt;/h2&gt;

&lt;p&gt;Here's what finally worked for my cousin, and what I'd tell any dev building or using one of these tools: stop treating this as "apply compression and check the result." Treat it as a search problem. You have a target byte count, and a monotonic (roughly) relationship between JPEG quality and output size — so a binary search over quality levels, re-encoding via canvas on each iteration, converges on the right setting in a handful of steps instead of one lucky guess. If quality alone can't hit the target even at the low end, the next lever is scaling down pixel dimensions in small increments, not crushing quality further.&lt;/p&gt;

&lt;p&gt;That's the approach behind a browser-based tool like &lt;a href="https://resizehub.in/tools/resize-image-to-kb" rel="noopener noreferrer"&gt;ResizeHub's exact-KB image resizer&lt;/a&gt; — pick a preset (20KB, 50KB, 100KB, whatever the form asks for) or type a custom number, and it runs that iterative compress-check-adjust loop for you instead of you doing it by hand in Photoshop. If a PNG comes in, it converts to JPG automatically, since PNG's lossless encoding can't be tuned down to an arbitrary byte target the way JPEG can. And because the whole thing runs in-browser via the Canvas API — no server round-trip — a signature or ID scan never actually leaves the device, which matters more than people think when the file in question is a government ID.&lt;/p&gt;

&lt;p&gt;A few things that make a real difference no matter which tool you use:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Crop first, compress second.&lt;/strong&gt; If the form wants a small passport-style photo and you upload a full 12-megapixel original, the compression algorithm has to work much harder — and throw away far more detail — to hit the target. Get close to the required dimensions before you compress.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JPG over PNG, every time, when a KB limit is involved.&lt;/strong&gt; This one rule alone solves half the frustration people run into.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Always preview before submitting.&lt;/strong&gt; Especially with signatures, where a few compressed pixels can turn a legible signature into a smudge.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Small Number That Actually Matters
&lt;/h2&gt;

&lt;p&gt;It's a strange feature of modern bureaucracy that a random number like "50KB" can decide whether someone makes a deadline. But once you understand &lt;em&gt;why&lt;/em&gt; that number exists and &lt;em&gt;how&lt;/em&gt; file compression actually works, it stops being a mystery and becomes a five-minute task instead of a two-hour one.&lt;/p&gt;

&lt;p&gt;My cousin re-registered the next cycle. Photo compressed correctly, uploaded on the first try, in about thirty seconds. Not a dramatic ending, but that's kind of the point — it shouldn't have taken two hours in the first place.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you're stuck on a similarly oddly-specific file size requirement, I wrote a more detailed breakdown of target sizes and formats &lt;a href="https://resizehub.in/tools/resize-image-to-kb" rel="noopener noreferrer"&gt;here&lt;/a&gt; — happy to answer questions in the comments if your form has a weird limit I haven't covered.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>softwareengineering</category>
      <category>programming</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Build a Client-Side Image Compressor in 30 Minutes (No Backend Needed)</title>
      <dc:creator>Vijay Kanna</dc:creator>
      <pubDate>Wed, 22 Jul 2026 05:08:41 +0000</pubDate>
      <link>https://dev.to/vijay_kanna_56/build-a-client-side-image-compressor-in-30-minutes-no-backend-needed-552m</link>
      <guid>https://dev.to/vijay_kanna_56/build-a-client-side-image-compressor-in-30-minutes-no-backend-needed-552m</guid>
      <description>&lt;p&gt;Most image compression tutorials assume you're running something server-side — Sharp, ImageMagick, a Node backend. This one doesn't need any of that. We're going to build a working image compressor that runs entirely in the browser, using nothing but the Canvas API.&lt;/p&gt;

&lt;p&gt;By the end, you'll have a single HTML file that takes an image, compresses it, and lets the user download the result — no server, no dependencies, no build step.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We're Building
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo2zogj6kv6lc1poom1ih.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo2zogj6kv6lc1poom1ih.png" alt="Final browser-based image compressor application built in this tutorial" width="800" height="827"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A simple page where a user can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Upload an image&lt;/li&gt;
&lt;li&gt;Adjust a quality slider&lt;/li&gt;
&lt;li&gt;See the compressed file size update live&lt;/li&gt;
&lt;li&gt;Download the compressed result
## Step 1: The HTML Skeleton
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Image Compressor&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"file"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"fileInput"&lt;/span&gt; &lt;span class="na"&gt;accept=&lt;/span&gt;&lt;span class="s"&gt;"image/*"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;Quality: &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"qualityValue"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;80&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;%&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"range"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"qualitySlider"&lt;/span&gt; &lt;span class="na"&gt;min=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; &lt;span class="na"&gt;max=&lt;/span&gt;&lt;span class="s"&gt;"100"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"80"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;canvas&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"canvas"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"max-width: 400px; display: none;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/canvas&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"sizeInfo"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"downloadLink"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"display: none;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Download Compressed Image&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"compress.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing fancy — a file input, a quality slider, a canvas to do the actual work, and a download link that appears once we have a result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Loading the Image Into Canvas
&lt;/h2&gt;

&lt;p&gt;Canvas can't compress a file directly — it works with pixels. So the first job is getting the uploaded image drawn onto a canvas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fileInput&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;canvas&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;currentImage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;fileInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bitmap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createImageBitmap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;currentImage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bitmap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bitmap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bitmap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drawImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bitmap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;block&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;compressAndPreview&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;createImageBitmap&lt;/code&gt; is the modern way to decode an image file — it's faster than the older &lt;code&gt;Image()&lt;/code&gt; + &lt;code&gt;onload&lt;/code&gt; pattern and works well with async/await.&lt;/p&gt;

&lt;p&gt;If you need to support older browsers that don't implement &lt;code&gt;createImageBitmap&lt;/code&gt;, you can fall back to using an &lt;code&gt;Image&lt;/code&gt; element with an &lt;code&gt;onload&lt;/code&gt; handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loadImageFallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onerror&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both approaches draw onto the canvas the same way afterward — &lt;code&gt;ctx.drawImage(bitmap, 0, 0)&lt;/code&gt; works whether &lt;code&gt;bitmap&lt;/code&gt; came from &lt;code&gt;createImageBitmap&lt;/code&gt; or this fallback.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Compressing With &lt;code&gt;toBlob&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This is the actual compression step. &lt;code&gt;canvas.toBlob()&lt;/code&gt; takes a format and a quality value (0 to 1) and re-encodes the canvas content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;qualitySlider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;qualitySlider&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;qualityValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;qualityValue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sizeInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sizeInfo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;downloadLink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;downloadLink&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;compressAndPreview&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;currentImage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;quality&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;qualitySlider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBlob&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sizeKB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;sizeInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Compressed size: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sizeKB&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; KB`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;downloadLink&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;downloadLink&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;download&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;compressed.jpg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;downloadLink&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Download (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sizeKB&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; KB)`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;downloadLink&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;inline&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image/jpeg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;qualitySlider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;qualityValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;qualitySlider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;compressAndPreview&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the entire core mechanism. Move the slider, &lt;code&gt;toBlob&lt;/code&gt; re-encodes at the new quality, and the size updates live.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: A Small but Important Fix — Memory Cleanup
&lt;/h2&gt;

&lt;p&gt;Every time &lt;code&gt;compressAndPreview()&lt;/code&gt; runs, it creates a new object URL with &lt;code&gt;URL.createObjectURL()&lt;/code&gt;. If you don't release the old one, you leak memory — this adds up fast if someone drags the slider around for a while:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;previousUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;compressAndPreview&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;currentImage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;quality&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;qualitySlider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBlob&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;previousUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;revokeObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;previousUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sizeKB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;sizeInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Compressed size: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sizeKB&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; KB`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;previousUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;downloadLink&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;downloadLink&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;download&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;compressed.jpg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;downloadLink&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Download (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sizeKB&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; KB)`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;downloadLink&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;inline&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image/jpeg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Small detail, easy to miss, and exactly the kind of thing that turns into a real memory leak if this code ever ships in something long-running.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Doesn't Handle (On Purpose)
&lt;/h2&gt;

&lt;p&gt;This tutorial deliberately stops at "quality slider compression" because that's enough to actually understand the mechanism. A few things it leaves out, if you want to take it further:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Target file size instead of quality&lt;/strong&gt; — this needs a search loop around &lt;code&gt;toBlob&lt;/code&gt; (binary search on quality, falling back to resizing dimensions when quality bottoms out) rather than a direct slider&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PNG support&lt;/strong&gt; — &lt;code&gt;toBlob&lt;/code&gt; works for PNG too, but PNG is lossless, so "quality" doesn't apply the same way; you'd be resizing or reducing color depth instead&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Large image performance&lt;/strong&gt; — very large images (many megapixels) can make &lt;code&gt;toBlob&lt;/code&gt; noticeably slow; moving this to a Web Worker keeps the UI responsive during compression
## Next Challenges&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you've built this version, try extending it with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Drag-and-drop uploads&lt;/li&gt;
&lt;li&gt;Batch compression&lt;/li&gt;
&lt;li&gt;WebP output&lt;/li&gt;
&lt;li&gt;AVIF output&lt;/li&gt;
&lt;li&gt;Exact target-size compression&lt;/li&gt;
&lt;li&gt;EXIF orientation handling
## Try It Yourself&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Paste both snippets into an HTML file and open it in a browser — no build tools, no npm install, nothing to configure. That's the whole appeal of doing this client-side: it's genuinely just HTML, CSS, and the Canvas API doing real work.&lt;/p&gt;

&lt;p&gt;If you want to see a fuller version of this same idea — target-size compression, PDF tools, and 70+ other browser-based utilities built on the same no-backend approach — I've been building &lt;a href="https://resizehub.in" rel="noopener noreferrer"&gt;ResizeHub&lt;/a&gt; using exactly this pattern, extended a lot further.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>performance</category>
    </item>
    <item>
      <title>Why Compressing an Image to Exactly 50KB Is Harder Than It Sounds</title>
      <dc:creator>Vijay Kanna</dc:creator>
      <pubDate>Tue, 21 Jul 2026 02:59:21 +0000</pubDate>
      <link>https://dev.to/vijay_kanna_56/why-compressing-an-image-to-exactly-50kb-is-harder-than-it-sounds-1pbg</link>
      <guid>https://dev.to/vijay_kanna_56/why-compressing-an-image-to-exactly-50kb-is-harder-than-it-sounds-1pbg</guid>
      <description>&lt;p&gt;Ever wondered why one image hits 50KB at 80% quality while another is still 200KB at 20%? The answer isn't your compression library — it's how JPEG compression actually works. If you've ever tried building an "exact target size" image compressor, you've probably discovered this is a surprisingly hard optimization problem, not the one-line &lt;code&gt;canvas.toBlob(quality)&lt;/code&gt; call it looks like from the outside.&lt;/p&gt;

&lt;p&gt;Here's what's actually happening under the hood, and why "just lower the quality until it's small enough" isn't as simple as it sounds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quality Percentage and File Size Aren't Linearly Related
&lt;/h2&gt;

&lt;p&gt;JPEG's "quality" setting doesn't control file size directly — it controls the quantization table used during compression, which determines how aggressively high-frequency detail gets discarded. The relationship between quality and resulting file size depends entirely on the image content:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A smooth gradient (sky, solid background) compresses extremely well at high quality — very little high-frequency detail to discard in the first place&lt;/li&gt;
&lt;li&gt;A busy, high-detail photo (foliage, fabric texture, crowd scene) can be 5–10x larger than a smooth image at the &lt;em&gt;same&lt;/em&gt; quality setting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means "quality 70" might produce a 15KB file for one image and a 180KB file for another. A tool that just applies a fixed quality value and calls it done will wildly miss a target size depending on what's actually in the photo.&lt;/p&gt;

&lt;p&gt;The relationship isn't linear either — file size drops fast at first as quality decreases, then flattens out, because the most "compressible" redundancy gets squeezed out early and what's left resists compression much harder:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Quality&lt;/th&gt;
&lt;th&gt;Typical size (busy photo)&lt;/th&gt;
&lt;th&gt;Typical size (smooth image)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;~2.1 MB&lt;/td&gt;
&lt;td&gt;~180 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;~1.3 MB&lt;/td&gt;
&lt;td&gt;~90 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;~900 KB&lt;/td&gt;
&lt;td&gt;~55 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;~650 KB&lt;/td&gt;
&lt;td&gt;~40 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;~520 KB&lt;/td&gt;
&lt;td&gt;~32 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;~380 KB&lt;/td&gt;
&lt;td&gt;~22 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;(Illustrative shape of the curve, not measured output — the point is the curve flattens, not the exact numbers, which depend entirely on your specific image.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That flattening is exactly why a naive "keep lowering quality until it's small enough" loop can spin through a dozen iterations near the bottom of the range and barely move the needle — at which point the only lever left is dimensions, not quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why You Can't Just Binary-Search Quality Alone
&lt;/h2&gt;

&lt;p&gt;The naive fix is: binary search the quality parameter until the output lands near your target size. This mostly works, but it breaks down at the edges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Some images can't hit low targets at any quality setting&lt;/strong&gt; without dropping resolution too — a 4000x3000px photo at quality 1 can still be well over 50KB, because dimension, not just quality, drives the byte count once compression is already near its floor&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JPEG quality below ~10-15 produces visible blocking artifacts&lt;/strong&gt; that make the "successful" output useless even though it technically hit the target size&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PNG doesn't have a quality slider at all&lt;/strong&gt; — it's lossless, so "compress to 50KB" means either reducing color depth (palette reduction) or reducing dimensions, an entirely different algorithm path than JPEG&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A compressor that only tunes quality will either fail outright on some images, or "succeed" by producing something visibly broken.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Works: A Multi-Variable Search
&lt;/h2&gt;

&lt;p&gt;A more robust approach searches across multiple parameters together, not quality alone:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Quality (for lossy formats)&lt;/strong&gt; — the first lever, cheapest to adjust&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dimensions&lt;/strong&gt; — downscaling before compressing is often more effective than cranking quality down further, especially for images that will be displayed small anyway (a signature, a thumbnail, an exam photo)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chroma subsampling&lt;/strong&gt; — reducing color resolution while keeping luminance detail (4:2:0 vs 4:4:4) can meaningfully cut size with minimal visible impact for photographic content&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Format&lt;/strong&gt; — for some content, switching from PNG to JPG or WebP entirely changes what's achievable at a given size, which is worth checking before assuming a target is impossible&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A binary search over quality alone, falling back to progressive downscaling when quality bottoms out before hitting the target, gets you close to the actual achievable minimum — without silently destroying the image or endlessly failing. The decision flow looks roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; Original image
       │
       ▼
 Binary search quality (target ±5%)
       │
       ▼
 Target reached? ──yes──▶ Done
       │no
       ▼
 Quality bottomed out (~10-15)?
       │yes
       ▼
 Reduce dimensions, reset quality search
       │
       ▼
 Target reached? ──yes──▶ Done
       │no
       ▼
 Report achievable minimum (don't fake success)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Case Where It's Genuinely Impossible
&lt;/h2&gt;

&lt;p&gt;Some images cannot hit a small target size without visible quality loss, full stop — a detailed, high-resolution photo asked to compress to 20KB is going to look bad no matter what combination of parameters you try, because there's a hard information-theoretic floor below which you're discarding data the human eye will notice missing.&lt;/p&gt;

&lt;p&gt;This is the part most tools get wrong: they either silently return a degraded image and call it a success, or they just fail with no explanation. The honest approach is to report the achievable minimum and let the user decide, rather than pretending the target was hit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example Results
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;(These reflect the kind of outcome this approach produces — verify against your own implementation's actual output before publishing, and only call it "measured" if you've genuinely run these exact images through your compressor.)&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Image type&lt;/th&gt;
&lt;th&gt;Resolution&lt;/th&gt;
&lt;th&gt;Original&lt;/th&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;th&gt;Final&lt;/th&gt;
&lt;th&gt;Iterations&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Portrait photo&lt;/td&gt;
&lt;td&gt;4000×3000&lt;/td&gt;
&lt;td&gt;3.8 MB&lt;/td&gt;
&lt;td&gt;50 KB&lt;/td&gt;
&lt;td&gt;49.8 KB&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Signature scan&lt;/td&gt;
&lt;td&gt;900×300&lt;/td&gt;
&lt;td&gt;180 KB&lt;/td&gt;
&lt;td&gt;20 KB&lt;/td&gt;
&lt;td&gt;20.1 KB&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Screenshot with text&lt;/td&gt;
&lt;td&gt;1920×1080&lt;/td&gt;
&lt;td&gt;1.1 MB&lt;/td&gt;
&lt;td&gt;50 KB&lt;/td&gt;
&lt;td&gt;64 KB&lt;/td&gt;
&lt;td&gt;Quality floor reached&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The screenshot case is the interesting one — quality search bottomed out before reaching 50KB, because a text-heavy screenshot doesn't have the same high-frequency redundancy a photo does. Going lower would've meant visible artifacts around the text, so the honest answer here is "closest achievable without damage," not "target hit."&lt;/p&gt;

&lt;h2&gt;
  
  
  How Format Choice Changes What's Achievable
&lt;/h2&gt;

&lt;p&gt;Before assuming a target is impossible, it's worth checking whether a different format gets you there:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;JPEG&lt;/strong&gt; — best general-purpose choice for photographic content; the format most compression tutorials assume by default&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PNG&lt;/strong&gt; — no quality slider at all; hitting a small target means reducing color depth (palette/indexed color) or dimensions, a completely different code path than JPEG's DCT-based approach&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WebP&lt;/strong&gt; — typically 25–35% smaller than JPEG at comparable visual quality for photographic content, and supports both lossy and lossless modes; can still lose to PNG for flat-color graphics or images with sharp text edges&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AVIF&lt;/strong&gt; — generally beats WebP on compression ratio for photographic content, sometimes significantly, but encoding is meaningfully slower — a real cost for client-side, in-browser compression where the user is waiting on the result, and browser support is less universal than WebP's&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a target-size compressor specifically, this means the "impossible without visible quality loss" conclusion is format-dependent — a target unreachable in PNG might be trivial in WebP for the same image.&lt;/p&gt;

&lt;h2&gt;
  
  
  Doing This in the Browser
&lt;/h2&gt;

&lt;p&gt;If you're doing this client-side (Canvas API, no server round-trip), the mechanics are similar but with real constraints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;compressToTarget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;targetKB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;maxIterations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createImageBitmap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;quality&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;scale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;low&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;high&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;maxIterations&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;canvas&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drawImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBlob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image/jpeg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sizeKB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sizeKB&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;targetKB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;targetKB&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// within 5% of target — good enough&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sizeKB&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;targetKB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;high&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;quality&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;low&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;high&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;low&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;quality&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;low&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;high&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// If quality search has bottomed out and we're still over target,&lt;/span&gt;
    &lt;span class="c1"&gt;// start reducing dimensions instead of pushing quality lower&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;quality&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.15&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;sizeKB&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;targetKB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;scale&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="mf"&gt;0.85&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;quality&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;low&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;high&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// couldn't hit target without falling back to scale reduction&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The interesting engineering problem isn't the binary search itself — it's deciding when to stop trusting quality reduction and switch to dimension reduction instead, and knowing when to give up and report the honest floor rather than returning something technically-compressed-but-visually-broken.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters Beyond Just Images
&lt;/h2&gt;

&lt;p&gt;The same shape of problem shows up anywhere you're targeting a byte budget instead of a quality parameter — video export for a size-limited platform, PDF compression for an email attachment cap, even audio encoding for a storage quota. The pattern is the same: single-variable search gets you most of the way, but the interesting failure modes live at the edges where the target genuinely isn't achievable without a second variable (dimensions, bitrate, sample rate) coming into play.&lt;/p&gt;

&lt;p&gt;I ran into these edge cases while building &lt;a href="https://resizehub.in" rel="noopener noreferrer"&gt;ResizeHub&lt;/a&gt;, a browser-based image compression tool with exact target-size support. The engineering challenge turned out to be much more about search strategies than compression itself. &lt;br&gt;
If you'd like to experiment with the algorithm,&lt;br&gt;
try the live implementation on&lt;br&gt;
&lt;a href="https://resizehub.in/tools/smart-png-compressor" rel="noopener noreferrer"&gt;ResizeHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.itu.int/rec/T-REC-T.81" rel="noopener noreferrer"&gt;JPEG Specification (ITU-T T.81)&lt;/a&gt; — the original standard, for anyone who wants the DCT/quantization details in full&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/speed/webp" rel="noopener noreferrer"&gt;WebP Documentation (Google)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aomediacodec.github.io/av1-avif/" rel="noopener noreferrer"&gt;AVIF Documentation (Alliance for Open Media)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob" rel="noopener noreferrer"&gt;MDN: HTMLCanvasElement.toBlob()&lt;/a&gt; — the browser API used in the code above&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you've implemented an exact-size image compressor, how did you handle the quality-vs-dimensions trade-off? Binary search like this, a perceptual metric like SSIM/PSNR to decide when quality loss becomes "visible," or something else entirely? I'd love to compare approaches.&lt;/p&gt;




</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>algorithms</category>
      <category>performance</category>
    </item>
    <item>
      <title>9 Things Developers Confidently Say About "It Runs Entirely in the Browser"</title>
      <dc:creator>Vijay Kanna</dc:creator>
      <pubDate>Sun, 19 Jul 2026 15:02:25 +0000</pubDate>
      <link>https://dev.to/vijay_kanna_56/9-things-developers-confidently-say-about-it-runs-entirely-in-the-browser-44e0</link>
      <guid>https://dev.to/vijay_kanna_56/9-things-developers-confidently-say-about-it-runs-entirely-in-the-browser-44e0</guid>
      <description>&lt;p&gt;"Client-side" has become one of those phrases that gets said with total confidence and about 60% accuracy. Here are nine things I've heard — said some of them myself — that sound right until you actually test them.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;"It's more private because it's client-side."&lt;br&gt;
Sure, if nothing else on the page is calling out to a third-party script, an analytics pixel, or a CDN-hosted library that phones home. "Runs in the browser" and "sends nothing anywhere" are not the same claim, and a lot of client-side tools quietly do both without realizing it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;"No server means no cost."&lt;br&gt;
No compute cost, sure. Still paying for hosting, bandwidth on every asset, and the CDN serving your now-much-larger JS bundle to every single visitor whether they use the feature or not.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;"It'll work the same on mobile."&lt;br&gt;
It will work. "The same" is doing a lot of heavy lifting in that sentence, especially the first time Canvas or Web Workers hits an actual memory ceiling on a five-year-old Android phone.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;"We don't need a backend at all now."&lt;br&gt;
Right up until you need auth, rate limiting, or literally anything that requires trusting the client not to lie to you — which, gentle reminder, you should never do.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;"Client-side means it's automatically fast."&lt;br&gt;
Client-side means it's fast if you're disciplined about what loads when. Ship every dependency on page load "because it's all client-side anyway" and you've just moved the slowness from a server response to a JS parse.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;"SEO doesn't matter as much since it's an app, not a website."&lt;br&gt;
Said by someone who has never tried to explain to a client why their beautifully engineered client-side app doesn't show up on page one of anything.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;"The browser will just garbage collect it, it's fine."&lt;br&gt;
It will, eventually, probably, unless you've built a closure that's quietly holding a reference to a 40MB canvas buffer that never gets released and now the tab crashes after the fourth image.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;"Nobody actually inspects the network tab."&lt;br&gt;
Somebody always inspects the network tab. Usually right when you claimed nothing gets uploaded.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;"It's basically done once it works on my machine."&lt;br&gt;
On your M-series MacBook, on fiber, on Chrome. The real test is a mid-range Android phone on 4G with twelve tabs already open — which is a huge share of real users and none of your local testing.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of this is an argument against client-side architecture — I'm neck-deep in exactly this building a 70+ tool browser-based toolkit and would make the same trade-offs again. It's more that "it runs in the browser" gets said as if it settles an engineering debate on its own, and it really doesn't.&lt;/p&gt;

&lt;p&gt;What's the one you've said out loud and had to walk back later? Or the one you still believe and think I'm wrong about — genuinely curious which of these gets argued with the most.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>jokes</category>
      <category>performance</category>
    </item>
    <item>
      <title>I Built a Tool That Resizes Images to an Exact KB Target Instead of a Quality Slider</title>
      <dc:creator>Vijay Kanna</dc:creator>
      <pubDate>Sun, 19 Jul 2026 05:37:27 +0000</pubDate>
      <link>https://dev.to/vijay_kanna_56/i-built-a-tool-that-resizes-images-to-an-exact-kb-target-instead-of-a-quality-slider-3aip</link>
      <guid>https://dev.to/vijay_kanna_56/i-built-a-tool-that-resizes-images-to-an-exact-kb-target-instead-of-a-quality-slider-3aip</guid>
      <description>&lt;p&gt;Every image resizer I tried had the same problem: you drag a "quality" slider and hope the output lands under whatever limit you actually need.&lt;/p&gt;

&lt;p&gt;That's fine if you're optimizing a blog header. It's useless if a government exam portal tells you "signature must be under 20KB" and rejects your file three times before you land close enough by trial and error.&lt;/p&gt;

&lt;p&gt;So I built a resizer that works backward from a target file size instead of a quality percentage. You type "50KB," it iterates compression internally until the output is as close as possible to that number, then gives you the file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How it works, roughly:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Runs entirely in the browser (Canvas API + a binary search over quality/dimension steps)&lt;/li&gt;
&lt;li&gt;No upload to a server, so it's fast and doesn't touch your original file&lt;/li&gt;
&lt;li&gt;Converges on a target size within a few passes instead of guessing
&lt;strong&gt;What surprised me building it:&lt;/strong&gt;
The hard part wasn't the compression — it was handling images that &lt;em&gt;can't&lt;/em&gt; hit the target no matter how much quality you strip, without silently returning garbage. Had to add a floor where it tells you "can't go below X without destroying the image" instead of pretending it succeeded.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's part of a small toolkit I've been building — &lt;a href="https://resizehub.in" rel="noopener noreferrer"&gt;ResizeHub&lt;/a&gt; — free, no login, no watermark. This particular tool came directly out of watching people in exam-prep forums complain about signature/photo size rejections.&lt;/p&gt;

&lt;p&gt;Curious if others have hit the same "quality slider vs. target size" mismatch in other contexts — video export, PDF compression, etc. What's your workaround been?&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Scaling a Single React App to 71+ Browser-Based Tools Without Killing Load Time</title>
      <dc:creator>Vijay Kanna</dc:creator>
      <pubDate>Sat, 18 Jul 2026 12:20:07 +0000</pubDate>
      <link>https://dev.to/vijay_kanna_56/scaling-a-single-react-app-to-71-browser-based-tools-without-killing-load-time-36hl</link>
      <guid>https://dev.to/vijay_kanna_56/scaling-a-single-react-app-to-71-browser-based-tools-without-killing-load-time-36hl</guid>
      <description>&lt;h2&gt;
  
  
  The problem with "just add another tool"
&lt;/h2&gt;

&lt;p&gt;When you're building one image tool, performance is easy. When you're building 71 of them in the same app — resize, compress, crop, PDF merge, format converters, exam-photo presets, social media templates — the naive approach (import everything, bundle it all together) turns your app into a multi-megabyte JavaScript payload before a user has even picked a tool.&lt;/p&gt;

&lt;p&gt;This is the actual engineering problem behind &lt;a href="https://resizehub.in" rel="noopener noreferrer"&gt;ResizeHub&lt;/a&gt;, which now has 71+ tools across 11 categories, all running client-side with zero server uploads. Here's how the architecture holds up at that scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stack, and why each piece earns its place
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;React + TypeScript&lt;/strong&gt; — type safety matters more, not less, as tool count grows. A shared &lt;code&gt;ImageProcessor&lt;/code&gt; interface that every tool implements catches integration bugs at compile time instead of in production.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vite&lt;/strong&gt; — its native ES modules dev server and Rollup-based production build made code-splitting dramatically easier to reason about than older bundlers, which matters a lot once you have dozens of independent tool routes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTML5 Canvas API&lt;/strong&gt; — the actual compression/resize/crop engine, shared across tools rather than reimplemented per-tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cropper.js&lt;/strong&gt; — for interactive cropping UI specifically (aspect-ratio locking, circular crop for signatures) rather than rebuilding drag-handle math from scratch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pica&lt;/strong&gt; — for high-quality image downscaling; the browser's native canvas scaling can introduce visible aliasing on large downscales, and Pica's algorithm handles this noticeably better.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloudflare Pages&lt;/strong&gt; — static hosting with edge caching, which matters since 100% of the actual processing work happens in the user's browser, not on any server at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Lesson 1: Route-level code splitting isn't optional past a handful of tools
&lt;/h2&gt;

&lt;p&gt;With React Router and dynamic &lt;code&gt;import()&lt;/code&gt;, each tool becomes its own chunk:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PhotoResizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;lazy&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./tools/PhotoResizer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PdfCompressor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;lazy&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./tools/PdfCompressor&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SignatureCropper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;lazy&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./tools/SignatureCropper&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without this, every visitor downloads the code for all 71 tools just to use one. With it, a user landing on the SSC photo tool only pulls that tool's bundle plus shared core — not the PDF merger, not the social media template generator, not tools they'll never touch in that session.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 2: Share the processing core, isolate the UI
&lt;/h2&gt;

&lt;p&gt;The temptation with many similar tools is to copy-paste a working tool and tweak it. That works for the first five tools and becomes unmaintainable by tool thirty. The fix that held up: a shared core engine (resize, compress-to-target-KB, format conversion, crop math) that every tool imports, with only the &lt;strong&gt;UI and preset configuration&lt;/strong&gt; differing per tool.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// shared core&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;resizeToTarget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HTMLCanvasElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ToolSpec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// per-tool, just config&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sscPhotoSpec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ToolSpec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;230&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;maxKB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jpeg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ibpsPhotoSpec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ToolSpec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;230&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;maxKB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jpeg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means adding tool #72 for a new exam board is a config object, not new processing logic — and a bug fix in the core engine fixes it everywhere at once instead of needing to be copy-pasted across dozens of files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 3: Category-level grouping helps both users and bundlers
&lt;/h2&gt;

&lt;p&gt;Organizing 71 tools into 11 categories (Resize, Compress, Convert, Crop &amp;amp; Edit, PDF, Exam Tools, Social Media, Enhancement, Utility, Batch, Privacy) wasn't just a UX decision — it maps naturally onto chunk boundaries too. Tools within a category often share more code (e.g., all exam-photo tools share background-validation logic) than tools across categories, so grouping by category keeps related code physically close and shared chunks smaller and more targeted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 4: Batch processing needs a different execution model
&lt;/h2&gt;

&lt;p&gt;Single-image tools can process synchronously without the user noticing. Batch tools (batch resize, batch compress, batch convert across many files) need to avoid blocking the main thread, or the UI freezes on a 50-image batch. The practical fix is chunked async processing with &lt;code&gt;requestIdleCallback&lt;/code&gt; or a Web Worker for the heavier operations, processing a few images at a time and yielding back to the browser between chunks so the UI stays responsive and progress can actually update.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 5: Type-safe tool specs prevent an entire category of bugs
&lt;/h2&gt;

&lt;p&gt;With 71 tools, many sharing similar-but-not-identical specs (every government exam board has slightly different photo requirements), a loosely-typed config object is a bug magnet. A strict &lt;code&gt;ToolSpec&lt;/code&gt; interface, checked at compile time, catches "someone typo'd the KB limit for one exam board" before it ships rather than after a user's application gets rejected because of it — which matters more here than in most apps, since a wrong spec has a real downstream cost for someone filling out a government form.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this adds up to
&lt;/h2&gt;

&lt;p&gt;None of these are exotic techniques individually — code splitting, shared core logic, typed configs are all fairly standard React practice. The interesting part is that at 5-10 tools, skipping all of this barely matters. At 70+, skipping any one of them compounds into real load-time and maintainability problems. The architecture decisions that don't matter early are exactly the ones that determine whether scaling to "one more tool" stays cheap or becomes expensive.&lt;/p&gt;

&lt;p&gt;If you're curious how this plays out in practice, the live result — 71+ tools, all client-side, no uploads — is at &lt;a href="https://resizehub.in" rel="noopener noreferrer"&gt;resizehub.in&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Curious from others in the comments
&lt;/h2&gt;

&lt;p&gt;If you've built a similar "many small tools, one app" product, I'd like to compare notes — specifically on whether you went with route-based splitting like this or a micro-frontend approach instead, and where that tradeoff started to matter for you.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>tutorial</category>
      <category>javascript</category>
    </item>
    <item>
      <title>I Built a Free Image Resizer That Never Uploads Your Photos to a Server — Here's How</title>
      <dc:creator>Vijay Kanna</dc:creator>
      <pubDate>Sat, 18 Jul 2026 00:51:55 +0000</pubDate>
      <link>https://dev.to/vijay_kanna_56/i-built-a-free-image-resizer-that-never-uploads-your-photos-to-a-server-heres-how-509d</link>
      <guid>https://dev.to/vijay_kanna_56/i-built-a-free-image-resizer-that-never-uploads-your-photos-to-a-server-heres-how-509d</guid>
      <description>&lt;h2&gt;
  
  
  The problem I kept running into
&lt;/h2&gt;

&lt;p&gt;Every time I needed to resize a photo for a passport application, an exam form, or just to shrink a screenshot before sharing it, I ended up on some random "free" image tool that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uploaded my photo to their server (privacy, anyone?)&lt;/li&gt;
&lt;li&gt;Buried the download button under three ad banners&lt;/li&gt;
&lt;li&gt;Forced me to sign up for a "free" account&lt;/li&gt;
&lt;li&gt;Randomly failed on files over 2MB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I built &lt;strong&gt;&lt;a href="https://resizehub.in" rel="noopener noreferrer"&gt;ResizeHub&lt;/a&gt;&lt;/strong&gt; — a completely free, browser-based image toolkit that resizes, compresses, and converts images &lt;strong&gt;without ever sending your file to a server&lt;/strong&gt;. Everything happens client-side, in your browser, using the Canvas API.&lt;/p&gt;

&lt;p&gt;Here's a breakdown of how it works and why client-side image processing is a genuinely underrated approach for web tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why process images in the browser instead of on a server?
&lt;/h2&gt;

&lt;p&gt;Most "free" image tools follow the same pattern: user uploads file → server processes it → server sends back a result. That's simple to build, but it comes with real costs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Privacy risk&lt;/strong&gt; — your image (which might contain a face, ID document, or signature) sits on someone else's server, even if briefly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server cost&lt;/strong&gt; — every resize operation burns compute and bandwidth on the backend, which is why most of these tools carry ads or paywalls&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latency&lt;/strong&gt; — upload time + processing time + download time, especially painful on slow connections&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalability ceiling&lt;/strong&gt; — more users means more server load, so free tiers get throttled fast&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Doing it client-side flips all of this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;canvas&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;targetWidth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;targetHeight&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drawImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;targetWidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;targetHeight&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBlob&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// blob is your resized image — ready to download&lt;/span&gt;
    &lt;span class="c1"&gt;// it never left the browser&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image/jpeg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userFile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's genuinely most of the core logic. The Canvas API gives you pixel-level control over dimensions, and &lt;code&gt;toBlob()&lt;/code&gt; lets you control output quality and format entirely on the client.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling exact file-size targets (the hard part)
&lt;/h2&gt;

&lt;p&gt;Resizing to exact &lt;em&gt;dimensions&lt;/em&gt; is easy. Resizing to an exact &lt;em&gt;file size&lt;/em&gt; (a common requirement for government exam forms — "signature must be under 20KB") is trickier, because JPEG compression quality doesn't map linearly to output size.&lt;/p&gt;

&lt;p&gt;The approach that works well is a binary search over the quality parameter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;compressToTargetSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;targetKB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mimeType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image/jpeg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;min&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;quality&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBlob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mimeType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sizeKB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sizeKB&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;targetKB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sizeKB&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;targetKB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;max&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;min&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;quality&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;min&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eight iterations of binary search converge close enough for practical purposes, and it all runs in milliseconds since there's no network round trip.&lt;/p&gt;

&lt;h2&gt;
  
  
  Format conversion without a backend
&lt;/h2&gt;

&lt;p&gt;Converting between formats (PNG ↔ JPEG ↔ WebP) is just a matter of changing the MIME type passed to &lt;code&gt;toBlob()&lt;/code&gt; — the browser's built-in codecs handle the actual encoding. No image-processing library, no server, no dependency bloat.&lt;/p&gt;

&lt;p&gt;For formats the Canvas API doesn't natively support (like HEIC, common on iPhone photos), a lightweight WASM decoder can convert to a canvas-compatible format first, still entirely client-side.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this approach is good and bad for
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Good for:&lt;/strong&gt; resizing, compressing, cropping, format conversion, basic adjustments — anything the Canvas API can express as pixel operations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not great for:&lt;/strong&gt; heavy AI operations like background removal or upscaling, which genuinely need a model running somewhere. Those still need a server (or an on-device ML model, which is a whole separate rabbit hole).&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;I packaged this into a free toolkit at &lt;strong&gt;&lt;a href="https://resizehub.in" rel="noopener noreferrer"&gt;resizehub.in&lt;/a&gt;&lt;/strong&gt; — includes image resize/compress, exact-KB compression for exam forms, passport/signature photo presets, and format conversion. No signup, no upload limits since nothing uploads in the first place.&lt;/p&gt;

&lt;p&gt;If you're building something similar or have ideas for improving the compression algorithm, I'd genuinely like to hear about it in the comments.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>productivity</category>
      <category>opensource</category>
    </item>
    <item>
      <title>15 Free Browser-Based Image Tools Every Student Should Bookmark published</title>
      <dc:creator>Vijay Kanna</dc:creator>
      <pubDate>Thu, 09 Jul 2026 03:52:40 +0000</pubDate>
      <link>https://dev.to/vijay_kanna_56/15-free-browser-based-image-tools-every-student-should-bookmark-published-2683</link>
      <guid>https://dev.to/vijay_kanna_56/15-free-browser-based-image-tools-every-student-should-bookmark-published-2683</guid>
      <description>&lt;p&gt;Whether you're submitting a university assignment, applying for internships, or filling out government exam applications, image editing is something most students eventually need.&lt;/p&gt;

&lt;p&gt;The problem? Many people still install large desktop applications just to resize a photo or convert a PNG into JPG.&lt;/p&gt;

&lt;p&gt;Modern browser-based tools can do these tasks in seconds without requiring installation. Here are 15 that are genuinely useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Passport Photo Resizer
&lt;/h2&gt;

&lt;p&gt;Perfect for exam applications that require strict dimensions and file-size limits.&lt;/p&gt;

&lt;p&gt;Useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Government exams&lt;/li&gt;
&lt;li&gt;Visa applications&lt;/li&gt;
&lt;li&gt;College admissions&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Signature Resizer
&lt;/h2&gt;

&lt;p&gt;Many online portals reject signatures because they're just a few KB too large.&lt;/p&gt;

&lt;p&gt;A signature resizer helps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduce file size&lt;/li&gt;
&lt;li&gt;Preserve sharpness&lt;/li&gt;
&lt;li&gt;Export in the required format&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Image Compressor
&lt;/h2&gt;

&lt;p&gt;Compress images while maintaining visual quality.&lt;/p&gt;

&lt;p&gt;Ideal for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Email attachments&lt;/li&gt;
&lt;li&gt;Assignment uploads&lt;/li&gt;
&lt;li&gt;Portfolio websites&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Bulk Image Compressor
&lt;/h2&gt;

&lt;p&gt;Need to optimize dozens of images? Batch compression saves a surprising amount of time compared to doing them one by one.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Crop Tool
&lt;/h2&gt;

&lt;p&gt;Crop passport photos, scanned documents, screenshots, or profile pictures with precise control.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. PNG → JPG Converter
&lt;/h2&gt;

&lt;p&gt;Some websites only accept JPG uploads. A quick format conversion solves the problem instantly.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. JPG → PNG Converter
&lt;/h2&gt;

&lt;p&gt;Useful when working with logos, graphics, or images that require transparency.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. WEBP → JPG Converter
&lt;/h2&gt;

&lt;p&gt;Many images downloaded from the web are now in WEBP format by default. Converting them to JPG improves compatibility with older systems and forms.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. HEIC → JPG Converter
&lt;/h2&gt;

&lt;p&gt;If you use an iPhone, you've probably encountered HEIC images that won't upload everywhere. This fixes that in one step.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. AVIF Converter
&lt;/h2&gt;

&lt;p&gt;Convert modern AVIF images into formats supported by older applications and portals.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. SVG Optimizer
&lt;/h2&gt;

&lt;p&gt;Developers can strip unnecessary SVG metadata and reduce file size without changing appearance — handy for keeping repos and websites lean.&lt;/p&gt;

&lt;h2&gt;
  
  
  12. Instagram Image Resizer
&lt;/h2&gt;

&lt;p&gt;Generate correctly sized images for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Feed&lt;/li&gt;
&lt;li&gt;Stories&lt;/li&gt;
&lt;li&gt;Reels&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  13. YouTube Thumbnail Resizer
&lt;/h2&gt;

&lt;p&gt;Prepare thumbnails using recommended dimensions before uploading videos.&lt;/p&gt;

&lt;h2&gt;
  
  
  14. Social Media Image Resizer
&lt;/h2&gt;

&lt;p&gt;Resize images for LinkedIn, Facebook, X (Twitter), Pinterest, and WhatsApp.&lt;/p&gt;

&lt;h2&gt;
  
  
  15. Universal Image Converter
&lt;/h2&gt;

&lt;p&gt;Convert between common formats including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JPG&lt;/li&gt;
&lt;li&gt;PNG&lt;/li&gt;
&lt;li&gt;WEBP&lt;/li&gt;
&lt;li&gt;GIF&lt;/li&gt;
&lt;li&gt;BMP&lt;/li&gt;
&lt;li&gt;AVIF&lt;/li&gt;
&lt;li&gt;HEIC&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Browser-Based Tools Are Becoming More Popular
&lt;/h2&gt;

&lt;p&gt;They offer several advantages over traditional desktop software:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No installation&lt;/li&gt;
&lt;li&gt;Works on Windows, macOS, Linux, Android and iPhone&lt;/li&gt;
&lt;li&gt;Fast processing&lt;/li&gt;
&lt;li&gt;Beginner friendly&lt;/li&gt;
&lt;li&gt;Accessible from anywhere&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For simple image tasks, they're often all you need.&lt;/p&gt;

&lt;h2&gt;
  
  
  One Toolkit Instead of Multiple Websites
&lt;/h2&gt;

&lt;p&gt;While writing this article, I realized I was constantly switching between different websites depending on the task.&lt;/p&gt;

&lt;p&gt;That's one reason I built &lt;strong&gt;ResizeHub&lt;/strong&gt;, a browser-based collection of image utilities including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Passport Photo Resizer&lt;/li&gt;
&lt;li&gt;Signature Resizer&lt;/li&gt;
&lt;li&gt;Image Compressor&lt;/li&gt;
&lt;li&gt;PNG ↔ JPG Converter&lt;/li&gt;
&lt;li&gt;HEIC Converter&lt;/li&gt;
&lt;li&gt;Crop Tool&lt;/li&gt;
&lt;li&gt;SVG Optimizer&lt;/li&gt;
&lt;li&gt;Bulk Image Compressor&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything runs directly in the browser without requiring installation.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://resizehub.in" rel="noopener noreferrer"&gt;https://resizehub.in&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'd also love to hear which browser-based image tools you use most often — drop them in the comments.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>beginners</category>
      <category>opensource</category>
    </item>
    <item>
      <title>7 Free Online Image Tools Every Student and Job Seeker Should Bookmark</title>
      <dc:creator>Vijay Kanna</dc:creator>
      <pubDate>Wed, 08 Jul 2026 03:11:23 +0000</pubDate>
      <link>https://dev.to/vijay_kanna_56/7-free-online-image-tools-every-student-and-job-seeker-should-bookmark-3ind</link>
      <guid>https://dev.to/vijay_kanna_56/7-free-online-image-tools-every-student-and-job-seeker-should-bookmark-3ind</guid>
      <description>&lt;p&gt;If you've ever applied for a government exam, a university program, or a job online, you already know the pain: &lt;em&gt;"Photo must be under 50KB, dimensions 200x230px, JPG format only."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;One rejected upload because your signature file is "too large" or your photo is "the wrong format" can cost you a completed application. The good news — you don't need Photoshop or any downloaded software to fix this. A handful of free, browser-based image tools can handle it in seconds, directly from your phone or laptop.&lt;/p&gt;

&lt;p&gt;Here are seven tools worth bookmarking, especially if you're prepping documents for &lt;strong&gt;SSC, IBPS, SBI, RRB, UPSC, NEET, or university admission portals&lt;/strong&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Passport Photo Resizer&lt;/td&gt;
&lt;td&gt;Government exam applications&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Signature Resizer&lt;/td&gt;
&lt;td&gt;Exam &amp;amp; job application signatures&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image Compressor&lt;/td&gt;
&lt;td&gt;Reduce image file size&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PNG to JPG&lt;/td&gt;
&lt;td&gt;Format conversion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JPG to PNG&lt;/td&gt;
&lt;td&gt;Transparent graphics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Crop Tool&lt;/td&gt;
&lt;td&gt;Trim unwanted areas&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bulk Image Compressor&lt;/td&gt;
&lt;td&gt;Multiple images at once&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  1. Passport Photo Resizer — Fix Dimensions and File Size Instantly
&lt;/h2&gt;

&lt;p&gt;Government exam portals are notoriously strict about photo specs. A dedicated &lt;strong&gt;passport photo resizer&lt;/strong&gt; lets you set:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Exact width and height (in px, cm, or inches)&lt;/li&gt;
&lt;li&gt;Target file size (KB/MB)&lt;/li&gt;
&lt;li&gt;Output format (JPG/PNG)&lt;/li&gt;
&lt;li&gt;Live preview before download&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of guessing settings in an image editor, you get exam-ready output in one pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Signature Resizer — Meet Strict Upload Requirements
&lt;/h2&gt;

&lt;p&gt;Digital signature requirements are often stricter than photo requirements — many forms reject a signature scan simply because it's a few KB too large. A &lt;strong&gt;signature resizer&lt;/strong&gt; should let you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Resize to the exact pixel dimensions requested&lt;/li&gt;
&lt;li&gt;Keep the signature crisp and legible after compression&lt;/li&gt;
&lt;li&gt;Shrink file size without visible quality loss&lt;/li&gt;
&lt;li&gt;Export instantly in the required format&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This one small step is where a lot of applications get stuck.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Image Compressor — Shrink File Size, Keep Quality
&lt;/h2&gt;

&lt;p&gt;Large images slow down uploads and often exceed portal limits outright. A browser-based &lt;strong&gt;image compressor&lt;/strong&gt; helps by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reducing file size significantly&lt;/li&gt;
&lt;li&gt;Preserving visual quality&lt;/li&gt;
&lt;li&gt;Speeding up form submissions&lt;/li&gt;
&lt;li&gt;Saving device storage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Handy well beyond exams too — resumes, portfolios, and personal websites all benefit from lighter image files.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. PNG to JPG Converter — Fix Format Compatibility Issues
&lt;/h2&gt;

&lt;p&gt;Not every portal accepts every format. A &lt;strong&gt;PNG to JPG converter&lt;/strong&gt; lets you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cut file size (PNGs are often much larger than JPGs)&lt;/li&gt;
&lt;li&gt;Match the format a portal actually accepts&lt;/li&gt;
&lt;li&gt;Convert in seconds, no software required&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. JPG to PNG Converter — When You Need Transparency
&lt;/h2&gt;

&lt;p&gt;Sometimes it's the opposite problem — you need a transparent background for a logo, presentation graphic, or design mockup. A &lt;strong&gt;JPG to PNG converter&lt;/strong&gt; covers that use case cleanly.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Crop Tool — Isolate Exactly What's Needed
&lt;/h2&gt;

&lt;p&gt;Many forms want just a face, just a signature, or just one section of a scanned document. A simple &lt;strong&gt;online crop tool&lt;/strong&gt; helps you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Remove unwanted background or margins&lt;/li&gt;
&lt;li&gt;Isolate the required content precisely&lt;/li&gt;
&lt;li&gt;Produce a cleaner, application-ready file&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. Bulk Image Compressor — Handle Multiple Files at Once
&lt;/h2&gt;

&lt;p&gt;If you're compressing photos one at a time, you're wasting time. A &lt;strong&gt;bulk image compressor&lt;/strong&gt; lets you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Process several images in a single batch&lt;/li&gt;
&lt;li&gt;Download everything as one archive&lt;/li&gt;
&lt;li&gt;Keep every file within size limits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Especially useful for photographers, content creators, and anyone submitting multiple documents at once (mark sheets, ID proofs, certificates, etc.).&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Browser-Based Image Tools Beat Desktop Software
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero installation&lt;/strong&gt; — works the moment you open the tab&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-platform&lt;/strong&gt; — Windows, macOS, Linux, Android, iPhone, all the same&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fast&lt;/strong&gt; — most edits process in under a second&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy-friendly&lt;/strong&gt; — many tools process images locally, so files never leave your device&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Beginner-friendly&lt;/strong&gt; — no editing skills required&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where to Find All of These in One Place
&lt;/h2&gt;

&lt;p&gt;Rather than bouncing between five different sites, I've been using &lt;strong&gt;&lt;a href="https://resizehub.in" rel="noopener noreferrer"&gt;ResizeHub&lt;/a&gt;&lt;/strong&gt; — it bundles the Passport Photo Resizer, Signature Resizer, Image Compressor, PNG↔JPG converters, Crop Tool, and Bulk Image Compressor into a single browser-based toolkit. No sign-up, no installs — you just &lt;a href="https://resizehub.in" rel="noopener noreferrer"&gt;open ResizeHub&lt;/a&gt; and pick the tool you need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is the best free tool to resize a passport photo online?&lt;/strong&gt;&lt;br&gt;
A browser-based passport photo resizer that lets you set exact width, height, and target file size (in KB) — like the one on &lt;a href="https://resizehub.in" rel="noopener noreferrer"&gt;ResizeHub&lt;/a&gt; — is the fastest way to meet exam or visa photo specifications without installing software.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I reduce my signature file size for exam applications?&lt;/strong&gt;&lt;br&gt;
Use a signature resizer that compresses the image while keeping the strokes legible, and lets you export directly to the KB limit the form specifies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I compress multiple images at once for free?&lt;/strong&gt;&lt;br&gt;
Yes — bulk image compressors let you upload several files together and download them all compressed in one batch, which saves significant time over compressing images individually.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is it safe to use online image tools for personal documents like signatures or ID photos?&lt;/strong&gt;&lt;br&gt;
Look for tools that process images locally in your browser rather than uploading them to a server. This keeps sensitive documents like signatures and ID photos on your own device.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Whether you're applying for a government job, submitting university documents, or just prepping images for a resume or portfolio, having a reliable browser-based toolkit saves real time. Bookmark one, and you'll never scramble over a "file size exceeds limit" error again.&lt;/p&gt;

&lt;p&gt;The best tools are the ones you don't have to think about. When resizing images takes just a few clicks, you can spend your time on what actually matters — completing your application, not fixing file errors.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>unsplash</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How to Resize Passport Photos &amp; Signatures for Government Exam Applications</title>
      <dc:creator>Vijay Kanna</dc:creator>
      <pubDate>Tue, 07 Jul 2026 06:41:28 +0000</pubDate>
      <link>https://dev.to/vijay_kanna_56/how-to-resize-passport-photos-signatures-for-government-exam-applications-5b73</link>
      <guid>https://dev.to/vijay_kanna_56/how-to-resize-passport-photos-signatures-for-government-exam-applications-5b73</guid>
      <description>&lt;p&gt;If you're applying for government jobs, banking exams, railway recruitment, university admissions, or competitive exams like SSC, SBI, UPSC, RRB, or IBPS, you've probably encountered strict image upload requirements.&lt;/p&gt;

&lt;p&gt;Most application portals require passport photos and signatures to meet specific file size, dimensions, and format requirements. Preparing these files correctly before uploading can save time and help avoid application rejections.&lt;/p&gt;

&lt;p&gt;** Why Image Resizing Matters**&lt;/p&gt;

&lt;p&gt;Many online application portals reject uploaded images because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The file size exceeds the allowed limit.&lt;/li&gt;
&lt;li&gt;The image dimensions don't match the required specifications.&lt;/li&gt;
&lt;li&gt;The file format isn't supported.&lt;/li&gt;
&lt;li&gt;Compression reduces image quality too much.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of repeatedly uploading and getting errors, it's easier to prepare the image correctly beforehand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resize Images in Your Browser&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I built &lt;strong&gt;ResizeHub.in&lt;/strong&gt; to help users resize and compress images directly in the browser while keeping their files private. It supports popular image formats, including &lt;strong&gt;JPG, PNG, WEBP, AVIF, and HEIC&lt;/strong&gt;, making it useful for both exam applications and everyday image optimization.&lt;/p&gt;

&lt;p&gt;** How to Resize Your Photo or Signature**&lt;/p&gt;

&lt;p&gt;The process is simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Upload your passport photo or signature.&lt;/li&gt;
&lt;li&gt;Select the required dimensions or file size.&lt;/li&gt;
&lt;li&gt;Preview the result before downloading.&lt;/li&gt;
&lt;li&gt;Download the optimized image.&lt;/li&gt;
&lt;li&gt;Upload the image to your application portal.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This usually takes less than a minute.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which Image Format Should You Use?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;| Format | Best For                                    |&lt;/p&gt;

&lt;p&gt;| JPG    | Passport photos                             |&lt;br&gt;
| PNG    | Signatures and transparent images           |&lt;br&gt;
| WEBP   | Websites and faster loading                 |&lt;br&gt;
| AVIF   | High-quality images with smaller file sizes |&lt;br&gt;
| HEIC   | Photos taken on iPhones                     |&lt;/p&gt;

&lt;p&gt;Choosing the right format can help maintain image quality while meeting upload requirements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Privacy Matters&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Government applications often require personal documents such as passport photographs and signatures. Using tools that process images directly in your browser can help reduce the need to upload these files to external servers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final Thoughts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Preparing your images correctly before submitting an application can save time and reduce the chances of upload errors.&lt;/p&gt;

&lt;p&gt;If you're looking for a browser-based image tool that helps resize, compress, crop, and convert images while keeping your files on your device, you can try &lt;strong&gt;ResizeHub&lt;/strong&gt; at &lt;strong&gt;&lt;a href="https://resizehub.in" rel="noopener noreferrer"&gt;https://resizehub.in&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
