সরাসরি পর্বে যান
পর্ব ৩২ · ৫৯-এর মধ্যে · মডিউল ৫

রেসপন্সিভ ছবি — srcset, sizes, picture আর lazy loading

Responsive Images — srcset, sizes, picture, modern formats, lazy loading and CSS images

Taught in Bangla, this lesson covers responsive images: srcset with x and w descriptors, how sizes helps the browser choose, picture with media for art direction and type for AVIF and WebP fallbacks, loading, fetchpriority and decoding, CSS tools such as object-fit, aspect-ratio and image-set(), responsive SVG, and image tooling, CDNs and checking currentSrc in DevTools.

এই পর্বে যা শিখবেন

  • রেসপন্সিভ ছবির তিনটি আলাদা সমস্যা — মাপ, কাটা (crop) আর ফরম্যাট — আর কোনটির জন্য কোন হাতিয়ার
  • srcset-এর x আর w descriptor, sizes, আর ব্রাউজারের বাছাইয়ের পুরো হিসাব
  • <picture> আর <source> দিয়ে art direction ও AVIF/WebP fallback
  • loading, fetchpriority, decoding — কোন ছবি কখন আনা উচিত
  • object-fit, object-position, aspect-ratio, image-set(), রেসপন্সিভ SVG, ছবির টুল আর যাচাইয়ের পদ্ধতি

১ · সমস্যাটা কী — একটি ছবি, হাজার রকম পর্দা

ধরুন আপনার একটি অনলাইন শাড়ির দোকান আছে। ফটোগ্রাফার দামি ক্যামেরায় ছবি তুলে দিয়েছেন — প্রতিটি ছবি ৩০০০ পিক্সেল চওড়া, ফাইল কয়েক মেগাবাইট। আপনি সেই ফাইলই সাইটে তুলে দিলেন। বড় মনিটরে দারুণ দেখাচ্ছে।

এবার একজন ক্রেতার কথা ভাবুন। তিনি গ্রামের বাজারে বসে একটি কম দামের ফোনে, মোবাইল ডেটার ছোট প্যাকেজে আপনার সাইট খুলেছেন। তাঁর ফোনের পর্দায় ছবিটি দেখাবে বড়জোর কয়েকশো পিক্সেল চওড়ায়। অথচ ফোনটিকে নামাতে হচ্ছে পুরো ৩০০০ পিক্সেলের ফাইল। ফল তিনটি: পাতা খুলতে অনেক সময় লাগে, তাঁর কেনা ডেটার একটা বড় অংশ এক পাতাতেই শেষ হয়, আর দুর্বল ফোনটি বিশাল ছবি খুলতে গিয়ে গরম হয়, আটকে যায়। দশটি পণ্যের পাতায় দশ গুণ। অনেক ক্রেতা অপেক্ষা না করে চলে যান।

দর্জির দোকানে একজন বাচ্চার জামা বানাতে কেউ বড়দের পুরো থান কাপড় কেটে দেয় না। মাপ নিয়ে যতটুকু লাগে ততটুকুই কাটে। রেসপন্সিভ ছবির মূল কথাও তাই: যে পর্দায় যতটুকু দরকার, ঠিক ততটুকু পিক্সেল পাঠান — বেশিও না, কমও না।

উল্টো দিকেও সমস্যা আছে। শুধু ছোট ফাইল দিলে আধুনিক ফোনে ছবি ঝাপসা লাগে। কারণ বেশিরভাগ ফোনের পর্দা খুব ঘন: একটি CSS pixel-এর জায়গায় সেখানে ২টি বা ৩টি আসল বিন্দু থাকে। এই অনুপাতের নাম device pixel ratio (DPR). ৩৬০ CSS পিক্সেল চওড়া একটি ফোনের DPR ৩ হলে পুরো চওড়া ছবিকে ঝকঝকে দেখাতে লাগে ৩৬০ × ৩ = ১০৮০টি আসল পিক্সেল। পর্ব ২০-এ একক নিয়ে যা দেখেছেন, এখানে সেটাই টাকার হিসাব হয়ে ফিরে আসছে।

রেসপন্সিভ ছবি আসলে তিনটি আলাদা সমস্যার নাম। প্রতিটির হাতিয়ার আলাদা — এই পার্থক্যটা মাথায় রাখলে বাকি পর্ব সহজ হয়ে যাবে:

📐 মাপ বদল (resolution switching)

একই ছবি, শুধু ছোট-বড় কয়েকটি ফাইল। কোনটি নামাবে, ব্রাউজার নিজে ঠিক করবে। হাতিয়ার: srcset আর sizes.

পণ্যের ছবি, খবরের ছবি

✂️ ছবির কাটা বদল (art direction)

ফোনে আলাদা করে কাটা বা আলাদা নকশার ছবি — চওড়া ব্যানারের বদলে কাছ থেকে কাটা অংশ। আপনি আদেশ দেন, ব্রাউজার মানে। হাতিয়ার: <picture> + media.

hero ব্যানার, বিজ্ঞাপন

🗂️ ফরম্যাট বদল (format switching)

যে ব্রাউজার AVIF বা WebP চেনে তাকে ছোট আধুনিক ফাইল, বাকিদের JPEG. হাতিয়ার: <picture> + type.

সব ফটো
মূল ধারণা

ব্রাউজার ছবি নামানো শুরু করে পাতার লেআউট জানার অনেক আগে। HTML আসতে না আসতেই তার একটি অংশ, preload scanner, সামনে উঁকি দিয়ে <img> খুঁজে ফাইল চাইতে শুরু করে — CSS তখনো আসেনি, ছবির ঘর কত চওড়া হবে কেউ জানে না। তাই সঠিক ফাইল বাছার জন্য যা যা জানা দরকার, সেটা HTML-এর ভেতরেই লিখে দিতে হয়। এ কারণেই রেসপন্সিভ ছবি CSS-এর কাজ নয়, HTML-এর কাজ। এই পর্বের প্রায় প্রতিটি নিয়ম এই একটি সত্য থেকে আসে।

<img>-এর ভিত্তি — src, alt, width/height আর ফরম্যাট বাছাই — আগেই পর্ব ০৬-এ দেখেছেন। এখানে আমরা সেই ভিত্তির ওপর দাঁড়িয়ে দেখব, কীভাবে একটি ছবিকে সব পর্দার জন্য মানানসই করা যায়।

২ · srcset আর x descriptor — নির্দিষ্ট মাপের ছবি, ঝকঝকে সব পর্দায়

সবচেয়ে সহজ ক্ষেত্র দিয়ে শুরু করি: এমন ছবি, যার চওড়া পর্দাভেদে বদলায় না। যেমন মন্তব্যের পাশে ৬৪ পিক্সেলের প্রোফাইল ছবি, বা হেডারে ১৬০ পিক্সেলের লোগো। এখানে একটাই প্রশ্ন: পর্দাটি কতটা ঘন? সাধারণ পর্দায় ৬৪ পিক্সেলের ফাইলই যথেষ্ট; DPR ২-এর ফোনে লাগবে ১২৮ পিক্সেলের ফাইল।

এর জন্য আছে srcset attribute. এর ভেতরে কমা দিয়ে আলাদা করে কয়েকটি ফাইল লেখা হয়, প্রতিটির পাশে একটি x descriptor: 1x মানে “সাধারণ পর্দার জন্য”, 2x মানে “দ্বিগুণ ঘন পর্দার জন্য”।

<img src="avatar-64.jpg"
     srcset="avatar-64.jpg 1x, avatar-128.jpg 2x, avatar-192.jpg 3x"
     width="64" height="64" alt="রহিমা আক্তার">

খেয়াল করুন, src রয়ে গেছে। যে খুব পুরোনো ব্রাউজার srcset চেনে না, সে src নেয়; আর আধুনিক ব্রাউজারের কাছে src হলো 1x-এর বিকল্প। width="64" লেখা থাকায় ফাইল যত বড়ই হোক, ছবি দেখাবে ৬৪ CSS পিক্সেলেই — বাড়তি পিক্সেল যায় ধারালো ভাবের জন্য, আকারের জন্য নয়।

নিচের এডিটরে চেনার সুবিধার জন্য ১x আর ২x-এ দুটি আলাদা প্রচ্ছদ দেওয়া হয়েছে (বাস্তবে এরা একই ছবির দুই মাপ হতো)। আপনার পর্দায় কোনটি এল দেখুন:

<img src="../image/c-programming-cover.jpg"
     srcset="../image/c-programming-cover.jpg 1x,
             ../image/python-programming-cover.jpg 2x"
     width="300" height="158"
     alt="কোর্সের প্রচ্ছদ">

<p>devicePixelRatio: <b id="dpr"></b> · এসেছে: <code id="file">…</code></p>
<script>
  var img = document.querySelector('img');
  function show() {
    document.getElementById('dpr').textContent = devicePixelRatio;
    document.getElementById('file').textContent = img.currentSrc.split('/').pop();
  }
  img.complete ? show() : img.addEventListener('load', show);
</script>

সাধারণ ল্যাপটপ-মনিটরে (DPR ১) আসবে c-programming-cover.jpg (ল্যাপটপের ছবি); বেশিরভাগ ফোনে বা ঘন পর্দার ল্যাপটপে (DPR ২ বা ৩) আসবে python-programming-cover.jpg — নিচের লাইনে ফাইলের নাম দেখাবে, যা পাওয়া যায় img.currentSrc থেকে (সেকশন ৮-এ বিস্তারিত)। দুই ক্ষেত্রেই ছবির আকার ৩০০×১৫৮ — বদলায় শুধু কোন ফাইল নামল। চেষ্টা করুন: ব্রাউজারে Ctrl + + চেপে zoom ২০০% করুন, তারপর এডিটরের লেখায় যেকোনো একটি অক্ষর বদলে আবার চালান — DPR এখন ২, তাই Python-এর ফাইল আসা উচিত। (Zoom বাড়ালে DPR-ও বাড়ে, কারণ একটি CSS পিক্সেল তখন বেশি আসল বিন্দু জুড়ে আঁকা হয়।)

Retina শব্দটি এসেছে ২০১০ সালের iPhone 4 থেকে — Apple তার দ্বিগুণ ঘন পর্দার এই নাম দেয়। হঠাৎ করে ওয়েবের সব ছবি সেই ফোনে ঝাপসা দেখাতে লাগল, কারণ ১টি পিক্সেলের তথ্য দিয়ে ৪টি বিন্দু (২×২) ভরাতে হচ্ছিল। ২০১২ সালে Apple-এর একজন প্রকৌশলী WHATWG-তে srcset-এর প্রস্তাব দেন — শুরুতে শুধু এই x descriptor নিয়েই। পরের অংশে দেখবেন, শুধু x দিয়ে কেন সব সমস্যা মেটেনি।

৩ · w descriptor আর sizes — ব্রাউজার কীভাবে ছবি বাছে

বেশিরভাগ ছবি কিন্তু নির্দিষ্ট মাপের নয়। খবরের সাইটে মূল ছবি ফোনে পুরো চওড়া, ট্যাবলেটে অর্ধেক, ডেস্কটপে এক-তৃতীয়াংশ। এখানে x descriptor অচল, কারণ ছবির ঘর নিজেই বদলাচ্ছে — ঘনত্ব জানলেও কাজ হয় না, ঘরের চওড়াও জানা দরকার।

সমাধান দুই ভাগে। প্রথম ভাগ: w descriptor দিয়ে প্রতিটি ফাইলের আসল চওড়া জানান — saree-800.jpg 800w মানে “এই ফাইলটি ৮০০ পিক্সেল চওড়া”। দ্বিতীয় ভাগ: sizes attribute দিয়ে জানান, পাতায় ছবির ঘর কত চওড়া হবে। এই দুটি তথ্য পেলে ব্রাউজার নিজেই অঙ্ক কষে নেয়। এই কৌশলের নাম resolution switching.

<img src="saree-800.jpg"
     srcset="saree-400.jpg 400w,
             saree-800.jpg 800w,
             saree-1600.jpg 1600w"
     sizes="(min-width: 900px) 33vw,
            100vw"
     width="1600" height="2000"
     alt="লাল পাড়ের সাদা জামদানি শাড়ি">

sizes পড়ার নিয়ম: কমা দিয়ে আলাদা করা কয়েকটি জোড়া, প্রতিটিতে একটি media শর্ত আর একটি চওড়া। ব্রাউজার বাঁ থেকে ডানে পড়ে, প্রথম যে শর্তটি মেলে সেটির চওড়া নেয়; শেষেরটির কোনো শর্ত নেই — সেটাই ডিফল্ট। ওপরের কোডের মানে: “viewport অন্তত ৯০০ পিক্সেল চওড়া হলে ছবির ঘর viewport-এর ৩৩%, নইলে পুরো viewport”. এখন ধাপে ধাপে ব্রাউজারের মাথার ভেতরে ঢুকি:

1<img src="saree-800.jpg"2     srcset="saree-400.jpg 400w,3             saree-800.jpg 800w,4             saree-1600.jpg 1600w"5     sizes="(min-width: 900px) 33vw,6            100vw"7     alt="জামদানি শাড়ি">
ব্রাউজারের খাতা
ডিভাইস
ঘরের চওড়া
দরকারি পিক্সেল
  1. তালিকা পড়া। ব্রাউজার srcset থেকে জানল তিনটি ফাইল আছে — ৪০০, ৮০০ আর ১৬০০ পিক্সেল চওড়া। সে এখনো কোনোটি নামায়নি। ধরা যাক যন্ত্রটি একটি ফোন: viewport ৩৯০ পিক্সেল, DPR ২।
  2. প্রথম শর্ত। (min-width: 900px) — viewport কি অন্তত ৯০০? ৩৯০ মোটেই নয়। শর্ত মেলেনি, তাই 33vw বাদ। ব্রাউজার পরের জোড়ায় যায়।
  3. ডিফল্ট চওড়া। শেষ জোড়ায় কোনো শর্ত নেই: 100vw, অর্থাৎ ৩৯০ পিক্সেল। এখন ব্রাউজার জানে, লেআউট না দেখেও, ছবির ঘর ৩৯০ CSS পিক্সেল চওড়া হবে — কারণ আপনি বলে দিয়েছেন।
  4. ঘনত্ব দিয়ে গুণ। DPR ২, তাই ঝকঝকে দেখাতে লাগবে ৩৯০ × ২ = ৭৮০টি আসল পিক্সেল। তালিকায় ৭৮০-এর সমান বা তার চেয়ে বড় সবচেয়ে ছোট ফাইল ৮০০w. ফোনটি নামাল saree-800.jpg — ১৬০০-এর ফাইলের প্রায় এক-চতুর্থাংশ পিক্সেল।
  5. এবার ল্যাপটপ। viewport ১৩৬৬, DPR ১। প্রথম শর্তই মিলেছে: ঘরের চওড়া ১৩৬৬-এর ৩৩% ≈ ৪৫১ পিক্সেল। দ্বিতীয় জোড়া আর পড়া হয় না — প্রথম মিল-ই চূড়ান্ত।
  6. ল্যাপটপের বাছাই। ৪৫১ × ১ = ৪৫১ পিক্সেল দরকার। ৪০০w একটু ছোট, তাই সাধারণত ৮০০w আসে। মজার ব্যাপার: বড় পর্দার ল্যাপটপ আর ছোট ফোন এখানে একই ফাইল পেল — কারণ ফোনের পর্দা ঘন, আর ল্যাপটপে ছবির ঘর ছোট। x descriptor দিয়ে এই হিসাব করাই যেত না।

ব্রাউজারের হাতে শেষ সিদ্ধান্ত। HTML স্ট্যান্ডার্ড বলে, srcset হলো পরামর্শ: ব্রাউজার চাইলে ধীর নেটওয়ার্কে বা “ডেটা সেভার” চালু থাকলে ছোট ফাইল নিতে পারে, আর বড় ফাইল আগে থেকেই cache-এ থাকলে সেটাই রেখে দিতে পারে। তাই “এই পর্দায় ঠিক এই ফাইলটাই আসবে” — এমন ধরে নিয়ে কখনো কোড লিখবেন না। প্রতিটি ফাইল যেন একই ছবির একই কাটা হয়, শুধু মাপ আলাদা।

এবার নিজে দেখুন। আবার চেনার জন্য তিনটি আলাদা প্রচ্ছদ: C = ৪০০w, Python = ৮০০w, Java = ১৬০০w. CSS দিয়ে ছবিকে প্রিভিউর অর্ধেক চওড়ায় রাখা হয়েছে, তাই sizes="50vw" সত্যি কথাই বলছে:

<style>
  img { width: 50%; height: auto; }
</style>
<img src="../image/python-programming-cover.jpg"
     srcset="../image/c-programming-cover.jpg 400w,
             ../image/python-programming-cover.jpg 800w,
             ../image/java-programming-cover.jpg 1600w"
     sizes="50vw"
     width="1200" height="630" alt="কোর্সের প্রচ্ছদ">
<p>এসেছে: <code id="file">…</code></p>
<script>
  var img = document.querySelector('img');
  function show() {
    document.getElementById('file').textContent = img.currentSrc.split('/').pop();
  }
  img.complete ? show() : img.addEventListener('load', show);
</script>

বড় পর্দায় প্রিভিউ প্রায় ৯০০–১০০০ পিক্সেল চওড়া, তাই ঘর প্রায় ৪৫০ পিক্সেল; DPR ১ হলে দরকার ~৪৫০ পিক্সেল, ফলে আসে python-programming-cover.jpg (৮০০w). ঘন পর্দার ফোনেও সাধারণত এটিই আসে, কারণ সেখানে প্রিভিউ সরু। চেষ্টা করুন: sizes-কে 200vw করুন — এখন ব্রাউজার ভাবছে ঘর অনেক চওড়া, তাই java-programming-cover.jpg আসবে, যদিও ছবি দেখাচ্ছে একই আকারে। অর্থাৎ sizes শুধু বাছাইয়ের হিসাব; আসল আকার ঠিক করে CSS. তারপর sizes-কে 10vw করে দেখুন — হয়তো Java-ই রয়ে যাবে! কারণ বড় ফাইলটি এখন cache-এ আছে, আর ব্রাউজার বড় ভালো ফাইল থাকতে ছোটটি আবার নামানোর দরকার দেখে না।

sizes লেখার নিয়ম আর ফাঁদ

  • sizes না দিলে ধরা হয় 100vw. সাইডবারের ছোট ছবিতে sizes ভুলে গেলে ব্রাউজার ভাববে ছবি পুরো পর্দা জুড়ে, আর অকারণে বড় ফাইল নামাবে — খুব সাধারণ ভুল।
  • sizes-এ % চলে না। শতাংশ কিসের — ঘরের মা-বাবার? সেটা জানতে লেআউট লাগে, যা তখনো হয়নি। তাই vw, px, em বা calc(100vw - 2rem) লিখুন।
  • শর্তের ক্রম জরুরি। প্রথম মিলই জেতে, তাই বড় min-width আগে লিখুন। পর্ব ৩১-এর media query-র জ্ঞান এখানে সরাসরি কাজে লাগে।
  • একই srcset-এ x আর w মেশানো যায় না। হয় সব x, নয় সব w. আর w ব্যবহার করলে sizes দিন।
  • মোটামুটি ঠিক হলেই চলে। sizes আপনার CSS-এর আন্দাজ মাত্র, পিক্সেল-নিখুঁত হওয়ার দরকার নেই। তবে CSS বদলালে sizes-ও বদলাতে মনে রাখুন — দুটি আলাদা জায়গায় একই তথ্য, এটাই এই পদ্ধতির দুর্বলতা।

কয়টি মাপ বানাবেন? কোনো জাদুর সংখ্যা নেই। সাধারণ অভ্যাস হলো ৩ থেকে ৬টি ধাপ, যেমন ৪০০, ৮০০, ১২০০, ১৬০০, যাতে পাশাপাশি দুটি ফাইলের আকারের পার্থক্য খুব বেশি না হয়। সবচেয়ে বড়টি হোক “বড় পর্দায় ঘরের চওড়া × ২” — এর বেশি সাধারণত চোখে ধরা পড়ে না। নতুন একটি মান আছে: lazy ছবিতে sizes="auto" লিখলে ব্রাউজার লেআউট দেখেই চওড়া নেয়, কারণ lazy ছবি তো লেআউটের পরেই আনা হয়। এটি Chromium-ভিত্তিক ব্রাউজারে এসেছে, কিন্তু এখনো Baseline নয় — তাই sizes="auto, (min-width: 900px) 33vw, 100vw" লিখুন, যে ব্রাউজার auto চেনে না সে বাকি অংশ নেবে।

৪ · picture আর media — ফোনের জন্য আলাদা করে কাটা ছবি (art direction)

বৈশাখী মেলার একটি ব্যানারের কথা ভাবুন: চওড়া ছবি, বাঁ দিকে নাগরদোলা, মাঝখানে লোকজন, ডান কোণে ছোট্ট একটি মাটির পুতুলের দোকান। ডেস্কটপে চমৎকার। ফোনে সেই একই ছবি ছোট হয়ে এমন ফিতার মতো হয় যে কিছুই বোঝা যায় না। এখানে ছোট ফাইল দিয়ে লাভ নেই — দরকার অন্যভাবে কাটা ছবি: ফোনে শুধু পুতুলের দোকানের কাছ থেকে তোলা অংশ, প্রায় বর্গাকার। এর নাম art direction — পত্রিকার আর্ট ডিরেক্টর যেমন পাতার মাপ দেখে ছবি কাটেন।

এর জন্য আছে <picture> element. এর ভেতরে কয়েকটি <source> আর শেষে একটি <img>. প্রতিটি <source>-এর media শর্ত থাকে; ব্রাউজার উপর থেকে নিচে পড়ে, প্রথম যে source-এর শর্ত মেলে সেটির srcset নেয়। কোনোটি না মিললে <img>-এর নিজের src/srcset.

<picture>
  <!-- ফোনে: শুধু লোগো, যাতে পড়া যায় -->
  <source media="(max-width: 600px)"
          srcset="../logo.webp"
          width="3162" height="1137">
  <!-- বাকি সবখানে: পুরো ব্যানার -->
  <img src="../image/ABCL-TECH-Cover.jpg"
       width="1280" height="720"
       style="max-width: 100%; height: auto"
       alt="ABCL TECH — বাংলায় প্রযুক্তি শিক্ষা">
</picture>

প্রিভিউ ৬০০ পিক্সেলের বেশি চওড়া হলে (সাধারণ ল্যাপটপে তাই হয়) পুরো ব্যানার দেখবেন; ফোনে, যেখানে প্রিভিউ সরু, দেখবেন শুধু লোগো। চেষ্টা করুন: max-width: 600px-কে 2000px করুন — এখন শর্ত মিলে গেছে, তাই বড় পর্দাতেও লোগো আসবে। তারপর পুরো <img> অংশটি মুছে দেখুন: কিছুই দেখাবে না! <source> নিজে কিছু আঁকে না; সে শুধু <img>-কে বলে দেয় কোন ফাইল নিতে হবে।

ওপরের এডিটরে লক্ষ করার মতো তিনটি নিয়ম:

  • <source>-এ src নয়, srcset. (<video>-এর source-এ ছিল src — দুটো গুলিয়ে ফেলা খুব সাধারণ ভুল।) প্রতিটি source-এর srcset-এ আবার w descriptor আর sizes-ও দেওয়া যায়, অর্থাৎ art direction-এর সাথে মাপ বদলও একসাথে চলে।
  • alt, loading, fetchpriority, class, style — সব <img>-এ। পর্দায় আঁকা হয় আসলে ওই <img>-টিই; <picture> শুধু একটি মোড়ক। তাই CSS-এ ছবির মাপ দিতে চাইলে picture img লিখুন।
  • source-এ width/height. ফোনের কাটা ছবির অনুপাত আলাদা হলে <source>-এ নিজের width/height দিন, যাতে সেখানেও জায়গা আগে থেকে ঠিক থাকে (CLS না হয়)। সব প্রধান ব্রাউজারের নতুন সংস্করণে এটি কাজ করে; যে ব্রাউজার চেনে না, সে <img>-এর মাপ নেয়।
মূল ধারণা

srcset হলো পরামর্শ — ব্রাউজার নিজের বুদ্ধিতে যেকোনোটি নিতে পারে, কারণ সব ফাইল একই ছবি। <source media> হলো আদেশ — শর্ত মিললে ব্রাউজার ওই source নিতে বাধ্য, কারণ ছবিগুলো এখন আর একই নয়। তাই শুধু মাপ বদলের জন্য <picture> ব্যবহার করবেন না; সেখানে ব্রাউজারের বুদ্ধি (ধীর নেটওয়ার্কে ছোট ফাইল, cache-এর ফাইল) কাজে লাগানোর সুযোগ হারান।

৫ · picture আর type — AVIF থেকে WebP থেকে JPEG

পর্ব ০৬-এ দেখেছেন, AVIF আর WebP একই মানের ছবি প্রায়ই JPEG-এর চেয়ে অনেক ছোট ফাইলে দেয়। কিন্তু সব ব্রাউজার সব ফরম্যাট চেনে না — বিশেষ করে বহুদিন আপডেট না হওয়া পুরোনো ফোন। সমাধান: <source>-এ type attribute দিয়ে ফাইলের MIME type লিখে দেওয়া। ব্রাউজার ফাইল না নামিয়েই বুঝে ফেলে, “এই ফরম্যাট আমি চিনি কি না”।

<picture>
  <source type="image/avif" srcset="saree-800.avif 800w, saree-1600.avif 1600w" sizes="(min-width: 900px) 33vw, 100vw">
  <source type="image/webp" srcset="saree-800.webp 800w, saree-1600.webp 1600w" sizes="(min-width: 900px) 33vw, 100vw">
  <img src="saree-800.jpg" srcset="saree-800.jpg 800w, saree-1600.jpg 1600w" sizes="(min-width: 900px) 33vw, 100vw"
       width="1600" height="2000" alt="লাল পাড়ের সাদা জামদানি শাড়ি" loading="lazy">
</picture>
  1. <picture>source → source → imgউপর থেকে নিচে
  2. AVIF চিনি?type="image/avif"হ্যাঁ → এখানেই থামো
  3. WebP চিনি?type="image/webp"হ্যাঁ → এটি
  4. <img>saree-800.jpgশেষ ভরসা + আঁকার কাজ
  5. একটি requestimg.currentSrcবাকি ফাইল নামায় না
  1. মোড়ক খোলা। ব্রাউজার <picture>-এর ভেতরের <source>গুলো লেখার ক্রমে দেখে। প্রতিটির জন্য দুটি প্রশ্ন: media থাকলে সেটা মেলে কি না, আর type থাকলে সেই ফরম্যাট সে চেনে কি না। দুটোর উত্তর “হ্যাঁ” হলেই source জেতে।
  2. AVIF. আধুনিক Chrome, Edge, Firefox বা Safari এখানেই উত্তর পায় — AVIF চেনে, তাই এই source-এর srcset থেকে sizes মিলিয়ে ফাইল বাছে আর থেমে যায়। নিচের source বা img-এর ফাইলের দিকে আর তাকায়ই না। AVIF এখন Baseline (২০২৪ থেকে সব প্রধান ব্রাউজারে)।
  3. WebP. ধরুন কয়েক বছর আপডেট না হওয়া একটি ফোনের ব্রাউজার, যে AVIF চেনে না কিন্তু WebP চেনে। সে প্রথম source বাদ দিয়ে এখানে থামে। WebP অনেক বছর ধরেই Baseline Widely available.
  4. img — শেষ ভরসা। যে ব্রাউজার কোনো source-ই মেলাতে পারেনি, বা <picture> চেনেই না (একেবারে পুরোনো), সে <img>-এর JPEG নেয়। আর মনে রাখুন, যে ফাইলই জিতুক, পর্দায় আঁকে এই <img> — তাই alt আর বাকি attribute এখানেই।
  5. একটি মাত্র request. বাছাই শেষ হয় ফাইল নামানোর আগে; নামে কেবল একটি ফাইল। এর একটি কড়া ফল আছে: জেতা source-এর ফাইল সার্ভারে না থাকলে (404) ব্রাউজার পরের source-এ ফিরে যায় না — ভাঙা ছবি দেখায়। কোন ফাইল এল, JavaScript-এ জানা যায় img.currentSrc দিয়ে।

এবার এই সাইটের দুটি আসল ফাইল দিয়ে দেখি: logo.webp (WebP) আর logo-white.png (PNG). প্রথম source-এ ইচ্ছে করে একটি কাল্পনিক ফরম্যাট লেখা হয়েছে, যা কোনো ব্রাউজার চেনে না:

<picture>
  <source type="image/x-future" srcset="../image/course.jpg">
  <source type="image/webp" srcset="../logo.webp">
  <img id="logo" src="../image/logo-white.png" width="300"
       style="height: auto; background: #0d1f3c"
       alt="ABCL TECH লোগো">
</picture>
<p>যে ফাইলটি এসেছে: <code id="out">…</code></p>
<script>
  var logo = document.getElementById('logo');
  function show() {
    var file = logo.currentSrc.split('/').pop();
    document.getElementById('out').textContent = file;
    console.log('currentSrc: ' + file);
  }
  logo.complete ? show() : logo.addEventListener('load', show);
</script>

লোগো দেখা যাচ্ছে গাঢ় নীল পটভূমিতে (রংটি <img>-এর style-এ দেওয়া), আর নিচে লেখা logo.webp. প্রথম source-এর image/x-future ব্রাউজার চেনে না, তাই course.jpg নামানোই হয়নি — যদিও সেই ফাইলটি সার্ভারে আছে। চেষ্টা করুন: দ্বিতীয় <source> লাইনটি মুছে দিন — এবার আসবে logo-white.png — লোগোর চেহারা একটু বদলাতে পারে, তবে কোন ফাইল এল তা নিচের লেখাই নিশ্চিত করে বলবে। তারপর প্রথম source-এর type বদলে image/jpeg করুন — এবার ব্রাউজার “চিনি” বলবে, আর লোগোর জায়গায় কোর্সের প্রচ্ছদ আসবে। অর্থাৎ ব্রাউজার আপনার লেখা type-কে বিশ্বাস করে; ভুল type লিখলে ভুল ফাইল আসবে।

কোন ক্রমে সাজাবেন? সবচেয়ে ছোট ও নতুন ফরম্যাট উপরে, সবচেয়ে পুরোনো ও নিরাপদটি <img>-এ। যেখানে art direction আর format switching দুটোই লাগে, সেখানে source সংখ্যা গুণ হয়ে যায় (২টি কাটা × ৩টি ফরম্যাট = ৬টি source). এত লম্বা HTML হাতে লেখা কষ্টকর আর ভুলের ঝুঁকি বেশি — এ কারণেই বাস্তবে এই কোড প্রায়ই টুল বা ফ্রেমওয়ার্ক বানিয়ে দেয় (সেকশন ৮)।

৬ · কখন আনবেন — loading, fetchpriority আর decoding

কোন ফাইল আনবেন, তা ঠিক হলো। এবার প্রশ্ন: কখন আনবেন, আর কোনটি আগে? একটি পাতায় ৩০টি ছবি থাকলে সবগুলো একসাথে চাইলে ধীর নেটওয়ার্কে ভিড় লেগে যায় — ঢাকার জ্যামের মতো, যেখানে অ্যাম্বুলেন্সও আটকে থাকে। পাতার প্রথম পর্দার বড় ছবিটি (hero ছবি) হলো সেই অ্যাম্বুলেন্স: এটি যত দেরিতে আসে, LCP তত খারাপ।

কোনো নির্দেশ নেইসব ছবি একসাথে, সমান গুরুত্বে
সময় →         0s     1s     2s     3s
HTML           ██
hero.jpg         ░░░░░░████████        ← ভিড়ে আটকে
gallery 1–20     ██████████████        (নিচের ছবি, কেউ দেখছে না)
LCP                           ▲ ≈ 2.6s
সব ছবিতে loading="lazy"hero-সহ — ভুল
সময় →         0s     1s     2s     3s
HTML           ██
style.css        ███
layout              ▪              ← lazy ছবি এর আগে আনা হয় না
hero.jpg             ░░████████    ← দেরিতে শুরু
LCP                             ▲ ≈ 2.9s
hero: fetchpriority="high"বাকিরা: loading="lazy"
সময় →         0s     1s     2s     3s
HTML           ██
hero.jpg        ██████             ← preload scanner পেয়েই আগে আনে
gallery 1–3            ███         (পর্দার কাছে এলে)
gallery 4–20                       (স্ক্রল না করলে কখনোই না)
LCP                 ▲ ≈ 1.2s
ফলকম ডেটা, দ্রুত প্রথম পর্দা
              আগে        পরে
নামানো ছবি      21টি   →    1 + দেখা ছবিগুলো
LCP            ≈ 2.6s →    ≈ 1.2s
CLS            0     →    0       (সব ছবিতে width/height)
দর্শকের ডেটা    বেশি   →    অনেক কম
  1. কোনো নির্দেশ নেই। ব্রাউজার HTML-এ যত ছবি পায়, সবগুলো চাইতে থাকে। নিচের ২০টি গ্যালারির ছবি — যা দর্শক হয়তো দেখবেনই না — hero ছবির সাথে ব্যান্ডউইথ ভাগ করে নেয়। hero আসে দেরিতে, LCP প্রায় আড়াই সেকেন্ডের সীমা পেরিয়ে যায় (সংখ্যাগুলো বোঝানোর জন্য কাল্পনিক)।
  2. উল্টো ভুল — সবকিছু lazy. lazy ছবি আনার আগে ব্রাউজারকে জানতে হয় ছবিটি পর্দার কাছে কি না, আর সেটা জানতে CSS এসে লেআউট হওয়া পর্যন্ত অপেক্ষা করতে হয়। preload scanner-এর আগাম সুবিধা নষ্ট হয়। ফলে hero আরও দেরিতে আসে। AI আর পুরোনো প্লাগইনের খুব সাধারণ ভুল এটি।
  3. সঠিক ভাগ। hero ছবি স্বাভাবিক (eager), সাথে fetchpriority="high" — “এটা সবার আগে”। বাকি ছবিতে loading="lazy": পর্দার কাছাকাছি এলে তবেই আসে। কত কাছে এলে, সেই দূরত্ব ব্রাউজার নিজে ঠিক করে, নেটওয়ার্ক ধীর হলে আগেভাগে আনে।
  4. ফল। প্রথম পর্দা দ্রুত, দর্শকের ডেটা খরচ হয় কেবল যা দেখেছেন তার জন্য, আর width/height থাকায় দেরিতে আসা ছবিও লেখা নড়ায় না। Core Web Vitals-এর দুটি মাপ — LCP আর CLS — একসাথে ভালো হলো।
Attributeমানকাজসাপোর্ট
loadinglazy / eagerপর্দার কাছে এলে আনো / এখনই আনো (ডিফল্ট eager)Baseline Widely available
fetchpriorityhigh / low / autoঅন্য ফাইলের তুলনায় এই ছবি কতটা জরুরি — hero-তে high, ক্যারোসেলের লুকানো স্লাইডে lowBaseline Newly available (২০২৪ থেকে); না চিনলে চুপচাপ উপেক্ষা
decodingasync / sync / autoফাইল খুলে পিক্সেলে রূপান্তর (decode) বাকি কাজ আটকে রাখবে কি না — async মানে আলাদা করে করোBaseline Widely available; প্রভাব সাধারণত সামান্য
width/heightপিক্সেলে সংখ্যাআগেই অনুপাত জানিয়ে জায়গা রাখা — CLS ঠেকায় (পর্ব ০৬)সর্বত্র

নিচের এডিটরে একটি লম্বা পাতা: উপরে একটি সাধারণ ছবি, তারপর অনেক ফাঁকা জায়গা, একদম নিচে একটি lazy ছবি। console-এ চোখ রাখুন, তারপর প্রিভিউর ভেতরে নিচে স্ক্রল করুন:

<p>নিচে স্ক্রল করুন ↓ আর console দেখুন</p>
<img src="../image/course.jpg" alt="কোর্সের ছবি"
     width="240" height="134" fetchpriority="high">

<div style="height: 5000px; background: linear-gradient(#fff, #cbd5e1)"></div>

<img id="far" src="../image/vibe-coding.jpg" alt="ভাইব কোডিং"
     width="240" height="134" loading="lazy" decoding="async">
<p>পাতার শেষ</p>

<script>
  document.querySelectorAll('img').forEach(function (img) {
    var name = img.src.split('/').pop();
    function done() { console.log('এসেছে: ' + name); }
    img.complete ? done() : img.addEventListener('load', done);
  });
</script>

শুরুতে console-এ শুধু এসেছে: course.jpg. নিচের vibe-coding.jpg তখনো নামানোই হয়নি — দর্শক সেখানে যাননি, তাই তাঁর ডেটাও খরচ হয়নি। প্রিভিউর ভেতরে নিচে স্ক্রল করতে থাকুন: শেষ প্রান্তে পৌঁছানোর কিছু আগেই এসেছে: vibe-coding.jpg লাইনটি যোগ হবে। চেষ্টা করুন: loading="lazy" মুছে আবার চালান — এবার দুটি ছবিই শুরুতে আসবে। তারপর ফাঁকা জায়গার 5000px-কে 300px করে lazy ফিরিয়ে দিন — ছবিটি পর্দার কাছে বলে lazy থাকলেও সাথে সাথেই আসবে।

কোন ছবিতে lazy দেবেন না: প্রথম পর্দার hero ছবি, লোগো, আর যে ছবি সম্ভবত LCP হবে। কোনটি প্রথম পর্দায় পড়বে, ফোন আর ডেস্কটপে আলাদা হতে পারে — সন্দেহ থাকলে প্রথম এক-দুটি বড় ছবি eager রাখুন। আর CSS background-এর hero ছবি preload scanner দেখতে পায় না (CSS না আসা পর্যন্ত কেউ জানে না সেটি আছে); তেমন ক্ষেত্রে <link rel="preload" as="image" fetchpriority="high"> দিয়ে আগাম জানিয়ে দেওয়া যায় — এর imagesrcset আর imagesizes attribute দিয়ে srcset-এর মতো বিকল্পও দেওয়া যায়। সবচেয়ে সরল পথ অবশ্য hero ছবিকে <img> হিসেবেই রাখা।

৭ · CSS-এর দিক — object-fit, aspect-ratio, image-set() আর রেসপন্সিভ SVG

HTML ঠিক করে কোন ফাইল আসবে; CSS ঠিক করে সেটি পাতায় কেমন বসবে। রেসপন্সিভ লেআউটের প্রথম নিয়মটি আপনি পর্ব ৩০-এ দেখেছেন: img { max-width: 100%; height: auto; } — ছবি কখনো নিজের ঘরের চেয়ে চওড়া হবে না, আর উচ্চতা অনুপাত মেনে চলবে। এবার আরেক ধাপ এগোই।

object-fit আর object-position — ছবিকে ঘরে বসানো

অনলাইন দোকানের পণ্যের গ্রিডে সব কার্ড সমান মাপের হওয়া চাই, কিন্তু বিক্রেতাদের তোলা ছবি কোনোটা লম্বা, কোনোটা চওড়া। ছবিকে জোর করে বর্গাকার ঘরে বসালে টেনে চ্যাপ্টা হয়ে যায়। object-fit ঠিক করে ছবি তার ঘরে কীভাবে বসবে, আর object-position ঠিক করে কোন অংশটি ঘরের মাঝখানে থাকবে। আর aspect-ratio দিয়ে ঘরটিকে এক লাইনে বর্গাকার (বা ১৬:৯) করা যায়:

.row { display: flex; flex-wrap: wrap; gap: 12px; }
figure { margin: 0; text-align: center; }

img {
  width: 150px;
  height: auto;          /* height attribute-কে বাতিল করে */
  aspect-ratio: 1;       /* বর্গাকার ঘর */
  border: 2px solid #334155;
}

.fill    { object-fit: fill; }      /* ডিফল্ট: টেনে চ্যাপ্টা */
.cover   { object-fit: cover; }     /* ঘর ভরায়, কিনারা কাটে */
.contain { object-fit: contain; background: #e2e8f0; }
.pos     { object-fit: cover; object-position: left center; }

চারটি একই মাপের বর্গাকার ঘর, একই চওড়া ছবি। fill-এ ছবি টেনে চেপে বসানো — লেখা আর আকার বিকৃত। cover-এ অনুপাত ঠিক, ঘর পুরো ভরা, দুই পাশের অংশ কাটা গেছে। contain-এ পুরো ছবি দেখা যাচ্ছে, ওপরে-নিচে ধূসর ফাঁকা। শেষটিতে cover, কিন্তু বাঁ দিকের অংশ রাখা হয়েছে। চেষ্টা করুন: aspect-ratio-কে 16 / 9 করুন — কাটাকাটি প্রায় উধাও, কারণ ঘরের অনুপাত এখন ছবির কাছাকাছি। তারপর .pos-এ right center লিখে দেখুন কোন অংশ রইল।

object-fit, object-position আর aspect-ratio তিনটিই Baseline Widely available. একটি সতর্কতা: cover ছবির কিনারা কাটে, তাই জরুরি কিছু (মুখ, দাম, লেখা) কিনারায় থাকলে object-position দিয়ে সেটি রক্ষা করুন — বা art direction-এর কথা ভাবুন। আর cover ছবির ফাইল ছোট করে না; ফাইলের পিক্সেল পুরোটাই নামে, শুধু অংশটি দেখায়।

image-set() — CSS background-এর srcset

সাজসজ্জার পটভূমি-ছবি HTML-এ নয়, CSS-এর background-image-এ থাকে (পর্ব ২২)। সেখানে srcset-এর মতো বিকল্প দিতে আছে image-set() function: প্রতিটি বিকল্পের সাথে 1x/2x ঘনত্ব, আর চাইলে type() দিয়ে ফরম্যাট। ব্রাউজার যে ফরম্যাট চেনে না সেটি বাদ দেয়, তারপর পর্দার ঘনত্বের সাথে মানানসইটি নেয়। w descriptor নেই — CSS-এ ঘরের চওড়া মেলাতে চাইলে media query লাগে।

.hero {
  min-height: 200px;
  padding: 16px;
  color: white;
  text-shadow: 0 1px 4px black;
  background-color: #0d1f3c;   /* ছবি না এলে */
  /* পুরোনো ব্রাউজারের জন্য আগে একটি সাধারণ url() */
  background-image: url("../image/course.jpg");
  background-image: image-set(
    url("../image/java-programming-cover.jpg") type("image/x-future"),
    url("../image/course.jpg") 1x,
    url("../image/vibe-coding.jpg") 2x
  );
  background-size: cover;
  background-position: center;
}

DPR ১-এর পর্দায় পটভূমিতে course.jpg (একদল শিক্ষার্থী আর একটি আলোকিত মস্তিষ্ক), ঘন পর্দায় vibe-coding.jpg. প্রথম বিকল্পটি কাল্পনিক ফরম্যাটের, তাই বাদ। একই সম্পত্তি দুবার লেখা হয়েছে ইচ্ছে করে: যে ব্রাউজার image-set() বোঝে না, সে দ্বিতীয় লাইনটিকে ভুল ভেবে ফেলে দেয় আর প্রথমটি রাখে — এটাই CSS-এর progressive enhancement. চেষ্টা করুন: 1x আর 2x জায়গা বদল করুন — এখন DPR ১-এ ভাইব কোডিংয়ের ছবি আসবে। তারপর background-size-কে contain করে দেখুন।

image-set()-এর prefix ছাড়া রূপ ২০২৩ সালে সব প্রধান ব্রাউজারে পৌঁছেছে (Baseline); পুরোনো কোডে -webkit-image-set() দেখবেন — এখন আর দরকার নেই। মনে রাখুন, background-ছবির alt নেই; তাই তথ্যপূর্ণ ছবি (পণ্য, মানুষ, চার্ট) সবসময় <img>-এ রাখুন।

রেসপন্সিভ SVG — একটি ফাইলই যথেষ্ট

SVG ছবি vector, তাই এর জন্য srcset লাগে না: একই ফাইল ৫০ পিক্সেলেও ঝকঝকে, ২০০০ পিক্সেলেও। রেসপন্সিভ করার চাবি হলো viewBox — আঁকার খাতার নিজস্ব মাপ। viewBox থাকলে বাইরে থেকে শুধু চওড়া দিলেই চলে, উচ্চতা অনুপাত মেনে আসে:

<svg viewBox="0 0 300 120" role="img" aria-label="বিক্রি: জানুয়ারি ৪০, ফেব্রুয়ারি ৭০, মার্চ ১০০"
     style="width: 100%; height: auto; max-width: 600px">
  <rect x="10"  y="80" width="60" height="40"  fill="#16a34a"/>
  <rect x="110" y="50" width="60" height="70"  fill="#16a34a"/>
  <rect x="210" y="20" width="60" height="100" fill="#16a34a"/>
</svg>

inline SVG-তে width/height attribute না দিলে কিছু পরিস্থিতিতে সেটি ঘরের পুরো চওড়া নিয়ে নেয়, আর <img src="logo.svg">-এ ফাইলের ভেতরে মাপ না থাকলে আকার অনুমান কঠিন হয় — তাই নিরাপদ অভ্যাস: viewBox রাখুন, <img>-এ width/height দিন, আর CSS-এ max-width: 100%; height: auto. ঘর আর খাতার অনুপাত না মিললে কী হবে, তা ঠিক করে preserveAspectRatio (ডিফল্ট: মাঝখানে বসাও, পুরোটা দেখাও — অনেকটা object-fit: contain-এর মতো)। SVG-র পুরো জগৎ পর্ব ৪৪-এ।

৮ · ছবির কারখানা আর যাচাই — টুল, image CDN, DevTools ও currentSrc

একটি ছবির ৩টি মাপ × ৩টি ফরম্যাট = ৯টি ফাইল। ২০০ পণ্যের দোকানে প্রায় দুই হাজার ফাইল। হাতে বানানো অসম্ভব — এই কাজ করে যন্ত্র। তিন রকম পথ আছে:

🖱️ হাতে, একটি-দুটি ছবি

Squoosh-এর মতো ব্রাউজারভিত্তিক টুলে ছবি টেনে এনে মাপ, ফরম্যাট আর মান বদলান, পাশাপাশি তুলনা করে দেখুন। ছোট সাইট বা ব্লগের জন্য যথেষ্ট।

শেখা, ছোট সাইট

⚙️ build tool, একবারে সব

Node.js-এর sharp, বা ImageMagick, cwebp, avifenc-এর মতো command-line টুল দিয়ে script লিখে সব ছবি একবারে বানান। অনেক ফ্রেমওয়ার্কের নিজস্ব ছবি-component ভেতরে এটাই করে।

পোর্টফোলিও, স্ট্যাটিক সাইট

☁️ image CDN, চাহিদামতো

একটি মূল ছবি তুলে দিন; ঠিকানায় মাপ-ফরম্যাট লিখে চাইলে সার্ভার তখনই বানিয়ে, cache করে পাঠায়। অনেক হোস্টিং আর ক্লাউড সেবা এটি দেয়, সাধারণত টাকার বিনিময়ে।

বড় দোকান, খবরের সাইট

build tool দেখতে কেমন, তার একটি ছোট নমুনা — Node.js আর sharp দিয়ে একটি ছবি থেকে কয়েকটি মাপের AVIF আর WebP:

import sharp from 'sharp';

const widths = [400, 800, 1600];
for (const w of widths) {
  await sharp('saree.jpg').resize({ width: w }).avif({ quality: 50 }).toFile(`saree-${w}.avif`);
  await sharp('saree.jpg').resize({ width: w }).webp({ quality: 75 }).toFile(`saree-${w}.webp`);
  await sharp('saree.jpg').resize({ width: w }).jpeg({ quality: 80, mozjpeg: true }).toFile(`saree-${w}.jpg`);
}

আর একটি image CDN-এর ঠিকানা সাধারণত এমন দেখায় (প্রতিটি সেবার নিয়ম আলাদা, এটি শুধু ধারণা দেওয়ার জন্য): https://img.example.com/saree.jpg?w=800&format=auto. format=auto মানে সার্ভার ব্রাউজারের পাঠানো Accept header দেখে ঠিক করবে AVIF, WebP না JPEG — একে বলে content negotiation. তখন HTML-এ <picture>-এর type ছাড়াই শুধু srcset লিখলেই চলে। সুবিধা বড়, তবে খরচ, নির্দিষ্ট কোম্পানির ওপর নির্ভরতা আর সেই সেবা বন্ধ হলে কী হবে — এসবও ভাবুন। ছোট সাইটের জন্য build tool প্রায়ই যথেষ্ট আর বিনামূল্যে।

কোন ছবি আসলে এল — যাচাইয়ের তিন উপায়

  1. Network panel. F12 → Network → ফিল্টারে Img. পাতা আবার লোড করুন (প্রথমে “Disable cache” চিহ্ন দিন, নইলে cache-এর ফাইল বিভ্রান্ত করবে)। কোন ফাইল এল, কত KB, কখন শুরু হলো আর Priority কলামে (ডান-ক্লিক করে কলাম যোগ করতে হয়) কত গুরুত্ব পেল — সব দেখা যায়।
  2. Device toolbar. Ctrl + Shift + M দিয়ে ফোনের মাপ আর DPR বেছে নিন, তারপর Network-এ দেখুন ফোনে কোন ফাইল নামছে। প্রতিবার মাপ বদলের পর cache বন্ধ রেখে আবার লোড করুন।
  3. currentSrc. Elements প্যানেলে ছবিটি বেছে Console-এ $0.currentSrc লিখুন — ব্রাউজার যে ঠিকানা বেছে নিয়েছে, সেটা দেখাবে। img.src কিন্তু সবসময় শুধু src attribute-এর লেখাই দেয়, বাছাই করা ফাইল নয়।

নিচের ছোট মাপার যন্ত্রটি একসাথে সব দেখায়: viewport, DPR, প্রতিটি ছবি কোন ফাইল নিল, কত চওড়ায় দেখাচ্ছে আর সেই চওড়ায় ঝকঝকে দেখাতে আসলে কত পিক্সেল দরকার:

// প্রতিটি ছবি: কোন ফাইল এল, কত চওড়ায় দেখাচ্ছে, কত পিক্সেল দরকার
function report() {
  console.log('viewport: ' + innerWidth + 'px, devicePixelRatio: ' + devicePixelRatio);
  document.querySelectorAll('img').forEach(function (img) {
    var file = img.currentSrc.split('/').pop();
    var need = Math.round(img.clientWidth * devicePixelRatio);
    console.log('#' + img.id + ' -> ' + file);
    console.log('   shown ' + img.clientWidth + ' CSS px, needs ~' + need + ' device px');
  });
}
// সব ছবি আসা আর layout শেষ হওয়া পর্যন্ত অপেক্ষা, তারপর মাপা
(function wait() {
  var ready = document.body.offsetWidth > 0 &&
    Array.from(document.images).every(function (i) { return i.complete; });
  ready ? report() : setTimeout(wait, 50);
})();

console-এ প্রথমে viewport আর DPR, তারপর দুটি ছবির হিসাব। #hero-এর পাশে দেখবেন কোন প্রচ্ছদ এল, আর “needs” সংখ্যাটির সাথে মিলিয়ে দেখুন: ব্রাউজার সাধারণত সেই সংখ্যার সমান বা তার চেয়ে বড় সবচেয়ে ছোট ফাইলটি নিয়েছে কি না। #logo-এর পাশে logo.webp, কারণ ব্রাউজার WebP চেনে। চেষ্টা করুন: markup-এ sizes-কে (min-width: 600px) 10vw, 100vw করুন কিন্তু CSS-এর 50% রেখে দিন — এখন sizes মিথ্যা বলছে, ফলে ব্রাউজার ছোট ফাইল নিতে পারে আর “needs”-এর চেয়ে ছোট ফাইল দেখে ছবি ঝাপসা হবে। (আগে বড় ফাইল এসে গেলে cache-এর কারণে বদল না-ও দেখতে পারেন।)

আরও গভীরে: HTML স্ট্যান্ডার্ড-এ srcset-এর প্রতিটি w descriptor আসলে ঘনত্বে রূপান্তরিত হয়: ঘনত্ব = ফাইলের চওড়া ÷ sizes-এর চওড়া। ৮০০w ফাইল ৪০০ পিক্সেলের ঘরে মানে ঘনত্ব ২x — তারপর বাছাই চলে x descriptor-এর মতোই। এ কারণে img.naturalWidth w descriptor-এর ছবিতে ফাইলের আসল পিক্সেল দেয় না, ঘনত্ব দিয়ে ভাগ করা মাপ দেয়। আর কোন ঘনত্বের ফাইল নেবে, তার কোনো একক সূত্র স্ট্যান্ডার্ড বেঁধে দেয়নি — ব্রাউজারভেদে নিয়ম একটু আলাদা, আর ব্যবহারকারীর সেটিং বা নেটওয়ার্কও হিসাবে ঢুকতে পারে।

পরীক্ষা ও ইন্টারভিউতে — এ ধরনের প্রশ্ন প্রায়ই আসে:

  • srcset আর <picture>-এর পার্থক্য কী? srcset একই ছবির কয়েকটি মাপ দেয়, ব্রাউজার বাছে (পরামর্শ); <picture>-এর media/type আলাদা ছবি বা ফরম্যাট বাধ্যতামূলকভাবে বেছে দেয় (আদেশ)।
  • sizes কেন লাগে, ব্রাউজার CSS থেকে বুঝে নেয় না কেন? ছবি নামানো শুরু হয় লেআউটের আগে (preload scanner), তখন ঘরের চওড়া জানা থাকে না।
  • w descriptor-এ sizes না দিলে কী হয়? ধরা হয় 100vw — ছোট ছবির জন্যও বড় ফাইল নামতে পারে।
  • LCP ছবিতে কী করবেন, কী করবেন না? loading="lazy" নয়; fetchpriority="high", width/height, আর সম্ভব হলে <img> (CSS background নয়)।
  • object-fit: cover আর contain-এর পার্থক্য? cover ঘর ভরায় কিনারা কেটে; contain পুরো ছবি দেখায়, ফাঁকা জায়গা রেখে।

৯ · AI-এর যুগে রেসপন্সিভ ছবি

AI-কে “একটা ই-কমার্স প্রোডাক্ট গ্রিড বানাও” বললে সুন্দর কোড আসে, কিন্তু ছবির অংশে প্রায়ই পুরোনো বা আধা-সঠিক অভ্যাস থাকে। কারণ AI শিখেছে বহু বছরের কোড থেকে, যার বেশিরভাগ লেখা হয়েছিল srcset আর lazy loading আসার আগে বা ভুলভাবে। আর ছবির ভুল চোখে ধরা পড়ে না — ছবি তো দেখাচ্ছেই! ধরা পড়ে দর্শকের ডেটার বিলে আর Lighthouse-এর রিপোর্টে।

AI-এর সাধারণ ভুলসমস্যাসঠিক কাজ
w descriptor আছে, sizes নেইসব ক্ষেত্রে 100vw ধরা হয়, বড় ফাইল নামেলেআউট অনুযায়ী sizes লিখুন
hero-সহ সব ছবিতে loading="lazy"LCP খারাপhero eager + fetchpriority="high"
<source src="...">, বা alt <source>-এpicture-এ কাজ করে না / alt হারায়source-এ srcset, alt <img>-এ
শুধু মাপ বদলের জন্য <picture media>ব্রাউজারের বুদ্ধি অকেজো, কোড লম্বাsrcset + sizes
JavaScript lazy library, data-src কৌশলবাড়তি script, JS না চললে ছবিই নেইনেটিভ loading="lazy"
হিরো ছবি CSS background-এ, -webkit-image-setদেরিতে আবিষ্কার, alt নেই, পুরোনো prefix<img>; background হলে prefix ছাড়া image-set()

AI-কে কোড চাওয়ার সময় পাতার লেআউট আর শর্তগুলো আগেই বলে দিন — sizes লিখতে লেআউট জানা লাগে, AI সেটা অনুমান করতে পারে না। নিচের প্রম্পটটি নিজের প্রয়োজনমতো বদলে নিন:

আমার অনলাইন দোকানের পণ্যের গ্রিডের HTML আর CSS লিখে দাও। লেআউট: ৬০০px-এর কম চওড়ায় ১ কলাম, ৬০০–৯৯৯px-এ ২ কলাম, ১০০০px থেকে ৪ কলাম; পাশে ১৬px প্যাডিং। শর্ত: ১. প্রতিটি পণ্যের ছবিতে srcset (400w, 800w, 1200w) আর এই লেআউটের সাথে মেলানো sizes. ২. picture দিয়ে AVIF, তারপর WebP, শেষে JPEG fallback; alt আর width/height img-এ। ৩. প্রথম সারির ছবিতে lazy নয়; প্রথম ছবিতে fetchpriority="high"; বাকি সবগুলোতে loading="lazy". ৪. কার্ডের ছবি বর্গাকার: aspect-ratio আর object-fit: cover. ৫. কোনো JavaScript, কোনো -webkit- prefix নয়। শেষে sizes-এর প্রতিটি অংশ কোন লেআউট থেকে এল, এক লাইনে ব্যাখ্যা করো।

AI-এর দেওয়া ছবির কোড যাচাইয়ের চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।

প্র ০১একটি ব্রাউজার কোম্পানির প্রস্তাব আর একদল সাধারণ ডেভেলপারের প্রস্তাব — srcset আর <picture> দুটোই কীভাবে স্ট্যান্ডার্ডে এল?

২০১০-এর দশকের শুরুতে মোবাইল ওয়েব দ্রুত বাড়ছিল, আর ডেভেলপাররা দেখছিলেন <img>-এর একটি মাত্র src দিয়ে ফোন আর ডেস্কটপ দুই জায়গায় ঠিক ছবি দেওয়া যায় না। JavaScript দিয়ে নানা কৌশল চলছিল, কিন্তু সেগুলো preload scanner-এর সাথে লড়াই করত — ব্রাউজার আগেই ভুল ছবিটা নামিয়ে ফেলত। একদল স্বেচ্ছাসেবী ডেভেলপার একটি W3C community group গড়ে তোলেন, পরে যার নাম হয় Responsive Issues Community Group (RICG). তাঁরা <video>-এর <source>-এর আদলে <picture> element-এর প্রস্তাব দেন।

২০১২ সালে প্রায় একই সময়ে Apple-এর একজন প্রকৌশলী WHATWG-তে ভিন্ন একটি প্রস্তাব দেন — srcset attribute, শুরুতে মূলত ঘনত্ব (x) নিয়ে। ব্রাউজার নির্মাতারা attribute-টি পছন্দ করেন, কারণ এটি সরল আর ব্রাউজারকে বাছাইয়ের স্বাধীনতা দেয়; অনেক ডেভেলপার ক্ষুব্ধ হন, কারণ তাঁদের মনে হয় community-র কাজ উপেক্ষা করা হচ্ছে, আর srcset-এর প্রথম রূপ art direction-এর সমস্যা মেটাতে পারত না।

শেষ পর্যন্ত যা হলো, তা ওয়েবের ইতিহাসে বিরল: দুটি প্রস্তাবই মিলিয়ে নেওয়া হলো। srcset-এ যোগ হলো w descriptor আর sizes, যাতে চওড়া বদলানো ছবির সমস্যাও মেটে; আর <picture> রইল art direction আর format switching-এর জন্য, ভেতরে সেই srcset-ই ব্যবহার করে। ব্রাউজারে এটি বাস্তবায়নের জন্য community থেকে টাকা তোলা হয় (crowdfunding), আর সেই অর্থে একজন ডেভেলপার, Yoav Weiss, Chromium-এ কোড লেখেন। ২০১৪ সালের দিকে Chrome-এ এটি চালু হয়, পরে অন্য ব্রাউজারেও।

এই নকশায় একটি গভীর দর্শন আছে: যে তথ্য শুধু লেখক জানেন (কোন ছবি কোন পর্দায় অর্থবহ, লেআউটে ঘর কত বড়), সেটা লেখক বলবেন; আর যে তথ্য শুধু ব্রাউজার জানে (পর্দার ঘনত্ব, নেটওয়ার্কের গতি, ব্যবহারকারীর ডেটা-সেভার, cache), সেই সিদ্ধান্ত ব্রাউজার নেবে। art direction-এ লেখকের আদেশ, resolution switching-এ ব্রাউজারের বিচার।

মূল উপলব্ধি: ওয়েব স্ট্যান্ডার্ড শুধু বড় কোম্পানির হাতে তৈরি হয় না — সাধারণ ডেভেলপারদের সংগঠিত চেষ্টাও তা বদলাতে পারে। আর ভালো API দায়িত্ব ভাগ করে: যে যা সবচেয়ে ভালো জানে, সিদ্ধান্ত তার হাতে।

প্র ০২CSS-এ তো ছবির মাপ লেখাই থাকে। তাহলে HTML-এ আবার sizes লিখতে হয় কেন — এটা কি একই তথ্য দুবার লেখা নয়?

হ্যাঁ, এটি সত্যিই একই তথ্য দুবার লেখা, আর এর জন্য বাস্তবে অনেক ভুল হয়। কিন্তু কারণটা বোঝা জরুরি, কারণ এটি ব্রাউজারের কাজের একেবারে ভেতরের কথা।

একটি পাতা খোলার সময় ব্রাউজারের সবচেয়ে বড় শত্রু হলো নেটওয়ার্কের অপেক্ষা। প্রতিটি ফাইল চাওয়া আর পাওয়ার মাঝে সময় যায়, ধীর মোবাইল নেটওয়ার্কে অনেক বেশি। তাই ব্রাউজার যত তাড়াতাড়ি সম্ভব ফাইল চাইতে চায়। মূল HTML parser যখন একটি script-এর জন্য থেমে থাকে, তখনও preload scanner নামের হালকা একটি অংশ সামনের HTML পড়ে <img>, stylesheet আর script খুঁজে খুঁজে আগেভাগে চেয়ে বসে। এটি পাতা লোডের গতিতে বিশাল প্রভাব ফেলে।

সমস্যা হলো, তখনো CSS আসেনি বা পুরো পড়া হয়নি, আর লেআউট — কোন বাক্স কত চওড়া — হিসাব হতে আরও দেরি। ব্রাউজার যদি বলত “আগে CSS আর লেআউট শেষ করি, তারপর দেখি ছবির ঘর কত বড়”, তাহলে প্রতিটি ছবি দেরিতে শুরু হতো। sizes এই দ্বন্দ্বের মীমাংসা: লেখক আগেভাগেই ঘরের মোটামুটি চওড়া বলে দেন, ব্রাউজার লেআউট ছাড়াই ঠিক ফাইল চেয়ে ফেলে।

lazy ছবির ক্ষেত্রে ছবিটি লেআউটের পরেই আনা হয়, তখন ঘরের আসল চওড়া জানা। সেই যুক্তি থেকেই এসেছে sizes="auto": lazy ছবিতে ব্রাউজার নিজেই মাপ নেয়। এটি Chromium-এ চালু হয়েছে, কিন্তু এখনো সব ব্রাউজারে নেই (Baseline নয়), তাই fallback হিসেবে সাধারণ sizes-ও পাশে লিখতে হয়। এখানে আবার দেখা যায়, দ্রুততা আর সুবিধার মধ্যে একটা টানাপোড়েন — eager ছবিতে দ্রুততার জন্য দুবার লেখা, lazy ছবিতে সুবিধার জন্য একবার।

মূল উপলব্ধি: ওয়েবের অনেক “অদ্ভুত” নিয়মের পেছনে থাকে পারফরম্যান্সের হিসাব। sizes বিরক্তিকর মনে হলে মনে রাখুন — এটি ব্রাউজারকে লেআউটের জন্য অপেক্ষা না করে কাজ শুরু করতে দেয়। আর যেখানে দুই জায়গায় একই তথ্য, সেখানে টুল বা টেমপ্লেট দিয়ে এক জায়গা থেকে দুটো বানানোই নিরাপদ।

প্র ০৩বাংলাদেশের মতো দেশে ছবির পারফরম্যান্স কি শুধু কারিগরি বিষয়, নাকি ন্যায্যতারও প্রশ্ন?

বেশিরভাগ ওয়েব ডেভেলপার কাজ করেন দ্রুত ব্রডব্যান্ডে, দামি ল্যাপটপে। তাঁদের চোখে একটি ৪ মেগাবাইটের ছবি “এক মুহূর্তেই চলে আসে”। কিন্তু আপনার সাইটের বড় একটি অংশ দর্শক হয়তো চালান কম দামি অ্যান্ড্রয়েড ফোন, মোবাইল ডেটার সীমিত প্যাকেজ, আর এমন নেটওয়ার্ক যা জায়গাভেদে আর দিনের সময়ভেদে ধীর হয়ে যায়। তাঁদের কাছে প্রতিটি মেগাবাইট আসলে টাকা — যে টাকা দিয়ে তাঁরা হয়তো কোনো অনলাইন ক্লাস করতেন বা পরিবারের সাথে ভিডিও কলে কথা বলতেন।

বিশ্বজুড়ে ওয়েব পাতার মাপ নিয়মিত মাপে HTTP Archive নামের একটি প্রকল্প, আর তাদের হিসাবে বহু বছর ধরেই একটি সাধারণ পাতার বাইটের সবচেয়ে বড় অংশ থাকে ছবিতে। অর্থাৎ পাতা হালকা করার সবচেয়ে বড় সুযোগও এখানেই। একটি ৩০০০ পিক্সেলের ছবির বদলে ৮০০ পিক্সেলের AVIF দিলে ফাইল প্রায়ই অনেক গুণ ছোট হয়, অথচ ফোনের পর্দায় চোখে কোনো পার্থক্য ধরা পড়ে না।

এর একটি অদৃশ্য দিক আছে: যাঁরা ধীর নেটওয়ার্ক বা পুরোনো ফোনে থাকেন, তাঁরা প্রায়ই অভিযোগ করেন না — চুপচাপ চলে যান। আপনার analytics-এ তাঁরা প্রায় দেখাই যান না, কারণ পাতা খোলার আগেই তাঁরা হাল ছেড়ে দেন। তাই “আমার ব্যবহারকারীরা তো সবাই দ্রুত” — এই ধারণা নিজেই পক্ষপাতদুষ্ট হতে পারে। কম দামি ফোনে, DevTools-এর throttling (ইচ্ছে করে নেটওয়ার্ক ধীর করা) দিয়ে নিজের সাইট পরীক্ষা করা তাই পেশাদার অভ্যাস।

ব্রাউজারও এখানে ব্যবহারকারীর পক্ষে দাঁড়াতে পারে: srcset পরামর্শ বলেই ডেটা-সেভার চালু থাকলে সে ছোট ফাইল বাছতে পারে। কিন্তু সেই সুযোগ তৈরি হয় তখনই, যখন আপনি কয়েকটি মাপ দিয়েছেন। একটিমাত্র বিশাল src দিলে ব্রাউজারের হাতে কিছুই থাকে না।

মূল উপলব্ধি: পারফরম্যান্স মানে শুধু Lighthouse-এর স্কোর নয় — এটি কে আপনার সাইট ব্যবহার করতে পারবেন আর কে পারবেন না, সেই প্রশ্ন। রেসপন্সিভ ছবি তাই অ্যাক্সেসিবিলিটির মতোই সবার জন্য ওয়েবের অংশ।

প্র ০৪lazy loading যদি এত ভালো হয়, তাহলে সব ছবিতে দিলে ক্ষতি কী? একটি জনপ্রিয় প্ল্যাটফর্মের অভিজ্ঞতা কী বলে?

নেটিভ lazy loading আসার আগে ডেভেলপাররা JavaScript দিয়ে এটা করতেন: src-এর বদলে data-src-এ ঠিকানা রেখে, স্ক্রলের সময় হিসাব করে, ছবি পর্দার কাছে এলে script দিয়ে আসল src বসাতেন। এতে বাড়তি script লাগত, script ব্যর্থ হলে ছবিই আসত না, আর সবচেয়ে বড় কথা — preload scanner এই ছবিগুলোকে দেখতেই পেত না, কারণ HTML-এ আসল ঠিকানাই নেই।

তারপর loading="lazy" attribute এল। Chrome ২০১৯ সালে এটি চালু করে, পরে Firefox ও Safari. এক লাইনের attribute, কোনো script নেই। খুব জনপ্রিয় একটি ওয়েবসাইট-নির্মাণ প্ল্যাটফর্ম, WordPress, ২০২০ সালের দিকে তাদের সব ছবিতে স্বয়ংক্রিয়ভাবে lazy যোগ করা শুরু করে — রাতারাতি লাখো সাইটে। পরে বিভিন্ন পরিমাপে দেখা গেল, এতে অনেক সাইটের LCP আসলে খারাপ হয়েছে, কারণ প্রথম পর্দার বড় ছবিটিও lazy হয়ে গিয়েছিল। পরের সংস্করণগুলোতে প্ল্যাটফর্মটি নিয়ম বদলায়: পাতার শুরুর দিকের ছবিতে lazy না দেওয়া, আর পরে hero ছবিতে স্বয়ংক্রিয়ভাবে fetchpriority="high" যোগ করা।

কেন lazy প্রথম পর্দার ছবিকে ধীর করে? lazy ছবি আনার আগে ব্রাউজারকে জানতে হয় ছবিটি পর্দার কতটা কাছে — আর সেটা জানতে লাগে লেআউট, যা CSS না আসা পর্যন্ত হয় না। eager ছবি preload scanner পেয়েই চেয়ে বসে; lazy ছবি অপেক্ষা করে। সাধারণ ছবির জন্য এই অপেক্ষা কোনো ব্যাপার না, কিন্তু LCP ছবির জন্য সেটাই পুরো পার্থক্য।

আরেকটি সূক্ষ্ম দিক: lazy মানে “একদম শেষ মুহূর্তে” নয়। ব্রাউজার একটা নিরাপদ দূরত্ব রেখে আগেই আনে, আর ধীর নেটওয়ার্কে সেই দূরত্ব বাড়িয়ে দেয়, যাতে স্ক্রল করে পৌঁছানোর আগেই ছবি এসে যায়। এজন্য খুব ছোট পাতায় lazy প্রায় কোনো পার্থক্যই আনে না।

মূল উপলব্ধি: কোনো অপ্টিমাইজেশন সব জায়গায় ভালো নয়। একটি নিয়ম অন্ধভাবে সবখানে বসালে (মানুষ, প্লাগইন বা AI — যেই বসাক) উপকারের জায়গায় ক্ষতি হতে পারে। মাপুন, তারপর সিদ্ধান্ত নিন।

প্র ০৫সার্ভার যদি নিজেই জানতে পারত ফোনটি কত চওড়া আর কত ঘন, তাহলে কি HTML-এ srcset লেখার ঝামেলাই থাকত না? Client Hints-এর গল্প কী শেখায়?

ধারণাটি পুরোনো আর আকর্ষণীয়: ব্রাউজার প্রতিটি request-এর সাথে জানিয়ে দেবে “আমার পর্দার DPR ২, viewport ৩৯০ পিক্সেল, ছবির ঘর ৩৬০ পিক্সেল”, আর সার্ভার ঠিক সেই মাপের ছবি বানিয়ে পাঠাবে। HTML-এ থাকবে শুধু একটি সাধারণ src. এই প্রস্তাবের নাম Client Hints. image CDN-গুলো এটা পছন্দ করে, কারণ তারা তো চাহিদামতো ছবি বানাতেই পারে।

Chromium-ভিত্তিক ব্রাউজারে Client Hints চালু আছে, তবে সার্ভারকে আগে একটি header (Accept-CH) দিয়ে চাইতে হয় যে কোন কোন তথ্য সে নিতে চায়। Firefox আর Safari এটি গ্রহণ করেনি, তাই এটি Baseline নয়। তাদের আপত্তির একটি বড় কারণ গোপনীয়তা: প্রতিটি request-এ যন্ত্রের বিস্তারিত তথ্য পাঠালে সেগুলো মিলিয়ে একজন ব্যবহারকারীকে আলাদা করে চেনা (fingerprinting) সহজ হয়।

আরেকটি কারিগরি সমস্যা হলো cache. একই ঠিকানা থেকে যন্ত্রভেদে আলাদা ফাইল এলে, মাঝপথের cache সার্ভারগুলোকে (CDN) জানাতে হয় কোন header-এর ভিত্তিতে ফল বদলায় — Vary header দিয়ে। এতে cache-এর কার্যকারিতা কমে যায়। ফরম্যাটের ক্ষেত্রে এটা অনেক দিন ধরেই চলছে: ব্রাউজার Accept header-এ জানায় সে AVIF বা WebP চেনে কি না, আর অনেক image CDN সেটা দেখে ফরম্যাট বাছে।

তুলনায় srcset আর sizes-এর একটা বড় গুণ আছে: সিদ্ধান্তের তথ্য ব্রাউজারের ভেতরেই থাকে, সার্ভারের কাছে যন্ত্রের বিবরণ যায় না, আর প্রতিটি ফাইল একটি সাধারণ, cache-যোগ্য ঠিকানা। দোষ হলো লম্বা HTML আর CSS-এর সাথে sizes মিলিয়ে রাখার দায়।

মূল উপলব্ধি: ওয়েবের নকশায় কোনো সমাধানই “বিনামূল্যে” নয় — সুবিধা, গোপনীয়তা, cache আর ব্রাউজারের সম্মতি, সবকিছুর মধ্যে ভারসাম্য খুঁজতে হয়। তাই আজকের নিরাপদ পথ: সবখানে চলে এমন srcset/picture, আর তার ওপর চাইলে image CDN-এর সুবিধা।

অনুশীলন

প্রথম তিনটিতে হিসাব, দেখা আর ভুল ধরা; শেষটিতে ছোট একটি কোড লিখবেন।

  1. ব্রাউজারের অঙ্ক নিজে কষুন: srcset="a-320.jpg 320w, a-640.jpg 640w, a-960.jpg 960w, a-1280.jpg 1280w" আর sizes="(min-width: 768px) 50vw, 100vw". তিনটি যন্ত্রে সাধারণত কোন ফাইল আসবে? (ক) ফোন: viewport ৩৭৫, DPR ৩ (খ) ডেস্কটপ: viewport ১৪৪০, DPR ১ (গ) ট্যাবলেট: viewport ৮২০, DPR ২।

    (ক) ৩৭৫ < ৭৬৮, তাই ঘর ১০০vw = ৩৭৫; দরকার ৩৭৫ × ৩ = ১১২৫ → a-1280.jpg.

    (খ) ১৪৪০ ≥ ৭৬৮, ঘর ৫০vw = ৭২০; দরকার ৭২০ × ১ = ৭২০ → ৬৪০ একটু ছোট, তাই a-960.jpg.

    (গ) ৮২০ ≥ ৭৬৮, ঘর ৪১০; দরকার ৪১০ × ২ = ৮২০ → a-960.jpg.

    লক্ষ করুন, সবচেয়ে ছোট পর্দার ফোনই সবচেয়ে বড় ফাইল পেল — কারণ তার পর্দা সবচেয়ে ঘন। “সাধারণত” বলা হলো, কারণ ব্রাউজার নেটওয়ার্ক বা cache দেখে অন্য সিদ্ধান্তও নিতে পারে।

  2. একটি আসল সাইটে খুঁজে দেখুন: যেকোনো বড় খবরের বা ই-কমার্স সাইট খুলে একটি ছবিতে ডান-ক্লিক → Inspect করুন। তার srcset আর sizes আছে কি? <picture>-এর ভেতরে আছে কি? Console-এ $0.currentSrc লিখে দেখুন কোন ফাইল এল। তারপর Device toolbar-এ ফোনের মাপ বেছে, cache বন্ধ করে, আবার লোড করে দেখুন ফাইল বদলায় কি না।

    বেশিরভাগ বড় সাইটে w descriptor-সহ srcset পাবেন, প্রায়ই ৫–১০টি মাপ, আর ঠিকানায় ?w=800 বা /800x/-এর মতো অংশ — যা দেখে বোঝা যায় তারা image CDN ব্যবহার করে। অনেক সময় ফাইলের নাম .jpg হলেও Network প্যানেলের Type কলামে দেখবেন avif বা webp — সার্ভার Accept header দেখে ফরম্যাট বদলেছে (content negotiation). ফোনের মাপে গেলে ছোট ঠিকানা আসা উচিত; না এলে sizes ভুল বা অনুপস্থিত হতে পারে — সেটাই আসল সাইটের একটি সাধারণ বাগ।

  3. ভুল খুঁজুন: নিচের কোডে অন্তত পাঁচটি সমস্যা আছে — <picture><source src="hero.avif" type="image/avif"><source srcset="hero-800.webp 800w, hero-1600.webp 2x"><img src="hero.jpg" loading="lazy"></picture> — যেখানে ছবিটি পাতার প্রথম পর্দার hero ব্যানার।

    (১) প্রথম source-এ src নয়, srcset লাগবে — নইলে এই source অকেজো।

    (২) দ্বিতীয় source-এ type="image/webp" নেই — WebP না চেনা ব্রাউজারও এটা বেছে নিয়ে ভাঙা ছবি দেখাতে পারে।

    (৩) একই srcset-এ 800w আর 2x মেশানো নিষেধ; আর w থাকলে sizes দরকার।

    (৪) <img>-এ alt নেই, width/height নেই — অ্যাক্সেসিবিলিটি আর CLS দুটোরই সমস্যা।

    (৫) hero ছবিতে loading="lazy" — LCP খারাপ হবে; এর বদলে fetchpriority="high". বোনাস: AVIF source-এও কয়েকটি মাপ আর sizes দেওয়া উচিত, নইলে AVIF চেনা ফোন সবসময় একটি মাত্র (হয়তো বিশাল) ফাইল পাবে।

  4. নিজে লিখুন: একটি <picture> বানান, যাতে ৫০০ পিক্সেল বা তার কম চওড়ায় আসবে এই সাইটের ../logo.webp, আর বাকি সবখানে ../image/ABCL-TECH-Cover.jpg (১২৮০×৭২০); ছবিটি ঘরের চেয়ে চওড়া হবে না, অর্থবহ alt থাকবে, আর এটি প্রথম পর্দার ছবি বলে আগে আনতে বলবেন। তারপর নিচের এডিটরে মিলিয়ে দেখুন।

    একটি সম্ভাব্য উত্তর:

    <picture>
      <source media="(max-width: 500px)" srcset="../logo.webp"
              width="3162" height="1137">
      <img src="../image/ABCL-TECH-Cover.jpg"
           width="1280" height="720"
           style="max-width: 100%; height: auto"
           fetchpriority="high"
           alt="ABCL TECH — বাংলায় প্রযুক্তি শিক্ষা">
    </picture>

    বড় পর্দায় পুরো কভার, সরু পর্দায় লোগো। fetchpriority="high" আছে, loading="lazy" নেই — কারণ এটি প্রথম পর্দার ছবি। alt একটাই, <img>-এ, আর দুটি ছবিই একই বার্তা দেয় বলে দুই ক্ষেত্রেই alt সঠিক। চেষ্টা করুন: 500px-কে 1500px করে লোগোটি বড় পর্দাতেও দেখুন, আর source-এর width/height মুছে দিলে লোগোর ঘরের অনুপাত কীভাবে লোড হওয়ার আগে ভুল থাকতে পারে, ভাবুন।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমsrcset (w descriptor) আর sizes-সহ একটি <img> পেয়ে ব্রাউজার যা যা করে, সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. 🔎 preload scanner HTML-এ <img>-টি খুঁজে পায়
  2. 📏 sizes-এর প্রথম মেলা শর্ত থেকে ছবির ঘরের চওড়া নেয়
  3. ✖️ সেই চওড়াকে device pixel ratio দিয়ে গুণ করে
  4. 🎯 srcset থেকে মানানসই একটি ফাইল বাছে
  5. 🌐 শুধু সেই ফাইলটি নামিয়ে, লেআউটের পর পর্দায় আঁকে

মন্তব্যের পাশে একটি প্রোফাইল ছবি সব পর্দায় ৬৪×৬৪ CSS পিক্সেলে দেখায়। ঘন পর্দায় ঝকঝকে রাখার সবচেয়ে উপযুক্ত উপায় কোনটি?

  • <picture>-এ কয়েকটি <source media>
  • srcset="a-64.jpg 1x, a-128.jpg 2x"
  • সবসময় একটি ৫১২ পিক্সেলের ফাইল দেওয়া
  • srcset="a-64.jpg 64w, a-128.jpg 128w", sizes ছাড়া

srcset="p-480.jpg 480w, p-800.jpg 800w, p-1200.jpg 1200w" আর sizes="(min-width: 1000px) 50vw, 100vw". viewport ১২০০ পিক্সেল, device pixel ratio ১ হলে সাধারণত কোন ফাইল আসবে?

  • p-480.jpg
  • p-1200.jpg
  • p-800.jpg
  • কোনোটি নয়, src-এর ফাইল

ডেস্কটপে চওড়া ব্যানার আর ফোনে একই দৃশ্যের কাছ থেকে কাটা বর্গাকার ছবি দেখাতে হবে। কোনটি সঠিক হাতিয়ার?

  • <picture>-এ <source media="…" srcset="…">
  • শুধু srcset আর sizes
  • শুধু CSS-এ object-fit: cover
  • <source type="image/webp">

<picture> ব্যবহার করলে ছবির alt কোথায় লিখতে হয়?

  • <picture>-এ
  • প্রতিটি <source>-এ
  • <figcaption>-এ
  • ভেতরের <img>-এ

একটি পাতার প্রথম পর্দার বড় hero ছবি (যা LCP হবে) — এর জন্য কোন জোড়া সবচেয়ে ভালো?

  • loading="lazy" আর decoding="async"
  • loading="lazy" আর fetchpriority="low"
  • loading না দেওয়া (eager) আর fetchpriority="high"
  • ছবিটি CSS background-image-এ সরিয়ে নেওয়া

বর্গাকার কার্ডে চওড়া একটি ছবি বসাতে হবে — অনুপাত বিকৃত না করে, পুরো ঘর ভরে, কিনারা কাটা গেলেও চলবে। কোন CSS?

  • object-fit: fill
  • object-fit: cover
  • object-fit: contain
  • object-position: center

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. রেসপন্সিভ ছবি তিন সমস্যা: মাপ (srcset + sizes), কাটা (<picture> + media), ফরম্যাট (<picture> + type).
  2. নির্দিষ্ট মাপের ছবিতে 1x, 2x; চওড়া বদলানো ছবিতে w + sizes. ব্রাউজারের অঙ্ক: ঘরের চওড়া × DPR, তারপর মানানসই ফাইল।
  3. srcset পরামর্শ, <source media> আদেশ; ভেতরের <img>-এ alt, width/height আর বাকি সব।
  4. hero ছবিতে lazy নয়, fetchpriority="high"; বাকিগুলোতে loading="lazy"; সবখানে width/height.
  5. CSS-এ object-fit, aspect-ratio, image-set(); ফাইল বানান টুল বা image CDN দিয়ে, আর যাচাই করুন Network ও currentSrc দিয়ে।

পরের পর্বে: একই রং, একই মাপ, একই ফাঁক পুরো সাইটে বারবার লিখতে হয়। CSS variable (custom properties) দিয়ে একবার লিখে সবখানে ব্যবহার — আর এক লাইনে পুরো থিম বদলানো।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান