Mundarija (34)
- Bu darsda
- 1. Nega bu kerak?
- 2. Rasm React komponentida
- 2.1 Atributlar JSX'da
- 2.2 preload(): rasmni oldindan aytib qo'yish
- 2.3 Placeholder: rasm kelguncha
- 3. CDN transformatsiyalari
- 3.1 Bitta asl fayl — ko'p variant
- 3.2 Manzil yasovchi funksiya
- 4. Hujumchi nigohi
- 5. Lightbox: rasm to'liq ekranda
- 5.1 Talablar
- 5.2 O'z lightbox'imiz
- 5.3 yet-another-react-lightbox
- 6. Zoom: rasmni joyida kattalashtirish
- 7. Rasmni kesish (crop)
- 7.1 Kesish — bu hisob
- 7.2 Boshqaruv: avval slider, keyin sudrash
- 7.3 Canvas: kesib, WebP qilish
- 7.4 react-easy-crop va Trusted Types
- 8. Vazifalar qadami: profil rasmini kesish (holat 12)
- 8.1 Qayerga qo'shiladi
- 8.2 RasmKesish.tsx
- 8.3 ProfilRasmi.tsx: fokus qaytishi
- 8.4 Natija va narx
- 9. Ko'p uchraydigan xatolar
- 10. Mashqlar
- 1-mashq (oson): bo'sh joylarni to'ldiring
- 2-mashq (o'rta): lightbox'da Home va End
- 3-mashq (qiyin): CDN srcset va ruxsat etilgan o'lchamlar
- 4-mashq: Vazifalar qadami
- 11. Real ishda
- Xulosa
- Manbalar
Rasm UI React'da: lightbox, zoom, kesish (crop) va CDN transformatsiyalari
Qisqacha: Rasm UI to'rt qismdan iborat. Yetkazish — CDN manzilida o'lcham va format (
w_400,f_auto),srcSet,sizes,fetchPriority; birinchi ekrandagi rasm uchun React 19preload(). Kutish — rasm kelguncha placeholder (rang, BlurHash, ThumbHash). Ko'rish — lightbox (to'liq ekranda galereya) va zoom. Kesish — kvadrat maydonda siljitish va kattalashtirish, keyincanvas→ kichik WebP. Kutubxonalar bor: yet-another-react-lightbox (+10.6 KB gzip), react-medium-image-zoom (+5.4), react-easy-crop (+7.3). Lekin react-easy-cropvazifalarning Trusted Types siyosati ostida ochilmadi — kanon kesishni o'zi yozdi.
Bu darsda
- Rasmni React'da
srcSet,sizes,loading,fetchPriorityvapreload()bilan to'g'ri yetkazasiz. - CDN transformatsiya manzilini yasaysiz va nega u imzolanishi kerakligini tushuntirasiz.
- Placeholder, klaviatura bilan boshqariladigan lightbox va zoom yozasiz.
- Rasm kesish hisobini va
canvas→ WebP qadamini yozasiz. - Uchta kutubxonani hajmi va xavfsizlik siyosati natijasi bo'yicha tanlaysiz.
Oldin bilishingiz kerak: Rasm unumdorligi, Responsive rasmlar I: srcset va sizes, Canvas amaliyotda, Fayl yuklash UI, Fokus boshqaruvi va klaviatura navigatsiyasi.
1. Nega bu kerak?
Oldingi darsda muharrirlar bilan matnni boyitdik. Endi rasmlar. Jasur aka «Bahor» saytiga menyu galereyasini qo'shmoqchi: har taomning rasmi. Uchta talab bor:
- telefonda sahifa tez ochilsin — 3 MB lik asl surat yuklanmasin;
- rasm bosilsa, u ekran bo'ylab katta ochilsin va qo'shni rasmlarga o'tish mumkin bo'lsin;
- xodimlar profil rasmini yuklaganda, uni dumaloq ramkaga moslab kesib olsin.
HTML qismida rasmni tez yetkazish atributlarini o'rgangansiz (Rasm unumdorligi). Bu dars ularni React komponentlari ichida ishlatadi va uch yangi narsani qo'shadi: CDN, lightbox va kesish.
Hayotiy o'xshatish: fotosalon. Asl surat arxivda turadi. Mijozga kerakli o'lchamda nusxa chiqariladi (CDN). Vitrinada kichik nusxa, bosilsa — katta ramkadagi nusxa (lightbox). Hujjat uchun esa suratning bir qismi kesib olinadi (crop).
2. Rasm React komponentida
2.1 Atributlar JSX'da
HTML'dagi atributlar JSX'da camelCase bo'ladi: srcset → srcSet, fetchpriority → fetchPriority. Ma'nosi o'sha (srcset va sizes, Rasm unumdorligi):
| Prop | Nima qiladi |
|---|---|
srcSet, sizes |
brauzer ekran eniga mos faylni o'zi tanlaydi |
width, height |
joy oldindan band — sahifa sakramaydi (CLS) |
loading="lazy" |
ko'rinish chegarasiga yaqinlashganda yuklanadi |
decoding="async" |
rasmni ochish (dekodlash) sahifani to'xtatmaydi |
fetchPriority |
"high" — birinchi ekrandagi asosiy rasm uchun |
Takroriy kodni bitta komponentga yig'amiz. Kod yozilganda brauzerga aynan nima ketishini ko'rish uchun DOM'dagi tegni konsolga chiqaramiz:
import { useEffect, useRef } from "react";
const WIDTHS = [400, 800, 1200];
function MenuImage({ name, alt, priority = false }) {
const ref = useRef(null);
useEffect(() => {
console.log(ref.current.outerHTML);
}, []);
return (
<img
ref={ref}
src={`/menyu/${name}-400.webp`}
srcSet={WIDTHS.map((w) => `/menyu/${name}-${w}.webp ${w}w`)
.join(", ")}
sizes="(min-width: 700px) 50vw, 100vw"
alt={alt}
width={400}
height={300}
loading={priority ? "eager" : "lazy"}
decoding="async"
fetchPriority={priority ? "high" : "auto"}
/>
);
}
export default function App() {
return <MenuImage name="osh" alt="Osh, ustida bedana tuxumi" />;
}Konsolda:
<img sizes="(min-width: 700px) 50vw, 100vw" alt="Osh, ustida bedana tuxumi" width="400" height="300" loading="lazy" decoding="async" fetchpriority="auto" srcset="/menyu/osh-400.webp 400w, /menyu/osh-800.webp 800w, /menyu/osh-1200.webp 1200w" src="/menyu/osh-400.webp">Natija oynasida rasm ko'rinmaydi — bu fayllar u yerda yo'q, faqat teg muhim. Qatorni o'qing:
fetchPriority→fetchpriority: React 19 bu propni taniydi va HTML atributiga aylantiradi.src— eng oxirida. Tekshiruvchimiz ko'rsatgan bu tartib tasodif emas: React<img>dasrcsetvasizesni avval yozadi. Aks holda ba'zi brauzerlarsrcni ko'rishi bilan uni yuklashni boshlab qo'yardi, keyinsrcsetdan boshqa faylni tanlardi — bitta rasm ikki marta yuklanardi.priority— bitta prop ikki atributni boshqaradi. Birinchi ekrandagi rasmgalazyqo'yish — eng ko'p uchraydigan xato (Rasm unumdorligi darsidagi qoida), komponent buni bir joyda hal qiladi.
2.2 preload(): rasmni oldindan aytib qo'yish
Asosiy rasm (LCP) ko'pincha JavaScript chizgandan keyin ma'lum bo'ladi: React komponenti ishlaguncha brauzer u haqda bilmaydi. React 19 da react-dom dan preload() funksiyasi bor. U <head> ga <link rel="preload"> qo'shadi va brauzer rasmni darhol so'raydi:
import { useEffect } from "react";
import { preload } from "react-dom";
export default function App() {
preload("/menyu/osh-800.webp", {
as: "image",
fetchPriority: "high",
});
useEffect(() => {
const link = document.head.querySelector("link[rel=preload]");
console.log(link.outerHTML);
}, []);
return <h1>Bugungi taom: osh</h1>;
}Konsolda:
<link rel="preload" href="/menyu/osh-800.webp" as="image" fetchpriority="high">preload ni render ichida chaqirsa bo'ladi — u hook emas, React bir xil manzilni ikki marta qo'shmaydi. imageSrcSet va imageSizes parametrlari bilan srcset'li rasmni ham oldindan aytish mumkin. Faqat bitta-ikkita eng muhim rasm uchun ishlating: hammasini "muhim" desangiz, hech biri muhim bo'lmay qoladi.
2.3 Placeholder: rasm kelguncha
Sekin internetda rasm o'rni bo'sh oq to'rtburchak bo'lib turadi. Rasm unumdorligi darsida placeholder turlarini ko'rgansiz: asosiy rang, kichik xira nusxa (LQIP), BlurHash. React'da g'oya — bitta state: yuklandi. Rasm kelguncha fon rangi ko'rinadi, onLoad dan keyin rasm silliq paydo bo'ladi.
Bu blok faqat saytda ishlaydi: tekshiruvchimiz (jsdom) rasmni umuman yuklamaydi va onLoad ni chaqirmaydi. Biz uni Chrome 155 da sinadik:
import { useState } from "react";
// Natija oynasida tashqi fayl yo'q — rasmni SVG'dan yasaymiz
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="400"
height="300"><rect width="400" height="300" fill="#c2410c"/>
<circle cx="200" cy="150" r="90" fill="#fde68a"/></svg>`;
const SRC = `data:image/svg+xml,${encodeURIComponent(svg)}`;
function SmoothImage({ src, alt, color }) {
const [loaded, setLoaded] = useState(false);
return (
<div style={{ background: color, width: 200 }}>
<img
src={src}
alt={alt}
width={200}
height={150}
onLoad={() => {
setLoaded(true);
console.log("yuklandi");
}}
style={{
opacity: loaded ? 1 : 0,
transition: "opacity 0.4s",
}}
/>
</div>
);
}
export default function App() {
return <SmoothImage src={SRC} alt="Lag'mon" color="#9a3412" />;
}Chrome 155 da (saytdagi natija oynasi hujjati — o'sha React, headless) konsol:
yuklandiPlaceholder rangi (color) rasmdan oldindan olinadi va ma'lumot bilan birga keladi: { src, color: "#9a3412" }. BlurHash va ThumbHash shu g'oyaning boyroq varianti: rasmning 20–30 baytlik "xira izi" serverda hisoblanadi va ma'lumotga qo'shiladi. Brauzerda kichik kutubxona uni xira rasmga aylantiradi (thumbHashToDataURL(bytes) kabi). ThumbHash rasm nisbatini va shaffoflikni ham saqlaydi. Bu ikki kutubxonani biz o'lchamadik — tanlasangiz, hajmini o'zingiz tekshiring.
Maslahat:
prefers-reduced-motionyoqilgan bo'lsa,transitionni o'chiring (UI animatsiya tamoyillari). Rasm baribir paydo bo'ladi, faqat silliq emas, darhol.
Tekshirib ko'ring: Nega
MenuImagekomponentidawidth={400} height={300}bor, garchi CSS rasmni baribir ekran eniga cho'zsa ham?
Javob
Bu ikki son brauzerga rasm nisbatini (4:3) aytadi. Rasm hali kelmagan bo'lsa ham, brauzer uning joyini to'g'ri balandlikda band qiladi. Busiz joy 0 px bo'ladi, rasm kelganda pastdagi matn birdan siljiydi — bu CLS (Performance API va Core Web Vitals). CSS'dagi width: 100%; height: auto esa o'lchamni o'zgartiradi, nisbatni emas.
3. CDN transformatsiyalari
3.1 Bitta asl fayl — ko'p variant
MenuImage uchta faylni kutadi: osh-400.webp, osh-800.webp, osh-1200.webp. Ularni kim yasaydi? Ikki yo'l bor:
- Build paytida. Rasmlar loyiha ichida bo'lsa, Vite plagini vite-imagetools ularni build vaqtida variantlarga aylantiradi. Import qatoridagi so'rov parametrlari aytadi:
import osh from "./osh.jpg?w=400;800&format=webp&as=srcset". Kanon bu plaginni o'lchamadi — faqat hujjatdagi g'oya. - So'rov paytida — rasm CDN'i. Foydalanuvchi yuklagan rasmlar uchun (menyu ertaga o'zgaradi) build yordam bermaydi. Rasm CDN'i asl faylni bir marta saqlaydi va har variantni birinchi so'rovda yasab, keshlaydi.
CDN transformatsiyasi (image transformation) — variant manzilning o'zida so'raladi. Ikki mashhur xizmat:
| Xizmat | Manzil ko'rinishi |
|---|---|
| Cloudinary (bulut) | …/image/upload/c_fill,w_400,h_300,f_auto,q_auto/osh.jpg |
| imgproxy (o'z serveringizda) | …/<imzo>/rs:fill:400:300/q:80/plain/<asl manzil>@webp |
Cloudinary'da c_fill — to'ldirib kesish, w_/h_ — o'lcham, f_auto — brauzer qabul qiladigan eng yaxshi format (AVIF yoki WebP), q_auto — sifat avtomatik. imgproxy — ochiq kodli dastur, uni o'z serveringizda ishga tushirasiz. Tashqi xizmatga bog'lanmaslik va maxfiylik uchun qulay.
3.2 Manzil yasovchi funksiya
Komponentlar CDN sintaksisini bilmasligi kerak — ular faqat "osh, 400 piksel" deydi. Bitta funksiya:
const CLOUD = "https://res.cloudinary.com/sizning_bulut";
function cdnUrl(id, { w, h, fit = "fill" }) {
const t = [`c_${fit}`, `w_${w}`, h && `h_${h}`, "f_auto", "q_auto"];
return `${CLOUD}/image/upload/${t.filter(Boolean).join(",")}/${id}`;
}
function cdnSrcSet(id, widths) {
return widths.map((w) => `${cdnUrl(id, { w })} ${w}w`).join(", ");
}
console.log(cdnUrl("menyu/osh.jpg", { w: 400, h: 300 }));
console.log(cdnSrcSet("menyu/osh.jpg", [400, 800]));Konsolda:
https://res.cloudinary.com/sizning_bulut/image/upload/c_fill,w_400,h_300,f_auto,q_auto/menyu/osh.jpg
https://res.cloudinary.com/sizning_bulut/image/upload/c_fill,w_400,f_auto,q_auto/menyu/osh.jpg 400w, https://res.cloudinary.com/sizning_bulut/image/upload/c_fill,w_800,f_auto,q_auto/menyu/osh.jpg 800wsizning_bulut — o'z hisobingiz nomi bilan almashtiriladi (biz hisob ochmadik — bu manzil ishlamaydi). h && `h_${h}` — balandlik berilmasa undefined, filter(Boolean) uni tashlaydi. Xizmatni almashtirsangiz (imgproxy, Cloudflare Images), faqat shu funksiya o'zgaradi.
4. Hujumchi nigohi
Transformatsiya manzilda bo'lsa, uni har kim o'zgartira oladi. Hujumchi skript yozadi: w_401, w_402, … w_5000 — har biri yangi variant. CDN har birini yasaydi (protsessor vaqti), saqlaydi (disk) va hisobingizga yozadi. O'n minglab so'rov — oy oxirida katta hisob yoki xizmatning to'xtashi. imgproxy'da esa plain/ dan keyingi manbaga boshqa saytdagi rasmni qo'yib, serveringizni begona rasmlar uchun ishlatish mumkin.
Himoya — imzolangan manzil (signed URL). Server maxfiy kalit bilan manzilning qolgan qismidan HMAC imzo hisoblaydi. CDN o'sha kalit bilan qayta hisoblaydi: bitta harf o'zgarsa — imzo mos kelmaydi, so'rov rad etiladi. imgproxy uslubidagi imzo (Node'da, kalit — namuna):
import { createHmac } from "node:crypto";
const KEY = Buffer.from("736563726574", "hex"); // namuna kalit
const SALT = Buffer.from("68656c6c6f", "hex"); // namuna "tuz"
function signPath(path) {
return createHmac("sha256", KEY)
.update(SALT)
.update(path)
.digest("base64url");
}
const source = "https://example.com/menyu/osh.jpg";
const path = `/rs:fill:400:300/q:80/plain/${source}@webp`;
console.log(`/${signPath(path)}${path}`);
const path2 = `/rs:fill:4000:3000/q:80/plain/${source}@webp`;
console.log(signPath(path2) === signPath(path));Konsolda:
/RuJmEa8veoFq6_Ky5_GPiHArC0KXY62fr1yHrGXukyg/rs:fill:400:300/q:80/plain/https://example.com/menyu/osh.jpg@webp
falseIkkinchi qator — hujumchining urinishi: o'lchamni 4000 ga o'zgartirsa, imzo boshqa bo'ladi. Kalitsiz to'g'ri imzoni topib bo'lmaydi. Uchta qoida:
- Kalit faqat serverda. Brauzer kodiga tushgan kalit — ochiq kalit: har kim DevTools'da ko'radi. Shuning uchun imzolangan manzillarni backend beradi (API javobida tayyor
src). Bu backend qismlarida (24-qismdan) yoziladi. - Ruxsat etilgan o'lchamlar ro'yxati. Cloudinary'da "strict transformations" rejimi faqat oldindan ruxsat berilgan variantlarni yasaydi. O'z kodingizda ham
WIDTHS = [400, 800, 1200]kabi qisqa ro'yxat. - Manba cheklovi. imgproxy'ga faqat o'z omboringizdagi rasmlarni ochishga ruxsat bering.
Tekshirib ko'ring: Nega imzo hisoblashda
SALTham bor, faqatKEYyetmasmidi?
Javob
Kalit yetadi — asosiy himoya o'sha. Tuz (salt) imzoni shu o'rnatishga xos qiladi: bir xil kalit tasodifan boshqa joyda ishlatilsa ham, imzolar mos kelmaydi. imgproxy ikkalasini ham talab qiladi, ikkalasi ham maxfiy. Muhim narsa — tuz kalitning o'rnini bosmaydi.
5. Lightbox: rasm to'liq ekranda
5.1 Talablar
Lightbox ("yorug' quti") — galereyadagi rasm bosilganda uni sahifa ustida, katta qilib ko'rsatadigan oyna. Fon qorayadi, o'rtada rasm, yonida "keyingi" va "oldingi". Bu — modal oyna, shuning uchun talablar o'sha:
- ochilganda fokus oyna ichiga o'tadi, yopilganda — bosilgan rasmga qaytadi;
Esc— yopadi,←va→— oldingi va keyingi rasm;- ekran o'quvchi oynani nomi bilan e'lon qiladi (
role="dialog",aria-label); - katta rasm faqat ochilganda yuklanadi — galereyada kichik nusxa.
5.2 O'z lightbox'imiz
Bobur faqat klaviatura bilan ishlaydi. Quyidagi blokni u qanday ishlatishini qadamlar bilan sinaymiz: «Manti» ni bosadi, ikki marta →, keyin Esc:
import { useEffect, useRef, useState } from "react";
const svg = (color) =>
"data:image/svg+xml," +
encodeURIComponent(
`<svg xmlns="http://www.w3.org/2000/svg" width="4" height="3">` +
`<rect width="4" height="3" fill="${color}"/></svg>`,
);
const PHOTOS = [
{ title: "Osh", alt: "Osh, qazi bilan", src: svg("#c2410c") },
{ title: "Manti", alt: "Manti, qatiq bilan", src: svg("#e5e7eb") },
{ title: "Lag'mon", alt: "Lag'mon, ko'kat bilan",
src: svg("#b45309") },
];
function Lightbox({ index, onClose, onMove }) {
const closeButton = useRef(null);
useEffect(() => closeButton.current.focus(), []);
const photo = PHOTOS[index];
function handleKey(event) {
if (event.key === "Escape") onClose();
if (event.key === "ArrowRight") onMove(1);
if (event.key === "ArrowLeft") onMove(-1);
}
return (
<div
role="dialog"
aria-modal="true"
aria-label={photo.title}
onKeyDown={handleKey}
style={{ position: "fixed", inset: 0, background: "#000c" }}
>
<img src={photo.src} alt={photo.alt} width={320} height={240} />
<p style={{ color: "white" }}>
{index + 1} / {PHOTOS.length} — {photo.title}
</p>
<button ref={closeButton} onClick={onClose}>
Yopish
</button>
</div>
);
}
export default function App() {
const [open, setOpen] = useState(null); // ochiq rasm indeksi
const opener = useRef(null); // qaysi tugma ochdi
function close() {
setOpen(null);
// yopilgach fokus — ochgan tugmaga
requestAnimationFrame(() => {
opener.current.focus();
console.log("fokus:", document.activeElement.textContent);
});
}
return (
<>
<ul>
{PHOTOS.map((photo, i) => (
<li key={photo.title}>
<button
onClick={(event) => {
opener.current = event.currentTarget;
setOpen(i);
}}
>
<img src={photo.src} alt="" width={40} />{" "}
{photo.title}
</button>
</li>
))}
</ul>
{open !== null && (
<Lightbox
index={open}
onClose={close}
onMove={(step) =>
setOpen((i) => (i + step + PHOTOS.length) % PHOTOS.length)
}
/>
)}
</>
);
}Konsolda:
fokus: MantiEkranda:
Osh
Manti
Lag'mon// harakat: dagi ekran(...) qadamlari oraliq holatni tekshirdi. Bobur ko'rgan narsa qadamma-qadam:
| Qadam | Holat (open) |
Ekranda | Fokus |
|---|---|---|---|
| «Manti» bosildi | 1 |
«2 / 3 — Manti», qora fon | «Yopish» |
→ |
2 |
«3 / 3 — Lag'mon» | «Yopish» |
→ |
0 (aylanib) |
«1 / 3 — Osh» | «Yopish» |
Esc |
null |
faqat ro'yxat | «Manti» tugmasi |
Oxirgi qator — yuqoridagi Ekranda: blokining o'zi: oyna yopildi, uchta tugma qoldi. Muhim joylar:
- Aylanma o'tish.
(i + step + PHOTOS.length) % PHOTOS.length— oxirgidan keyin birinchisi, birinchidan oldin oxirgisi.+ PHOTOS.lengthmanfiy qoldiqdan saqlaydi: JavaScript'da-1 % 3—-1, indeks bo'la olmaydi. - Fokus kirdi —
useEffect«Yopish» tugmasiga fokus berdi. Klaviatura hodisalari endi oyna ichidagi tugmadandivningonKeyDowniga ko'tariladi. - Fokus qaytdi —
openerref'i bosilgan tugmani eslab qoldi. Yopilgandan keyin «Manti» yana fokusda. Bobur sahifadagi joyini yo'qotmadi.
Bu blok — o'quv modeli. Unda ikki narsa yetishmaydi: Tab bilan oynadan chiqib ketish mumkin (fokus tuzog'i yo'q) va orqadagi sahifa ekran o'quvchi uchun "ochiq". Loyihada <dialog> ning showModal() usuli ikkalasini ham bepul beradi: orqa sahifa inert bo'ladi, Esc o'zi yopadi (<dialog> va Popover API). vazifalar kanoni ham 20-qismdan beri modal oynalar uchun native <dialog> ishlatadi.
5.3 yet-another-react-lightbox
Galereya katta bo'lsa — barmoq bilan surish, kichik rasmlar qatori, zoom, video — tayyor kutubxona olgan ma'qul. Eng ko'p ishlatiladiganlaridan biri — yet-another-react-lightbox (YARL). Saytdagi natija oynasida u yo'q, kod hujjat bo'yicha:
import Lightbox from "yet-another-react-lightbox";
import Zoom from "yet-another-react-lightbox/plugins/zoom";
import "yet-another-react-lightbox/styles.css";
function Gallery({ photos }) {
const [index, setIndex] = useState(-1); // -1 — yopiq
return (
<>
{photos.map((p, i) => (
<button key={p.id} onClick={() => setIndex(i)}>
<img src={p.thumb} alt={p.alt} />
</button>
))}
<Lightbox
open={index >= 0}
index={index}
close={() => setIndex(-1)}
slides={photos.map((p) => ({ src: p.big, alt: p.alt }))}
plugins={[Zoom]}
/>
</>
);
}slides — rasmlar ro'yxati, plugins — qo'shimcha imkoniyatlar (Zoom, Thumbnails, Fullscreen, Captions). Klaviatura, surish va fokus kutubxona ichida. Kanon tajribasida (3.32.2) u +10.6 KB JS va +1.4 KB CSS qo'shdi. vazifalar ning CSP va Trusted Types siyosati ostida ochildi va 0 buzilish berdi.
6. Zoom: rasmni joyida kattalashtirish
Lightbox — galereya uchun. Bitta rasmni (masalan, chek yoki xarita) yaqindan ko'rish uchun zoom yetadi: rasm bosilganda o'z joyidan silliq kattalashib, ekran markaziga chiqadi. react-medium-image-zoom shunday qiladi — oddiy <img> ni o'rab qo'yasiz:
import Zoom from "react-medium-image-zoom";
import "react-medium-image-zoom/dist/styles.css";
<Zoom>
<img src="/menyu/osh-400.webp" alt="Osh" width={400} height={300} />
</Zoom>;Kanon tajribasida (5.4.9): +5.4 KB JS, +0.6 KB CSS, CSP 0. Telefonda esa ko'pincha kutubxona kerak emas: brauzerning o'zi ikki barmoq bilan kattalashtiradi. Faqat CSS'da touch-action: none qo'yib, buni o'chirib qo'ymang.
Tekshirib ko'ring: Galereyada 40 ta rasm. Lightbox'dagi katta rasmlarni sahifa ochilishi bilan yuklab qo'yish kerakmi?
Javob
Yo'q. Katta rasm faqat ochilganda kerak. 40 ta katta rasmni oldindan yuklash — o'nlab megabayt, ko'pchiligi hech qachon ochilmaydi. Galereyada kichik nusxa (thumb) va loading="lazy", lightbox'da — ochilgan rasm va eng ko'pi bilan qo'shnilari (keyingi bosish tez bo'lsin). YARL ham sukut bo'yicha joriy rasm atrofidagi bir nechtasini yuklaydi.
7. Rasmni kesish (crop)
7.1 Kesish — bu hisob
Profil rasmi dumaloq ramkada turadi, foydalanuvchi esa keng surat yuklaydi. Unga maydon beramiz: rasm kvadrat ichida, foydalanuvchi uni siljitadi va kattalashtiradi. Ko'rinadigan qism — kesib olinadigan qism.
Hammasi uchta sonda: zoom (1–3), x va y (−1 dan 1 gacha — chap chetdan o'ng chetgacha). Ulardan ikki narsa hisoblanadi:
- Ko'rinish — rasm ekranda qanday o'lchamda va qayerda turadi (CSS
transform). - Manba kvadrati — asl rasmning qaysi piksellari olinadi (canvas uchun).
vazifalar kanonidagi kesish.ts aynan shu ikkisini sof funksiya bilan hisoblaydi. Uning yadrosi (kanon kodi, turlari qisqartirilgan):
const KESISH_OLCHAMI = 256;
type Kesish = { zoom: number; x: number; y: number };
function kesishOrni(
eni: number,
boyi: number,
{ zoom, x, y }: Kesish,
maydon = KESISH_OLCHAMI,
) {
const masshtab = Math.max(maydon / eni, maydon / boyi) * zoom;
const korinishEni = eni * masshtab;
const korinishBoyi = boyi * masshtab;
const ortiqX = korinishEni - maydon;
const ortiqY = korinishBoyi - maydon;
const chap = -ortiqX / 2 - (x * ortiqX) / 2 + 0;
const tepa = -ortiqY / 2 - (y * ortiqY) / 2 + 0;
return {
korinishEni,
korinishBoyi,
chap,
tepa,
manba: {
x: -chap / masshtab + 0,
y: -tepa / masshtab + 0,
tomon: maydon / masshtab,
},
};
}
const markaz = kesishOrni(800, 400, { zoom: 1, x: 0, y: 0 });
console.log(markaz.korinishEni, markaz.chap, markaz.manba);
console.log(kesishOrni(800, 400, { zoom: 1, x: 1, y: 0 }).manba.x);
console.log(kesishOrni(400, 400, { zoom: 2, x: 1, y: 1 }).manba);Konsolda:
512 -128 { x: 200, y: 0, tomon: 400 }
400
{ x: 200, y: 200, tomon: 200 }Birinchi qatorni qadam-qadam o'qiymiz. Rasm 800×400, maydon 256×256:
masshtab = max(256/800, 256/400) = 0.64. Kattasi olinadi — rasm maydonni to'liq yopsin (CSS'dagiobject-fit: coverg'oyasi). Kichigi olinsa, yonlarda bo'sh joy qolardi.- Ko'rinishda rasm 512×256. Maydondan 256 piksel ortiq, markazda turishi uchun chapga 128 surilgan:
chap = -128. - Asl rasmda bu —
x: 200dan boshlanadigan 400×400 kvadrat. 800 lik rasmning o'rtasi.
Ikkinchi qator: x: 1 — o'ng chet, manba 400 dan 800 gacha. Uchinchisi: zoom: 2 — manba tomoni ikki baravar kichik (200), ya'ni yaqinroq ko'rinadi. + 0 — -0 ni 0 ga aylantiradi: -0 ekranda farq qilmaydi, lekin testdagi deepEqual uni ajratadi.
7.2 Boshqaruv: avval slider, keyin sudrash
Rasmni sichqoncha bilan sudrash qulay. Lekin Bobur sichqoncha ishlatmaydi, Malika ekranni ko'rmaydi. Shuning uchun kanonning asosiy boshqaruvi — uchta <input type="range"> (yorliqli). Ular klaviatura bilan (← →) ishlaydi va ekran o'quvchi qiymatini o'qiydi. Sudrash — qo'shimcha qulaylik, o'rniga emas.
Sliderdan kelgan qiymat bilan hisob qanday o'zgarishini ko'ramiz. Natija oynasida slider'ni suring:
import { useState } from "react";
const SIDE = 256;
function cropSquare(w, h, zoom) {
const scale = Math.max(SIDE / w, SIDE / h) * zoom;
return Math.round(SIDE / scale);
}
export default function App() {
const [zoom, setZoom] = useState(1);
return (
<>
<label htmlFor="zoom">Kattalashtirish</label>{" "}
<input
id="zoom"
type="range"
min={1}
max={3}
step={0.5}
value={zoom}
onChange={(event) => setZoom(Number(event.target.value))}
/>
<p>
Zoom {zoom}: 800×400 rasmdan {cropSquare(800, 400, zoom)} px
lik kvadrat olinadi
</p>
</>
);
}Ekranda:
Kattalashtirish
Zoom 1.5: 800×400 rasmdan 267 px lik kvadrat olinadiZoom 1 da — 400 px (rasm balandligi to'liq), 1.5 da — 267, 3 da — 133. Kvadrat kichraygani sari natija yaqinroq, lekin xiraroq: 133 pikseldan 256 piksel yasalsa, har asl piksel taxminan ikki baravar cho'ziladi.
7.3 Canvas: kesib, WebP qilish
Hisob tayyor bo'lsa, haqiqiy kesish — Canvas amaliyotda darsidagi drawImage ning 9 argumentli shakli: manba kvadrati → natija o'lchami. Keyin toBlob — fayl. Bu blok faqat saytda ishlaydi (jsdom'da canvas yo'q). Rasm sifatida boshqa canvas'ni olamiz — tashqi fayl kerak emas:
function makeSource() {
// 800×400 sinov rasmi: chapda to'q sariq, o'ngda yashil
const c = document.createElement("canvas");
c.width = 800;
c.height = 400;
const ctx = c.getContext("2d");
ctx.fillStyle = "#c2410c";
ctx.fillRect(0, 0, 400, 400);
ctx.fillStyle = "#1f6f4a";
ctx.fillRect(400, 0, 400, 400);
return c;
}
function crop(source, box, size, done) {
const canvas = document.createElement("canvas");
canvas.width = size;
canvas.height = size;
canvas.getContext("2d").drawImage(
source,
box.x, box.y, box.tomon, box.tomon, // manbadan
0, 0, size, size, // natijaga
);
canvas.toBlob(done, "image/webp", 0.85);
}
export default function App() {
return (
<button
onClick={() =>
crop(makeSource(), { x: 200, y: 0, tomon: 400 }, 256, (b) =>
console.log(b.type, b.size > 0),
)
}
>
Kesish
</button>
);
}Chrome 155 da «Kesish» bosilgach konsol:
image/webp trueHajm aniq son emas — u rasmga bog'liq. vazifalar da o'lchangani: 512×512 PNG 7.5 KB → kesilgan 256×256 WebP 2.2 KB. Serverga asl fayl emas, kichigi ketadi. Yana bir foyda: canvas faqat piksellarni chizadi. Suratdagi yashirin ma'lumotlar (EXIF: telefon modeli, ba'zan GPS koordinatalari) yangi faylga o'tmaydi.
7.4 react-easy-crop va Trusted Types
Tayyor yechim — react-easy-crop: sudrash, barmoq bilan kattalashtirish, dumaloq maska, onCropComplete da tayyor piksel koordinatalari:
import Cropper from "react-easy-crop";
<div style={{ position: "relative", width: 300, height: 300 }}>
<Cropper
image={url}
crop={crop}
zoom={zoom}
aspect={1}
cropShape="round"
onCropChange={setCrop}
onZoomChange={setZoom}
onCropComplete={(_, pixels) => setPixels(pixels)}
/>
</div>;Kutubxona kichik — kanon tajribasida (6.2.4) +7.3 KB. Lekin vazifalar CSP'si va Trusted Types ostida komponent umuman chizilmadi. Konsoldagi haqiqiy xatolar (kutubxonalar-21.txt):
This document requires 'TrustedHTML' assignment. The action has been blocked.
Failed to set the 'innerHTML' property on 'Element': This document requires 'TrustedHTML' assignmentTarjimasi: "Bu hujjat TrustedHTML talab qiladi, amal to'sildi" va "innerHTML xususiyatiga yozib bo'lmadi". react-easy-crop o'z CSS'ini sahifaga <style> qilib, innerHTML orqali qo'shadi. Trusted Types esa innerHTML ga oddiy satrni taqiqlaydi. Siyosatsiz loyihada kutubxona ishlaydi (tajribada CSP'siz — ochildi). Ikki yo'l bor edi: siyosatga istisno qo'shish yoki o'zimiz yozish. Kesish hisobi izohlari bilan 60 qatorcha ekan — kanon ikkinchisini tanladi.
- react-medium-image-zoom 5.4.9+0.6 KB CSS, CSP 05,4 KB
- react-easy-crop 6.2.4Trusted Types ostida ochilmadi7,3 KB
- yet-another-react-lightbox 3.32.2+1.4 KB CSS, CSP 010,6 KB
- o'z kesish (vazifalar, holat 12)birinchi yuklash farqi1 KB
Manba: O'lchandi: kanon tajribasi, Vite 8.3.3, gzip -9, Chrome 155, natijalar/tajriba/kutubxonalar-21.txt va tekshiruv-12.txt, 2026-10-17
8. Vazifalar qadami: profil rasmini kesish (holat 12)
8.1 Qayerga qo'shiladi
Fayl yuklash UI darsida vazifalar ga profil rasmi qo'shilgan edi: tanlash, oldindan ko'rish, «Saqlash». Endi oraga bitta ixtiyoriy qadam qo'shiladi — «Kesish…». Oqim:
flowchart TB
A["Fayl tanlandi"] --> B["blob: URL — oldindan ko'rish"]
B --> C{"«Kesish…» bosildimi?"}
C -- yo'q --> S["«Saqlash» — asl fayl"]
C -- ha --> D["RasmKesish: 3 slider + sudrash"]
D --> E["kesishOrni — manba kvadrati"]
E --> F["canvas drawImage → 256×256"]
F --> G["toBlob WebP 0.85 → File"]
G --> S2["«Saqlash» — kesilgan fayl"]
D -- «Bekor qilish» --> BDiagrammaga qarang: kesish majburiy emas. Asl rasm ham saqlanadi — foydalanuvchi tanlaydi.
git switch -c feat/rasm-kesishUchta yangi fayl: kesish.ts (hisob — yuqorida), RasmKesish.tsx (komponent), tekshiruv/kesish.test.js (3 test). ProfilRasmi.tsx va asos.css o'zgaradi.
8.2 RasmKesish.tsx
Kanon komponentining boshidagi izoh qarorni o'zi aytadi:
// RasmKesish.tsx — profil rasmini kvadratga kesish
// (21/#12, 19/#36 davomi).
// O'z komponentimiz: react-easy-crop 6.2.4 o'lchandi
// (+7.3 KB gzip) — Trusted Types ostida ochilmadi
// (o'z <style> ini innerHTML bilan qo'shadi;
// natijalar/tajriba/kutubxonalar-21.txt). Bu yerda:
// - boshqaruv — uchta oddiy slider (kattalashtirish,
// gorizontal, vertikal): klaviatura va ekran o'quvchi
// bilan ishlaydi (sudrash — qo'shimcha)
// - rasm — CSS transform (style prop: CSSOM,
// CSP style-src 'self' ga mos)
// - natija — <canvas> da 256×256 WebP: serverga asl fayl
// emas, kichigi ketadi"style prop: CSSOM" — muhim detal. React style={{ transform: … }} ni HTML atributi qilib yozmaydi, balki element.style orqali o'rnatadi (CSS Object Model). CSP'ning style-src 'self' qoidasi <style> teglarini va style="…" atributlarini to'sadi, CSSOM'ni esa to'smaydi. Shuning uchun React style prop'i CSP bilan ishlaydi, react-easy-crop'ning <style> tegi esa yo'q.
Komponentning rasmi va kesish funksiyasi (kanon kodi aynan):
<img
ref={rasm}
src={url}
alt=""
draggable={false}
onLoad={(event) =>
setOlcham({
eni: event.currentTarget.naturalWidth,
boyi: event.currentTarget.naturalHeight,
})
}
style={
orin === null
? { visibility: "hidden" }
: {
width: orin.korinishEni,
height: orin.korinishBoyi,
transform: `translate(${orin.chap}px, ${orin.tepa}px)`,
}
}
/>Rasm yuklanmaguncha uning o'lchami noma'lum — orin null, rasm yashirin. onLoad asl o'lchamni (naturalWidth) beradi, keyin hisob ishlaydi. alt="" — bu rasm bezak: yonidagi guruh aria-label="Rasmni kesish" va slider'lar yetarli ma'lumot beradi.
const { manba } = orin;
ctx.drawImage(
element,
manba.x,
manba.y,
manba.tomon,
manba.tomon,
0,
0,
KESISH_OLCHAMI,
KESISH_OLCHAMI,
);
canvas.toBlob(
(blob) => {
if (blob !== null) {
onTayyor(
new File([blob], "profil.webp", { type: blob.type }),
);
}
},
"image/webp",
0.85,
);blob.type — haqiqiy tur. WebP'ni bilmaydigan brauzer toBlob da PNG qaytaradi. Nom profil.webp qolsa ham, server turi bo'yicha to'g'ri ish qiladi.
8.3 ProfilRasmi.tsx: fokus qaytishi
Kesish oynasi yopilganda (Kesish yoki Bekor qilish) fokus qayerga tushadi? Oyna DOM'dan yo'qoladi va fokus <body> ga "yiqiladi" — Bobur sahifa boshiga qaytib qoladi. Kanon diff'i:
+ onBekor={() => {
+ flushSync(() => setKesilmoqda(false));
+ kesTugmasi.current?.focus();
+ }}Muammo: setKesilmoqda(false) dan keyin «Kesish…» tugmasi hali DOM'da yo'q — React uni keyinroq chizadi. flushSync (State II darsida ko'rgansiz) React'ni darhol chizishga majbur qiladi, keyingi qatorda tugma bor va fokus oladi. Kanon avval requestAnimationFrame ni sinab ko'rgan — e2e testida beqaror chiqdi, flushSync barqaror. Bu xatoni brauzer testi topdi. Yuqoridagi lightbox o'quv modelida requestAnimationFrame ishlatdik — kichik misolda yetadi, lekin ishonchli yo'l — flushSync.
8.4 Natija va narx
Tekshiruvlar (haqiqiy, holat 12):
npm test— node:test 224 (+3 kesish testi), Vitest 65;lint,format:check,tip,knip—exit 0.- axe — 0 buzilish, yangi ko'rinish «profil rasmini kesish» bilan (43 qoida o'tdi).
- e2e-21 — 36 ta, kesishga oidlari:
✅ kesish: 3 slider (yorliqli), maydon 256 px: [["Kattalashtirish","Gorizontal","Vertikal"],256]
✅ klaviatura: zoom 1.5 — rasm 384 px: ["1.5","384px"]
Kesildi: 256×256, 2.2 KB (asli 7.5 KB). Saqlashga tayyor.
✅ natija: 256×256, xabarda yangi va asl hajm: [256,256,true]
✅ fokus «Kesish…» ga qaytdi: "rasm-kes"
✅ kesish: CSP buzilishi yo'q: []Ikkinchi qatorni hisob bilan solishtiring: kvadrat rasm, zoom 1.5 → 256 × 1.5 = 384 piksel. Birinchi yuklash JS — 220.5 → 221.5 KB gzip (+1.0), CSS — 4.99 KB.
git add -A
git commitCommit (kanon): feat: profil rasmini kesish — o'z komponent, canvas → 256×256 WebP.
9. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| birinchi ekrandagi rasm kech chiqadi | unga ham loading="lazy" |
priority prop: eager + fetchPriority="high" |
| rasm kelganda matn sakraydi | width/height yo'q |
nisbat beradigan ikki son |
| CDN hisobi kutilmaganda katta | imzosiz transformatsiya manzili | imzo + ruxsat etilgan o'lchamlar |
| lightbox yopilgach fokus sahifa boshida | ochgan tugma eslab qolinmagan | ref + flushSync bilan fokus qaytarish |
-1 % 3 → -1, rasm topilmadi |
manfiy qoldiq | (i + step + n) % n |
| kesilgan rasm cho'zilgan | drawImage da nisbat buzilgan |
manba ham, natija ham kvadrat |
konsolda requires 'TrustedHTML' |
kutubxona innerHTML bilan <style> qo'shadi |
siyosat yoki o'z komponent |
| kesish oynasi rasm o'rnida bo'sh | o'lcham onLoad dan oldin o'qilgan |
naturalWidth ni onLoad da oling |
10. Mashqlar
Mashqlar kurs/mashqlar/21/12-rasm-ui/ da. Jonli yechimlarni «Ishga tushir» bilan sinang.
1-mashq (oson): bo'sh joylarni to'ldiring
- Birinchi ekrandagi asosiy rasmga qo'yiladigan prop qiymati:
fetchPriority="" - Rasm kelguncha joyni band qiladigan ikki atribut:
widthva - React 19 da rasmni
<head>orqali oldindan so'raydigan funksiya: - Imzosiz CDN manzilida hujumchi o'zgartiradigan qism:
- react-easy-crop
vazifalarda ochilmadi, chunki u<style>ni bilan qo'shadi
Yechim
- high — brauzer bu rasmni boshqa rasmlardan oldin so'raydi.
- height —
widthbilan birga nisbatni beradi, CLS bo'lmaydi. - preload —
react-domdan, render ichida chaqirsa bo'ladi. - o'lcham (yoki boshqa transformatsiya parametri) — har yangi qiymat yangi variant va xarajat.
- innerHTML — Trusted Types oddiy satrni
innerHTMLga yozishni taqiqlaydi.
2-mashq (o'rta): lightbox'da Home va End
«O'z lightbox'imiz» blokini oling va ikki tugma qo'shing: Home — birinchi rasm, End — oxirgisi. Rasm sarlavhasi yonida «(oxirgi)» so'zi chiqsin, agar oxirgi rasm ochiq bo'lsa. Sinov: «Osh» ni bosing, End, keyin Escape — konsolda fokus: Osh.
Ishora: onMove o'rniga onJump(index) prop'i qo'shing; handleKey ga ikki if. Fokus qaytishi — o'sha opener ref'i.
Yechim
import { useEffect, useRef, useState } from "react";
const PHOTOS = [
{ title: "Osh", alt: "Osh, qazi bilan" },
{ title: "Manti", alt: "Manti, qatiq bilan" },
{ title: "Lag'mon", alt: "Lag'mon, ko'kat bilan" },
];
function Lightbox({ index, onClose, onJump }) {
const closeButton = useRef(null);
useEffect(() => closeButton.current.focus(), []);
const last = PHOTOS.length - 1;
const photo = PHOTOS[index];
function handleKey(event) {
if (event.key === "Escape") onClose();
if (event.key === "Home") onJump(0);
if (event.key === "End") onJump(last);
}
return (
<div role="dialog" aria-modal="true" aria-label={photo.title}
onKeyDown={handleKey}>
<p>
{index + 1} / {PHOTOS.length} — {photo.title}
{index === last && " (oxirgi)"}
</p>
<button ref={closeButton} onClick={onClose}>
Yopish
</button>
</div>
);
}
export default function App() {
const [open, setOpen] = useState(null);
const opener = useRef(null);
function close() {
setOpen(null);
requestAnimationFrame(() => {
opener.current.focus();
console.log("fokus:", document.activeElement.textContent);
});
}
return (
<>
{PHOTOS.map((photo, i) => (
<button
key={photo.title}
onClick={(event) => {
opener.current = event.currentTarget;
setOpen(i);
}}
>
{photo.title}
</button>
))}
{open !== null && (
<Lightbox index={open} onClose={close} onJump={setOpen} />
)}
</>
);
}Konsolda:
fokus: OshonJump={setOpen} — state funksiyasini to'g'ridan-to'g'ri prop qilib berish mumkin: u indeksni oladi. index === last && " (oxirgi)" — shart yolg'on bo'lsa false chiqadi, React uni chizmaydi.
3-mashq (qiyin): CDN srcset va ruxsat etilgan o'lchamlar
cdnSrcSet(id, widths) funksiyasini xavfsizroq qiling:
- Faqat
ALLOWED = [320, 640, 960, 1280]dagi o'lchamlar ishlatilsin; ro'yxatda yo'q o'lcham eng yaqin kattasiga almashsin (700 → 960). 1280 dan kattasi — 1280. - Takror o'lchamlar bir marta chiqsin (700 va 900 ikkalasi 960 bo'lsa — bitta).
- Natija o'sish tartibida.
Sinov: cdnSrcSet("osh.jpg", [700, 300, 900, 2000]) → uchta variant: 320, 960, 1280.
Ishora: eng yaqin kattasi — ALLOWED.find((a) => a >= w) ?? 1280; takrorsiz — Set; tartib — sort((a, b) => a - b).
Yechim
const CLOUD = "https://res.cloudinary.com/sizning_bulut";
const ALLOWED = [320, 640, 960, 1280];
function cdnUrl(id, w) {
return `${CLOUD}/image/upload/c_fill,w_${w},f_auto,q_auto/${id}`;
}
function cdnSrcSet(id, widths) {
const max = ALLOWED[ALLOWED.length - 1];
const safe = widths.map((w) => ALLOWED.find((a) => a >= w) ?? max);
return [...new Set(safe)]
.sort((a, b) => a - b)
.map((w) => `${cdnUrl(id, w)} ${w}w`);
}
for (const line of cdnSrcSet("osh.jpg", [700, 300, 900, 2000])) {
console.log(line);
}Konsolda:
https://res.cloudinary.com/sizning_bulut/image/upload/c_fill,w_320,f_auto,q_auto/osh.jpg 320w
https://res.cloudinary.com/sizning_bulut/image/upload/c_fill,w_960,f_auto,q_auto/osh.jpg 960w
https://res.cloudinary.com/sizning_bulut/image/upload/c_fill,w_1280,f_auto,q_auto/osh.jpg 1280wEndi kod qancha xilma-xil o'lcham so'ramasin, CDN faqat to'rt variant yasaydi — kesh samarali, xarajat oldindan ma'lum. Funksiya massiv qaytaradi; srcSet uchun .join(", ") qilasiz. Bu klient tomondagi tartib, himoya emas: hujumchi manzilni qo'lda yozishi mumkin. Himoya — imzo va CDN sozlamasidagi ro'yxat.
4-mashq: Vazifalar qadami
feat/rasm-kesish branch'ida «Vazifalar qadami» dagi o'zgarishlarni qiling: kesish.ts va uning 3 testi, RasmKesish.tsx, ProfilRasmi.tsx dagi «Kesish…» tugmasi va flushSync bilan fokus, asos.css dagi .kesish-maydon.
Yechim
Tekshiruv ro'yxati:
npm test— node 224, Vitest 65;npm run tip,lint,knip— toza.- Sozlamalar → rasm tanlang → «Kesish…»: dumaloq maydon, uchta yorliqli slider. Klaviatura bilan kattalashtirish ishlaydi, sichqoncha bilan sudrash ham.
- «Kesish» — xabarda yangi va asl hajm («Kesildi: 256×256, … (asli …)»), fokus «Kesish…» tugmasida.
- «Bekor qilish» — asl rasm joyida, fokus yana «Kesish…» da.
- DevTools konsolida CSP xatosi yo'q;
npm run build— birinchi yuklash ~221.5 KB.
11. Real ishda
- Onlayn do'konlar va menyular — mahsulot galereyasi lightbox bilan, rasmlar CDN'dan
srcsetbilan. Rasm CDN'i (Cloudinary, imgproxy, Cloudflare Images) deyarli har katta loyihada bor. - Profil va hujjatlar — avatar kesish, pasport rasmini yuklashdan oldin qirqish. Ko'p jamoalar react-easy-crop ishlatadi; qattiq CSP'li jamoalar — o'z komponentini.
- Kod ko'rigida so'raladi: "LCP rasmga
lazyqo'yilmaganmi?", "CDN manzili imzolanganmi?", "lightbox fokusni qaytaradimi?". - Intervyu savollari: "
srcsetvasizesqanday ishlaydi?", "placeholder qanday qilinadi?", "rasmni brauzerda qanday kesasiz?", "imzolangan URL nega kerak?".
Xulosa
- React'da rasm:
srcSet,sizes,width/height,loading,decoding,fetchPriority; asosiy rasm uchunpreload(). Reactsrcni oxirida yozadi. - Placeholder — rang yoki BlurHash/ThumbHash,
onLoaddan keyin rasm. - CDN transformatsiyasi variantni manzilda so'raydi; manzil serverda imzolanadi, o'lchamlar ro'yxat bilan cheklanadi.
- Lightbox — modal oyna: fokus ichkariga va qaytib,
Escva strelkalar; loyihada<dialog>yoki YARL (+10.6 KB). - Kesish = hisob (
maxmasshtab, siljish ulushi) + canvasdrawImage+toBlobWebP; boshqaruv — slider, sudrash qo'shimcha. - react-easy-crop Trusted Types ostida ochilmadi → kanon o'z komponenti (+1.0 KB).
Keyingi dars: Video va audio — React'da pleyer: <video> ref bilan, holatni hodisalardan olish, HLS oqimi, YouTube embed va Media Session.
Manbalar
- React:
<img>(fetchPriority),preload— react.dev/reference/react-dom/components/img, react.dev/reference/react-dom/preload - MDN:
HTMLImageElement.fetchPriority,CanvasRenderingContext2D.drawImage(),HTMLCanvasElement.toBlob() - Cloudinary: "Image transformations", "Strict transformations" — cloudinary.com/documentation
- imgproxy: "Signing a URL", "Processing options" — docs.imgproxy.net
- yet-another-react-lightbox — yet-another-react-lightbox.com; react-medium-image-zoom — npm; react-easy-crop — npm
- ThumbHash — evanw.github.io/thumbhash; BlurHash — blurha.sh; vite-imagetools — npm
- Kanon:
vazifalarholat 12,natijalar/tekshiruv-12.txt,12-axe.txt,brauzer-tekshiruv/natija-21-12.txt,natijalar/tajriba/kutubxonalar-21.txt(2026-10-17)
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!