اکثر اپلیکیشنهای وب هنوز با آپلود تصویر مانند یک جعبه سیاه برخورد میکنند. کاربر فایلی را رها میکند، مرورگر آن را ارسال میکند و سرور یا محتوا را میپذیرد یا خطای 413 میدهد که هیچکس برای آن آماده نیست. فشردهسازی سمت مرورگر معادله را تغییر میدهد. این کار به شما فرصت میدهد تا حجم دادهها را قبل از ارسال کاهش دهید، که به معنای آپلود سریعتر، هزینهی پهنای باند کمتر و تایماوتهای کمتر سرور است. اما انجام این کار میتواند به راحتی اشتباه پیش برود. اگر با فشردهسازی مانند یک اسلایدر جادویی با برچسب "quality" برخورد کنید، تصاویر خراب، تامنیلهای کشیده شده و تجربهی کاربری گیجکنندهای تحویل خواهید داد. رویکرد بهتر این است که با کل این فرآیند مانند یک خط لوله (pipeline) برخورد کنید.
به جای اسلایدر، به خط لوله فکر کنید
وظیفه را به مراحل مجزا تقسیم کنید. فایل را از عنصر input بخوانید. اندازه تصویر را به ابعاد هدف خود کاهش دهید. یک Blob تازه کدگذاری (encode) کنید. سپس نتیجه را دوباره به کاربر نمایش دهید. هر مرحله یک کار انجام میدهد و خروجی خود را به مرحله بعد میفرستد. این جداسازی فقط برای تمیزتر شدن کد نیست؛ بلکه تست واحد (unit testing) را ساده میکند. شما میتوانید یک بافر مشخص را بدون دست زدن به عنصر input فایل، به مرحله تغییر اندازه بدهید. میتوانید تأیید کنید که انکودر شما یک JPEG زیر ۲۰۰ کیلوبایت تولید میکند، بدون اینکه منتظر رفت و برگشت به سرور بمانید. وقتی چیزی خراب میشود، دقیقاً میدانید کدام مرحله با شکست مواجه شده است.
جداسازی این وظایف همچنین از غافلگیریهای حین آپلود جلوگیری میکند. اگر تغییر اندازه و کدگذاری را در یک تابع درهمتنیده قرار دهید، یک خطای رمزگشایی (decoding) در میانه راه ممکن است صف آپلود شما را در وضعیتی ناسازگار رها کند. یک خط لوله شما را مجبور میکند در هر مرز، دادهها را اعتبارسنجی کنید. اگر فایل قابل رمزگشایی نباشد، قبل از اینکه حتی یک canvas ایجاد کنید، متوجه آن میشوید. اگر Blob کدگذاری شده خیلی بزرگ باشد، قبل از اینکه از سرور بخواهید آن را ذخیره کند، متوجه میشوید.
قبل از نوشتن کد، یک قرارداد تعریف کنید
قبل از اینکه کسی دستور canvas draw را بنویسد، قوانین را یادداشت کنید و آنها را با تیم به اشتراک بگذارید. انواع MIME پذیرفته شده را انتخاب کنید. آیا JPEG، PNG، WebP یا AVIF را اجازه میدهید؟ هر کدام پیامدهایی برای کانالهای آلفا، پشتیبانی مرورگر و هزینهی پردازنده (CPU) دارند. حداکثر اندازه ورودی را تعیین کنید. یک عکس خام ۳۰ مگابایتی از یک گوشی پرچمدار، اگر سعی کنید آن را کاملاً در حافظه رمزگشایی کنید، یک لپتاپ قدیمی را منجمد یا کرش میدهد. حداکثر ابعاد خروجی را تعریف کنید. اگر رابط کاربری شما هرگز تصاویری با عرض بیش از ۲۰۴۸ پیکسل را نمایش نمیدهد، دلیلی ندارد اجازه دهید عکسی با عرض ۶۰۰۰ پیکسل از خط لوله عبور کند.
مهمتر از همه، برای شکست در رمزگشایی برنامهریزی کنید. یک فایل خراب، یک پروفایل رنگی عجیب یا یک آپلود ناقص میتواند باعث خطا در سازنده Image شود. خط لوله شما به یک بلوک catch مشخص و یک پیام خطای قابل فهم برای انسان نیاز دارد. اجازه ندهید مرورگر بیصدا از کار بیفتد و کاربر را در حالی که هیچ اتفاقی نمیافتد، در تماشای یک نشانگر انتظار (spinner) رها کند.
به تصویر احترام بگذارید
اعوجاج (Distortion) غیرحرفهای به نظر میرسد. نسبت ابعاد (aspect ratio) را حفظ کنید و طولانیترین ضلع را محدود کنید. اگر کادر هدف شما ۱۰۲۴ در ۱۰۲۴ پیکسل است، یک عکس ۴۰۰۰ در ۳۰۰۰ باید به صورت ۱۰۲۴ در ۷۶۸ درآید، نه ۱۰۲۴ در ۱۰۲۴. ضریب مقیاس را از لبه بلندتر محاسبه کنید و اجازه دهید لبه کوتاهتر از آن پیروی کند. این کار از کشیده شدن تصاویر به اشکال عجیب جلوگیری میکند.
برای خروجی گرفتن واقعی، از متد toBlob در canvas استفاده کنید. این متد کنترل مستقیمی بر فرمت خروجی و تنظیمات کیفیت به شما میدهد و به صورت ناهمگام (asynchronously) اجرا میشود تا رشته اصلی (main thread) را مسدود نکند. یک offscreen canvas ایجاد کنید، تصویر تغییر اندازه یافته را روی آن بکشید، سپس canvas.toBlob را با نوع و مقدار کیفیت مورد نظر خود فراخوانی کنید. آن Blob جدید همان چیزی است که به منطق آپلود یا API ذخیرهسازی خود میدهید.
شواهد را نشان دهید
فشردهسازی کاری نامرئی است. اگر اعداد را نمایش ندهید، کاربران به فرآیند اعتماد نخواهند کرد. رابط کاربریای بسازید که به آنها اجازه دهد نسخه اصلی را با نتیجه مقایسه کنند. اندازه فایل اصلی، اندازه فایل جدید، ابعاد جدید و نوع فرمت نهایی را نمایش دهید. دیدن اینکه یک عکس گوشی ۴.۲ مگابایتی به یک WebP ۳۸۰ کیلوبایتی تبدیل شده است، این ترس را از بین میبرد که شما مخفیانه در حال خراب کردن تصویر آنها هستید.
این شفافیت همچنین به عیبیابی کمک میکند. وقتی کاربری شکایت میکند که آپلود با شکست مواجه شده است، اولین چیزی که بررسی خواهید کرد این است که آیا ابعاد خروجی از محدودیت سرور شما فراتر رفته است، یا اینکه فرمت از PNG به JPEG تغییر کرده و کانال آلفا را از دست داده است. آن دادهها را در رابط کاربری قرار دهید تا کاربر بتواند قبل از باز کردن تیکت پشتیبانی، خودش مشکل را تشخیص دهد.
پیشتنظیمها بهتر از فشردهسازی مجدد هستند
هرگز یک تصویر را دو بار فشرده نکنید. هر بار عبور از یک انکودر با اتلاف (lossy encoder)، جزئیات بیشتری را از بین میبرد و آرتیفکتهای بلوکی ایجاد میکند. اگر به کاربر اجازه دهید مکرراً روی "optimize" کلیک کند، نسل سوم تصویر شبیه به کپی از یک کپی خواهد بود. در عوض، هر خروجی را از فایل منبع اصلی تولید کنید و پیشتنظیمها (presets) را ارائه دهید:
- Smaller file: Push quality lower and cap dimensions aggressively for thumbnails or fast previews.
- Balanced: Target a moderate quality level with sensible dimensions, suitable for social feeds and galleries.
- More detail: Keep quality high and preserve larger dimensions for photography, artwork, or print previews.
Store the original Blob in memory so the user can switch between presets without stacking generations of loss. Always generate from the source, never from the last output.
Test Like Your Users Upload
Your development machine with a fiber connection and 32 GB of RAM is not reality. Test with the actual files real users carry. Phone photos from iOS and Android use different metadata orientations and may originate from HEIC sources. Transparent assets such as logos and icons behave differently under JPEG conversion because JPEG simply does not support alpha channels. Huge files will expose memory limits on devices with 2 GB of RAM. Slow mobile CPUs will reveal exactly how long that toBlob call really takes.
Use Chrome DevTools to throttle the CPU and network. Try a five-year-old Android phone. If your pipeline locks the UI for three seconds while encoding, you need to move the heavy work into a Web Worker so the interface stays responsive.
Ship the Basics First
It is tempting to support every format and
