IlmHamroh
JavaScript Full-stack/20-qism. React: UI kutubxonalar va animatsiya19/30-dars24 daqiqa
Mundarija (30)

Node-diagramma va canvas React'da: React Flow, Konva va qachon SVG, qachon canvas

Qisqacha: Ekranda tugunlar va ularni bog'lagan chiziqlar bo'lsa (jarayon sxemasi, workflow muharriri) — React Flow (@xyflow/react): tugun va bog'lanish oddiy massiv, sudrash, kattalashtirish va ulash tayyor. Erkin chizma, ko'p shakl va tez harakat kerak bo'lsa — canvas va uning React qobig'i react-konva. Kam elementli, matnli va a11y muhim bo'lgan sxemani esa o'zingiz SVG bilan chizasiz. Tanlovda hajm (+56 va +95 KB) va CSP ham hisobga olinadi: Konva qattiq CSP ostida umuman ochilmadi.

Bu darsda

  • SVG va canvas qanday farq qilishini va qaysi biri qachon to'g'ri ekanini tushuntira olasiz.
  • Tugun-bog'lanish modelini (nodes, edges) o'zingiz React'da chizasiz va klaviatura bilan boshqarasiz.
  • React Flow'da diagramma, custom node va ulashni yozasiz, uning tuzoqlarini bilasiz.
  • Konva bilan canvas sahnasini quradingiz va canvas'ning a11y muammosini hal qilish yo'llarini bilasiz.
  • Kutubxona tanlashda hajm va CSP o'lchoviga tayanasiz: Konva nega vazifalar CSP'si ostida ochilmaganini tushuntira olasiz.

Oldin bilishingiz kerak: Drag and drop: dnd-kit, SVG HTML ichida va ikonkalar, Canvas 2D asoslari, Frontend xavfsizligi React'da.

1. Nega bu kerak?

Oldingi darsda panellar va masonry bilan sahifa tartibini qurdik. Bugun ekranda "rasm" kerak bo'ladi.

Jasur aka ikki narsa so'radi. Birinchisi — buyurtma yo'li sxemasi: «Qabul → Oshxona → Kuryer». Oshpaz yangi bosqich qo'sha olsin, masalan «Qadoqlash», va uni kerakli joyga ulasin. Ikkinchisi — zal xaritasi: 12 ta stol, ularni sichqoncha bilan surib joylashtirish mumkin bo'lsin.

Sardor birinchi bo'lib <div> lar va position: absolute bilan boshladi. Bir soatdan keyin savollar ko'paydi: chiziqni ikki quti orasiga qanday tortaman? Kattalashtirish (zoom) qanday bo'ladi? 200 ta stol bo'lsa-chi?

Bu darsda ikki xil vazifani ajratamiz:

  • Node-diagramma (node-based UI) — tugunlar (node) va ularni bog'lagan chiziqlar (edge). Workflow muharriri, jarayon sxemasi, tashkiliy tuzilma.
  • Erkin chizma — ko'p shakl, rasm, tez harakat. Zal xaritasi, rasm muharriri, o'yin, grafik.

Hayotiy o'xshatish: metro xaritasi va rassom molberti. Metro xaritasida bekatlar va yo'llar bor — qaysi bekat qaysi bilan bog'langani muhim. Molbertda esa faqat bo'yoq — rassom xohlagan narsani chizadi, lekin "bu bekat" degan tushuncha yo'q.

2. Ikki xil chizish usuli: SVG va canvas

2.1 SVG: har shakl — DOM elementi

SVG darsidan bilasiz: <svg> ichidagi <rect>, <line>, <text> — oddiy DOM elementlari. Demak React ularni boshqa teglar kabi chiza oladi. Ma'lumot massivda, ekran — map.

Buyurtma yo'lini shunday chizamiz. Tugunlar (nodes) va bog'lanishlar (edges) — ikki massiv. Bog'lanish faqat ikki tugunning id sini biladi, chiziq koordinatasini esa tugunlardan hisoblaymiz:

jsx
import { useState } from "react";

const W = 110;
const H = 36;
const startNodes = [
  { id: "qabul", label: "Qabul", x: 20, y: 10 },
  { id: "oshxona", label: "Oshxona", x: 20, y: 80 },
  { id: "kuryer", label: "Kuryer", x: 20, y: 150 },
];
const startEdges = [
  { id: "e1", source: "qabul", target: "oshxona" },
  { id: "e2", source: "oshxona", target: "kuryer" },
];

export default function App() {
  const [nodes, setNodes] = useState(startNodes);
  const [edges, setEdges] = useState(startEdges);
  const byId = Object.fromEntries(nodes.map((n) => [n.id, n]));

  function addPacking() {
    setNodes([
      ...nodes,
      { id: "qadoq", label: "Qadoqlash", x: 170, y: 150 },
    ]);
    setEdges([
      ...edges,
      { id: "e3", source: "oshxona", target: "qadoq" },
    ]);
  }

  return (
    <div>
      <button onClick={addPacking}>Qadoqlash qo'shish</button>
      <div>
        <svg width="300" height="200">
          {edges.map((e) => {
            const a = byId[e.source];
            const b = byId[e.target];
            return (
              <line
                key={e.id}
                x1={a.x + W / 2}
                y1={a.y + H}
                x2={b.x + W / 2}
                y2={b.y}
                stroke="#666"
              />
            );
          })}
          {nodes.map((n) => (
            <g key={n.id}>
              <rect x={n.x} y={n.y} width={W} height={H} rx="8"
                fill="#fff" stroke="#1d4ed8" />
              <text x={n.x + 12} y={n.y + 23}>{n.label}</text>{" "}
            </g>
          ))}
        </svg>
      </div>
      <p>
        Tugunlar: {nodes.length}, bog'lanishlar: {edges.length}
      </p>
    </div>
  );
}

Ekranda:

text
Qadoqlash qo'shish
Qabul Oshxona Kuryer Qadoqlash
Tugunlar: 4, bog'lanishlar: 3

«Ishga tushir» bosing va tugmani bosing — yangi quti va unga boradigan chiziq paydo bo'ladi. Biz chiziqni "chizmadik". Biz faqat massivga bitta obyekt qo'shdik, qolganini React qildi: UI = f(state).

{" "} nega bor? <text> lar orasiga bo'sh joy qo'yadi — Ekranda: da so'zlar yopishib qolmasin. Rasmga ta'sir qilmaydi.

E'tibor bering: tugun matnlari ekranda haqiqiy matn. Ularni belgilab nusxalash mumkin, brauzerning Ctrl+F qidiruvi topadi, Malika (ekran o'quvchi) o'qiydi. SVG'ning eng katta kuchi shu.

2.2 Canvas: piksellar to'plami

Canvas 2D asoslari darsida <canvas> ga getContext("2d") bilan chizganmiz. Canvas — bitta element va uning ichida piksellar. "Bu to'rtburchak — Oshxona" degan tushuncha brauzerda yo'q: chizildi va unutildi.

React'da canvas bilan ishlash — useRef va effekt: React faqat <canvas> ni chizadi, ichini esa biz effektda bo'yaymiz. Zal xaritasi uchun stollarni chizamiz va bosilgan stolni topamiz:

jsx
import { useEffect, useRef, useState } from "react";

const tables = [
  { id: 1, x: 20, y: 20 },
  { id: 2, x: 100, y: 20 },
  { id: 3, x: 180, y: 20 },
  { id: 4, x: 20, y: 100 },
];
const SIZE = 60;

export default function App() {
  const ref = useRef(null);
  const [picked, setPicked] = useState(null);

  useEffect(() => {
    const ctx = ref.current.getContext("2d");
    ctx.clearRect(0, 0, 260, 180);
    for (const t of tables) {
      ctx.fillStyle = t.id === picked ? "#1d4ed8" : "#d6e4ff";
      ctx.fillRect(t.x, t.y, SIZE, SIZE);
      ctx.fillStyle = t.id === picked ? "#fff" : "#111";
      ctx.fillText(`Stol ${t.id}`, t.x + 10, t.y + 34);
    }
  }, [picked]);

  // Qaysi stol bosildi? O'zimiz hisoblaymiz (hit testing)
  function pick(e) {
    const r = e.currentTarget.getBoundingClientRect();
    const x = e.clientX - r.left;
    const y = e.clientY - r.top;
    const t = tables.find(
      (t) => x >= t.x && x <= t.x + SIZE && y >= t.y &&
        y <= t.y + SIZE,
    );
    setPicked(t ? t.id : null);
  }

  return (
    <div>
      <canvas ref={ref} width="260" height="180" onClick={pick} />
      <p>Tanlangan: {picked ? `Stol ${picked}` : "yo'q"}</p>
    </div>
  );
}

«Ishga tushir» dan keyin stollardan birini bosing — u ko'k bo'ladi va pastda raqami chiqadi. Ikki narsaga qarang:

  1. Bosilgan stolni brauzer bilmaydi. onClick butun canvas'ga keladi. Qaysi stol ekanini biz koordinata bilan o'zimiz hisobladik. Bu hit testing (bosilgan nuqtani tekshirish) deyiladi.
  2. Har o'zgarishda hammasini qayta bo'yaymiz. clearRect — tozalash, keyin hamma stollar qaytadan. Canvas'da "bitta stolning rangini o'zgartirish" yo'q.

Bu blok saytda ishlaydi, lekin kurs tekshiruvchisida emas: u brauzersiz muhitda ishlaydi va unda canvas chizish imkoniyati yo'q. Shuning uchun natijani o'zingiz ko'rasiz.

2.3 Qachon SVG, qachon canvas

SVG (yoki HTML) Canvas
Shakl DOM elementi, React chizadi piksel, effektda bo'yaladi
Hodisa har elementda onClick bitta; hit testing o'zingizda
Matn va a11y haqiqiy matn, ARIA rasm; muqobil kerak
Ko'p element minglab elementda DOM og'irlashadi ko'p shakl va tez kadrlar uchun

Qoida oddiy. Elementlar kam (o'nlab, yuzlab), ularda matn bor va har biri bilan alohida ishlash kerak — SVG. Shakl juda ko'p, har kadrda harakat bor yoki rasm bilan ishlaysiz — canvas.

Kutubxonalar ham shu ikki yo'lning ustiga quriladi. Quyidagi sxema tanlovni bir joyga yig'adi:

flowchart TD
  A{"Tugun va bog'lanish bormi?"} -->|ha| B["React Flow"]
  A -->|yo'q| C{"Shakl ko'p yoki tez harakat?"}
  C -->|ha| D["canvas: Konva"]
  C -->|yo'q| E{"Erkin doska kerakmi?"}
  E -->|ha| F["Excalidraw"]
  E -->|yo'q| G["o'z SVG'ingiz"]

Tekshirib ko'ring: «Bahor» saytida filiallar xaritasi kerak: 5 ta filial, har biriga bosilsa manzil chiqadi. SVG yoki canvas?

Javob

SVG. Elementlar kam (5 ta), har biri alohida bosiladi va nomi matn bo'lib o'qilishi kerak. SVG'da har filial — <a> yoki tugma bo'la oladi, Malika ularni ro'yxat kabi eshitadi. Canvas'da esa hit testing'ni o'zingiz yozardingiz va ekran o'quvchi uchun alohida muqobil yasardingiz — foydasiz mehnat.

3. Tugun va bog'lanish: modelni o'zimiz qursak

3.1 Tugunni klaviatura bilan surish

Diagramma muharririning birinchi talabi — tugunni surish. Sichqoncha bilan sudrash ko'p kod talab qiladi (dnd-kit darsida ko'rdik). Lekin Bobur (faqat klaviatura) uchun eng muhimi — strelkalar bilan surish.

React Flow ham aynan shunday qiladi: tugun — HTML element (bizda <button>), chiziqlar — SVG. Tugunning joyi transform bilan beriladi:

jsx
import { useState } from "react";

const STEP = 10;

export default function App() {
  const [pos, setPos] = useState({ x: 20, y: 80 });

  function moveByKey(e) {
    const d = {
      ArrowRight: [STEP, 0],
      ArrowLeft: [-STEP, 0],
      ArrowDown: [0, STEP],
      ArrowUp: [0, -STEP],
    }[e.key];
    if (!d) return;
    e.preventDefault(); // sahifa aylanib ketmasin
    setPos((p) => ({ x: p.x + d[0], y: p.y + d[1] }));
  }

  return (
    <div style={{ position: "relative", height: 160 }}>
      <button
        onKeyDown={moveByKey}
        style={{
          position: "absolute",
          transform: `translate(${pos.x}px, ${pos.y}px)`,
        }}
      >
        Oshxona
      </button>
      <p
        aria-live="polite"
        style={{ position: "absolute", bottom: 0 }}
      >
        Oshxona: x {pos.x}, y {pos.y}
      </p>
    </div>
  );
}

Ekranda:

text
Oshxona
Oshxona: x 40, y 90

Ikki marta → va bir marta ↓: tugun 20 px o'ngga va 10 px pastga ketdi. Pastdagi aria-live qatori yangi joyni e'lon qiladi — Malika tugun qayerga borganini eshitadi (React'da accessibility darsidagi jonli hudud).

e.preventDefault() nega kerak? Strelkalar sukut bo'yicha sahifani aylantiradi. Tugunni surayotganda sahifa ham siljisa, Bobur adashib qoladi.

3.2 Yana nimalar kerak bo'ladi

Sardor modelni qurdi. Endi haqiqiy muharrir uchun ro'yxatni yozib chiqamiz:

  • sichqoncha va barmoq bilan sudrash (pointer hodisalari, chegaralar);
  • kattalashtirish va surish (zoom, pan) — butun sahnaning transform i;
  • tugundan tugunga yangi chiziqni sichqoncha bilan tortish;
  • bir nechta tugunni tanlash, o'chirish, nusxalash;
  • egri chiziqlar va strelkalar, tugun o'lchamini o'lchab, chiziqni chetiga ulash;
  • kichik xarita (minimap) va 500 ta tugunda ham tez ishlash.

Har biri bir necha kunlik ish. Shu yerda kutubxona o'z narxini oqlaydi.

Tekshirib ko'ring: Tugunni left/top bilan emas, transform: translate(...) bilan joylashtirdik. Nega?

Javob

left va top o'zgarsa, brauzer sahifaning joylashuvini (layout) qayta hisoblaydi. transform esa faqat tayyor qatlamni siljitadi — layout'ga tegmaydi (Rendering pipeline darsida). Tugun sudralganda bu har kadrda takrorlanadi, farq sezilarli bo'ladi. Bu g'oyani UI animatsiya tamoyillari darsida chuqur o'lchaymiz.

4. React Flow (xyflow)

4.1 O'rnatish va birinchi diagramma

React Flow — node-diagramma uchun React kutubxonasi. Paket nomi @xyflow/react (12-versiya). Eski loyihalarda reactflow paketini ko'rsangiz — bu 11-versiya, importlar boshqacha. Jamoa nomi xyflow: ular Svelte uchun ham xuddi shunday kutubxona chiqaradi.

React Flow natija oynasidagi kutubxonalar ro'yxatida yo'q. Shuning uchun kod — o'z loyihangiz uchun:

bash
npm i -E @xyflow/react@12.12.0
jsx
import { useCallback } from "react";
import {
  ReactFlow,
  Background,
  Controls,
  MiniMap,
  addEdge,
  useEdgesState,
  useNodesState,
} from "@xyflow/react";
import "@xyflow/react/dist/style.css";

const startNodes = [
  { id: "qabul", position: { x: 0, y: 0 },
    data: { label: "Qabul" } },
  { id: "oshxona", position: { x: 0, y: 100 },
    data: { label: "Oshxona" } },
  { id: "kuryer", position: { x: 0, y: 200 },
    data: { label: "Kuryer" } },
];
const startEdges = [
  { id: "e1", source: "qabul", target: "oshxona" },
  { id: "e2", source: "oshxona", target: "kuryer" },
];

export function OrderFlow() {
  const [nodes, , onNodesChange] = useNodesState(startNodes);
  const [edges, setEdges, onEdgesChange] =
    useEdgesState(startEdges);
  const onConnect = useCallback(
    (params) => setEdges((eds) => addEdge(params, eds)),
    [setEdges],
  );
  return (
    <div style={{ height: 400 }}>
      <ReactFlow
        nodes={nodes}
        edges={edges}
        onNodesChange={onNodesChange}
        onEdgesChange={onEdgesChange}
        onConnect={onConnect}
        fitView
      >
        <Background />
        <Controls />
        <MiniMap />
      </ReactFlow>
    </div>
  );
}

Tanish narsalarni toping:

  • nodes va edges — biz «Tugun va bog'lanish» bo'limida yozgan model, deyarli o'zi. Faqat joy position: { x, y } ichida, matn esa data da. data — sizning ma'lumotingiz: istalgan maydon qo'shasiz.
  • useNodesState — useState ga o'xshaydi, lekin uchinchi qiymat ham qaytaradi: onNodesChange. Foydalanuvchi tugunni sudrasa, tanlasa yoki o'chirsa, React Flow "o'zgarishlar ro'yxati" yuboradi. Bu funksiya ularni state'ga qo'llaydi. Controlled komponent — 18-qismdagi uchlik naqshi.
  • onConnect — foydalanuvchi bir tugunning chetidan ikkinchisiga chiziq tortdi. addEdge yangi bog'lanishni massivga qo'shadi (takrorini qo'shmaydi).
  • fitView — birinchi chizishda hamma tugun ekranga sig'adigan kattalik.
  • Background, Controls, MiniMap — nuqtali fon, +/− tugmalari va kichik xarita. Ixtiyoriy.

style.css ni ulash shart: tugunlar, chiziqlar va tutqichlarning asosiy ko'rinishi shu faylda.

4.2 Custom node: tugun ichida o'z komponentingiz

Standart tugun faqat matn ko'rsatadi. Oshpaz har bosqichda vaqtni ham ko'rmoqchi: «Oshxona — 15 daq». Buning uchun custom node (o'z tugun komponenti) yozamiz:

jsx
import { Handle, Position } from "@xyflow/react";

function StepNode({ data }) {
  return (
    <div className="step-node">
      <Handle type="target" position={Position.Top} />
      <strong>{data.label}</strong>
      <span>{data.minutes} daq</span>
      <Handle type="source" position={Position.Bottom} />
    </div>
  );
}

// Komponentdan TASHQARIDA — har renderda yangi obyekt bo'lmasin
const nodeTypes = { step: StepNode };

// tugunda: { id: "oshxona", type: "step",
//   position: { x: 0, y: 100 },
//   data: { label: "Oshxona", minutes: 15 } }
// <ReactFlow nodeTypes={nodeTypes} ... />
  • Handle — tutqich: tugun chetidagi kichik nuqta, chiziq shu yerdan chiqadi yoki shu yerga keladi. type="source" — chiqish, type="target" — kirish. Position — qaysi chetda.
  • nodeTypes — tur nomi → komponent. Tugundagi type: "step" shu jadvaldan komponentni tanlaydi.

nodeTypes ni komponent ichida yozsangiz, har chizishda yangi obyekt yaratiladi. React Flow buni "turlar o'zgardi" deb tushunadi va tugunlarni qayta yaratadi. React Flow hujjati buni eng ko'p uchraydigan xatolar ro'yxatida beradi. Davosi — obyektni komponentdan tashqariga chiqarish yoki useMemo.

4.3 Tuzoq: ota-elementning balandligi

React Flow sahnasi ota-elementning o'lchamini oladi. Ota-elementda balandlik bo'lmasa, diagramma ko'rinmaydi — xato ham chiqmaydi. Biz buni haqiqiy Chrome'da sinadik (height siz <div>, development build, @xyflow/react 12.12.0; natija — natijalar/tajriba/19-diagramma-csp.txt). Sahna balandligi 0 px bo'ldi, konsolda esa ogohlantirish:

text
[React Flow]: The parent container needs a width and a height to render the graph. Help: https://reactflow.dev/error#004

Tarjimasi: "Ota konteynerga graf chizish uchun eni va balandligi kerak". Davosi — ota-elementga aniq balandlik: height: 400, height: 100vh yoki flex/grid ichida flex: 1. Production build'da bu ogohlantirish yo'q — ekran shunchaki bo'sh qoladi. Shuning uchun dev rejimdagi konsolni o'qib yuring.

4.4 A11y: Bobur va Malika uchun

React Flow klaviaturani o'zi qo'llab-quvvatlaydi (hujjatdagi «Accessibility» bo'limi):

  • Tab — tugun va chiziqlar bo'ylab yuradi (ular tabIndex={0} oladi).
  • Enter yoki Space — fokusdagi tugunni tanlaydi, Escape — tanlovni bekor qiladi.
  • Strelkalar — tanlangan tugunni suradi, Shift bilan tezroq. «Tugunni klaviatura bilan surish» bo'limida o'zimiz yozgan narsa — tayyor.
  • Delete — tanlanganini o'chiradi.

Ekran o'quvchi uchun izohlar (masalan "Press enter or space to select a node") — inglizcha. Ularni ariaLabelConfig props'i bilan o'zbekchaga o'giring:

jsx
const ariaLabels = {
  "node.a11yDescription.default":
    "Tanlash uchun Enter yoki Space. O'chirish — Delete.",
  "controls.zoomIn.ariaLabel": "Kattalashtirish",
  "controls.zoomOut.ariaLabel": "Kichraytirish",
};

<ReactFlow ariaLabelConfig={ariaLabels} ... />

Lekin diagrammaning o'zi Malika uchun baribir qiyin: "Oshxonadan Kuryerga chiziq bor" ni tasavvur qilish uchun butun sxemani eshitish kerak. Yaxshi amaliyot — diagramma yonida yoki uning o'rniga matnli ko'rinish: bosqichlar ro'yxati va "keyingi bosqich" ustuni. Ma'lumot bir xil (nodes, edges), faqat boshqa ko'rinish.

4.5 Narxi

Kanon tajribasida (tajriba/kitlar/src/k/flow.tsx: ikki tugun va bitta chiziq, vazifalar CSP'si ostida) React Flow:

text
flow            JS  125152 (+  56.1 KB) CSS   2530 | CSP 0 | xato: yo'q

+56.1 KB JS va 2.5 KB CSS (gzip). CSP buzilishi yo'q: React Flow joylashuvni style xususiyati orqali beradi (JavaScript'dan bitta xususiyat — ruxsat), ko'rinish esa alohida .css faylda. Qattiq CSP bilan muammosiz ishlaydi.

Tekshirib ko'ring: Oshpaz tugunni sudradi, lekin sahifani yangilaganda tugun eski joyiga qaytdi. Nima yetishmayapti?

Javob

Tugunlar faqat React state'ida. Sudrash onNodesChange orqali state'ni yangiladi, lekin hech qayerga saqlanmadi. Yangi position ni saqlash kerak: localStorage, server yoki zustand persist. Odatda o'zgarish to'xtagach saqlanadi (debounce) yoki onNodeDragStop hodisasida — sudrash tugaganda bir marta.

5. Konva: canvas sahnasi React'da

5.1 Stage, Layer va shakllar

Konva — canvas uchun JavaScript kutubxonasi. U canvas'ga "obyekt" tushunchasini qaytaradi: har shakl — obyekt, o'z hodisalari bilan. Hit testing'ni ham o'zi qiladi. react-konva — uning React qobig'i: shakllarni JSX bilan yozasiz.

bash
npm i -E konva@10.7.1 react-konva@19.3.0

react-konva ning asosiy versiyasi React'nikiga mos keladi (19.x — React 19 uchun). Zal xaritasi:

jsx
import { useState } from "react";
import { Layer, Rect, Stage, Text } from "react-konva";

export function HallMap() {
  const [tables, setTables] = useState([
    { id: 1, x: 20, y: 20 },
    { id: 2, x: 120, y: 20 },
  ]);

  function moveTable(id, e) {
    const { x, y } = { x: e.target.x(), y: e.target.y() };
    setTables((all) =>
      all.map((t) => (t.id === id ? { ...t, x, y } : t)),
    );
  }

  return (
    <Stage width={360} height={240}>
      <Layer>
        {tables.map((t) => (
          <Rect
            key={t.id}
            x={t.x}
            y={t.y}
            width={60}
            height={60}
            fill="#d6e4ff"
            draggable
            onDragEnd={(e) => moveTable(t.id, e)}
          />
        ))}
        <Text x={10} y={210} text="Zal xaritasi" />
      </Layer>
    </Stage>
  );
}
  • Stage — sahna: ichida haqiqiy <canvas> lar bo'ladi.
  • Layer — qatlam: har biri alohida canvas. Tez o'zgaradigan narsalarni alohida qatlamga qo'yish mumkin — faqat o'sha qatlam qayta bo'yaladi.
  • Rect, Circle, Text, Image, Line — shakllar. draggable — sudrash tayyor.
  • onDragEnd — sudrash tugaganda: e.target.x() — shaklning yangi joyi. Biz uni state'ga yozamiz — saqlash mumkin bo'ladi.

Sudrash paytida Konva shaklni o'zi siljitadi, React'ni chaqirmaydi. State faqat oxirida bir marta yangilanadi. Bu tezlik uchun muhim: har kadrda React render qilsa, 60 kadr/s ushlab turish qiyinlashadi.

5.2 Canvas va a11y

Konva canvas'da chizadi — demak «Canvas: piksellar to'plami» bo'limidagi muammo shu yerda ham: Malika uchun zal xaritasi — bitta rasm. Konva buni hal qilmaydi. Sizning vazifangiz:

  • canvas'ga role="img" va mazmunli aria-label ("Zal xaritasi, 12 ta stol");
  • canvas yonida HTML ro'yxat yoki jadval: stollar, holati (bo'sh/band), "Tanlash" tugmasi bilan;
  • klaviatura bilan boshqarish — HTML tugmalar orqali (Oshxona tugunidagidek), canvas faqat ko'rsatadi.

Qoida: canvas — ko'rinish, haqiqat — HTML'da. Bron qilish kabi muhim amal faqat canvas'da bo'lmasin.

5.3 CSP saboq: Konva ochilmadi

Kanon tajribasida Konva'ni (tajriba/kitlar/src/k/konva.tsx: bitta Stage, Layer va sudraladigan doira) vazifalar CSP'si ostida ochdik. Natija:

text
konva           JS  165458 (+  95.4 KB) CSS      0 | CSP require-trusted-types-for×1 | xato: This document requires 'TrustedHTML' assignment. The action has been blocked. ; Failed to set the 'innerHTML' property on 'Element': This document requires 'TrustedHTML' 

(Qator o'lchov skriptida 90 belgigacha qisqartirilgan.)

Xato matnining tarjimasi: "Bu hujjat TrustedHTML berishni talab qiladi. Amal to'sildi" va "innerHTML xususiyatini o'rnatib bo'lmadi". Biz uni yana bir bor, development build bilan sinadik (konva 10.7.1, react-konva 19.3.0; natijalar/tajriba/19-diagramma-csp.txt). CSP siz sahifada <canvas> — 1 ta. CSP bilan — 0: Konva sahna qurayotganda innerHTML ga oddiy satr yozdi, brauzer buni to'sdi. React esa xatoni ushlamadi va butun daraxtni o'chirdi — ekran bo'sh. Konsolda React ham xabar berdi: An error occurred in the <StageWrap> component.

Nima bo'ldi? Frontend xavfsizligi darsida vazifalar CSP'siga Trusted Types qo'shganmiz: require-trusted-types-for 'script'. Bu "innerHTML ga faqat siyosatdan o'tgan qiymat yozilsin" degani. Oddiy satr — taqiqlangan. XSS hujumlarining katta qismi aynan innerHTML orqali bo'lgani uchun bu qatlam qo'yilgan.

Yo'llar uchta:

  1. Konva'ni tanlamaslik — kerakli chizmani o'zimiz Canvas API bilan chizamiz (stollar misolidagidek) yoki SVG bilan.
  2. Konva uchun alohida sahifa (masalan admin uchun) — o'z, yumshoqroq CSP'si bilan. Asosiy ilova himoyasi saqlanadi.
  3. default nomli Trusted Types siyosati — har innerHTML ni "tozalab" o'tkazadi. Bu butun ilovaga kalit: har qanday kutubxonaning innerHTML i o'tib ketadi. Faqat sabablarini yozib, xavfsizlik tekshiruvidan keyin.

Bu UI kutubxonalar xaritasi dagi savolning yana bir misoli: "kutubxona sizning xavfsizlik siyosatingizga mosmi?" Bu savol o'lchov bilan javob topadi, reklama bilan emas. Mantine va Ant Design ham xuddi shu sabab bilan ochilmagan edi.

6. Excalidraw: tayyor doska

Excalidraw — qo'lda chizilgandek ko'rinadigan doska (whiteboard): shakl, strelka, matn, erkin chiziq, eksport. U React komponenti sifatida ham tarqatiladi (@excalidraw/excalidraw). Jamoa uchun "sxema chizib tushuntirish" sahifasi kerak bo'lsa — eng tez yo'l.

Biz uni kanonda o'lchamadik. Bilishingiz kerak bo'lgan ikki narsa:

  • U katta: o'z shrift va chizish mexanizmi bilan keladi. Uni faqat kerak bo'lganda yuklang — lazy bilan alohida chunk.
  • Unga ham ota-elementda balandlik kerak (React Flow kabi).
jsx
import { lazy, Suspense } from "react";

const Excalidraw = lazy(() =>
  import("@excalidraw/excalidraw").then((m) => ({
    default: m.Excalidraw,
  })),
);

export function Board() {
  return (
    <div style={{ height: 500 }}>
      <Suspense fallback={<p>Doska yuklanmoqda…</p>}>
        <Excalidraw />
      </Suspense>
    </div>
  );
}

then((m) => ({ default: m.Excalidraw })) nega? lazy modulning default eksportini kutadi, Excalidraw esa nomli eksport. Biz uni default ga "o'raymiz". O'rnatish tafsilotlarini (CSS fayli, shriftlar joyi) versiyangiz hujjatidan tekshiring. Qattiq CSP bilan ishlashini ham alohida o'lchang.

7. Kanon tajribasi: vazifalar da diagramma yo'q

Bu dars uchun vazifalar ga qadam yo'q — ilovada sxema yoki chizma kerak bo'ladigan ekran yo'q. Biz faqat o'lchadik (natijalar/tajriba/kutubxonalar.txt, Chrome 155, React 19.3.0, Vite 8.3.3; har kutubxona alohida build, "asos" — faqat React):

Kutubxona narxi: JS gzip (faqat React ustiga)
  • dnd-kitCSP 014,5 KB
  • React FlowCSP 0, +2.5 KB CSS56,1 KB
  • KonvaTrusted Types: ochilmadi95,4 KB

Manba: IlmHamroh kurs tajribasi — kutubxonalar o'lchovi (Chrome 155, 2026-10-14)

Grafikda nimaga qarang: React Flow vazifalar ning butun sudrash qismidan (dnd-kit) to'rt barobar katta. Konva esa olti barobar va u qattiq CSP bilan umuman ishlamadi.

Xulosa (kanon qarori): vazifalar da diagramma paydo bo'lsa — React Flow mos keladi (CSP 0), faqat kerakli sahifada lazy yuklanadi. Canvas kerak bo'lsa — o'z Canvas API kodimiz yoki SVG. Konva — Trusted Types tufayli yo'q.

8. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
React Flow ekrani bo'sh, xato yo'q ota-elementda balandlik yo'q height yoki flex: 1
The parent container needs a width and a height… o'sha sabab, dev rejim o'sha davo
tugunlar har renderda "sakraydi" yoki qayta yaratiladi nodeTypes komponent ichida komponentdan tashqariga
tugun sudraladi, lekin joyida qolmaydi onNodesChange ulanmagan useNodesState dan uchinchi qiymat
canvas bosilganda noto'g'ri element tanlanadi koordinata canvas'ga nisbatan emas getBoundingClientRect() ni ayiring
Failed to set the 'innerHTML' property… Trusted Types (Konva) boshqa yo'l yoki alohida sahifa
ekran o'quvchi xaritada hech narsa eshitmaydi canvas — rasm aria-label + HTML ro'yxat

9. Mashqlar

Mashqlar kurs/mashqlar/20/19-diagramma/ da. Har yechimni «Ishga tushir» bilan sinang.

1-mashq (oson): qaysi vosita?

Vaziyatga mos vositani yozing: React Flow, canvas, SVG yoki Excalidraw.

  • Chatbot ssenariysi muharriri: bloklar va ularni bog'lovchi strelkalar, foydalanuvchi o'zi ulaydi →
  • 3 ta ustunli oddiy "Bugungi buyurtmalar" sxemasi, har ustunda matn →
  • 5 000 ta nuqtali jonli harita, har soniyada yangilanadi →
  • Jamoa yig'ilishida qo'lda chizib tushuntirish doskasi →
Yechim
  • Chatbot muharriri — tugun va bog'lanish, ulash foydalanuvchida: React Flow.
  • Oddiy sxema — elementlar kam, matnli: SVG (kutubxonasiz).
  • 5 000 nuqta, tez yangilanish — DOM og'ir bo'ladi: canvas (o'zingiz yoki kutubxona).
  • Erkin chizish doskasi — Excalidraw: shakl, strelka, matn tayyor.

2-mashq (o'rta): bog'lanishni yo'q qilish

«Bahor» sxemasida «Qabul → Oshxona → Kuryer» bor. «Kuryerni olib tashlash» tugmasi qo'shing: u tugunni ham, unga bog'langan hamma chiziqlarni ham o'chirsin. Aks holda chiziq yo'q tugunga ishora qiladi va kod yiqiladi.

Ishora: filter bilan — tugunlarni id bo'yicha, chiziqlarni esa source va target bo'yicha. Natijada ekranda:

text
Kuryerni olib tashlash
Qabul Oshxona
Tugunlar: 2, bog'lanishlar: 1
Yechim
jsx
import { useState } from "react";

const W = 110;
const H = 36;

export default function App() {
  const [nodes, setNodes] = useState([
    { id: "qabul", label: "Qabul", x: 20, y: 10 },
    { id: "oshxona", label: "Oshxona", x: 20, y: 80 },
    { id: "kuryer", label: "Kuryer", x: 20, y: 150 },
  ]);
  const [edges, setEdges] = useState([
    { id: "e1", source: "qabul", target: "oshxona" },
    { id: "e2", source: "oshxona", target: "kuryer" },
  ]);
  const byId = Object.fromEntries(nodes.map((n) => [n.id, n]));

  function removeNode(id) {
    setNodes(nodes.filter((n) => n.id !== id));
    setEdges(
      edges.filter((e) => e.source !== id && e.target !== id),
    );
  }

  return (
    <div>
      <button onClick={() => removeNode("kuryer")}>
        Kuryerni olib tashlash
      </button>
      <div>
        <svg width="300" height="200">
          {edges.map((e) => {
            const a = byId[e.source];
            const b = byId[e.target];
            return (
              <line key={e.id} x1={a.x + W / 2} y1={a.y + H}
                x2={b.x + W / 2} y2={b.y} stroke="#666" />
            );
          })}
          {nodes.map((n) => (
            <g key={n.id}>
              <rect x={n.x} y={n.y} width={W} height={H} rx="8"
                fill="#fff" stroke="#1d4ed8" />
              <text x={n.x + 12} y={n.y + 23}>{n.label}</text>{" "}
            </g>
          ))}
        </svg>
      </div>
      <p>
        Tugunlar: {nodes.length}, bog'lanishlar: {edges.length}
      </p>
    </div>
  );
}

Ekranda:

text
Kuryerni olib tashlash
Qabul Oshxona
Tugunlar: 2, bog'lanishlar: 1

Chiziqlarni filtrlamasak, e2 qolardi. byId["kuryer"] — undefined, b.x esa TypeError: Cannot read properties of undefined (reading 'x') bilan yiqilardi. React Flow'da bu ishni onNodesChange o'zi qiladi: tugun o'chirilsa, unga ulangan chiziqlar ham onEdgesChange ga "o'chirildi" bo'lib keladi.

3-mashq (qiyin): sahna chegarasi

«Tugunni klaviatura bilan surish» bo'limidagi tugunni yaxshilang:

  1. Tugun sahnadan chiqib ketmasin: x 0…200, y 0…120 oralig'ida qolsin.
  2. Chegaraga yetganda e'lon: «Oshxona: chegara».
  3. Shift bilan qadam 10 emas, 50 bo'lsin (React Flow'dagidek).

Tekshirish uchun tugunni chegara yonidan boshlang: x: 180, y: 0. Tugunni bosing, keyin → → va ↑. Ekranda Oshxona: x 200, y 0 — chegara chiqishi kerak.

Ishora: chegaralash — Math.min(max, Math.max(min, qiymat)). Shift — hodisadagi e.shiftKey.

Yechim
jsx
import { useState } from "react";

const MAX = { x: 200, y: 120 };
const clamp = (v, max) => Math.min(max, Math.max(0, v));

export default function App() {
  const [pos, setPos] = useState({ x: 180, y: 0 });
  const [edge, setEdge] = useState(false);

  function moveByKey(e) {
    const d = {
      ArrowRight: [1, 0],
      ArrowLeft: [-1, 0],
      ArrowDown: [0, 1],
      ArrowUp: [0, -1],
    }[e.key];
    if (!d) return;
    e.preventDefault();
    const step = e.shiftKey ? 50 : 10;
    const x = pos.x + d[0] * step;
    const y = pos.y + d[1] * step;
    const next = { x: clamp(x, MAX.x), y: clamp(y, MAX.y) };
    setEdge(next.x !== x || next.y !== y);
    setPos(next);
  }

  return (
    <div style={{ position: "relative", height: 180 }}>
      <button
        onKeyDown={moveByKey}
        style={{
          position: "absolute",
          transform: `translate(${pos.x}px, ${pos.y}px)`,
        }}
      >
        Oshxona
      </button>
      <p
        aria-live="polite"
        style={{ position: "absolute", bottom: 0 }}
      >
        Oshxona: x {pos.x}, y {pos.y}
        {edge && " — chegara"}
      </p>
    </div>
  );
}

Ekranda:

text
Oshxona
Oshxona: x 200, y 0 — chegara

Tekshiruv uchun tugun chegara yoniga qo'yilgan (x: 180, y: 0). Birinchi → — 190. Ikkinchisi — 200, chegaraga tegdi, lekin chiqmadi. ↑ — y −10 bo'lardi, 0 da to'xtadi va «chegara» yozuvi chiqdi. Shift'ni natija oynasida o'zingiz sinang: Shift+→ tugunni 50 px suradi. next.x !== x — "chegaralangan qiymat istagan qiymatdan farq qiladimi?" — ya'ni chegaraga urildikmi.

4-mashq: o'z loyihangiz uchun

Portfolio loyihangizda jarayon yoki tuzilma ko'rsatadigan joy bormi (masalan "loyiha bosqichlari", "jamoa tuzilmasi")? «SVG: har shakl — DOM elementi» bo'limidagi model bilan chizing: nodes va edges massivi JSON fayldan o'qilsin. Diagramma ostiga xuddi shu ma'lumotdan matnli ro'yxat qo'shing. Malika uchun.

Yechim

Tekshiruv ro'yxati:

  • Diagramma ham, ro'yxat ham bitta nodes/edges dan chiziladi — biri o'zgarsa, ikkinchisi ham.
  • Ro'yxatda har bosqich va "keyingi: …" yozuvi bor (edges.filter((e) => e.source === n.id)).
  • Tugun matnlari SVG'da haqiqiy <text>, Ctrl+F topadi.
  • Kutubxona qo'shilmadi — kerak bo'lsa, React Flow faqat shu sahifada lazy bilan yuklanadi.

10. Real ishda

  • Workflow va avtomatlashtirish servislari (chatbot muharrirlari, CRM'dagi "voronka" sozlagichlari, ma'lumot quvurlari) — node-diagramma interfeyslari. React ekotizimida bunday muharrirlarning ko'pi React Flow ustiga quriladi.
  • Dizayn va rasm vositalari, o'yinlar, xaritalar — canvas (yoki WebGL, 3D va WebGL darsida). Konva, Fabric.js, PixiJS — shu oiladan.
  • Ichki vositalar — "tizim arxitekturasi" sahifasi, tashkiliy tuzilma: ko'pincha oddiy SVG yetadi.
  • Intervyu savollari: "SVG va canvas farqi nima, qachon qaysi biri?", "canvas'da bosilgan elementni qanday topasiz?", "canvas'ni ekran o'quvchi uchun qanday ochiq qilasiz?", "kutubxona CSP'ga mosligini qanday tekshirasiz?".

Xulosa

  • SVG — har shakl DOM elementi: React chizadi, matn va a11y bor; ko'p elementda og'irlashadi.
  • Canvas — piksellar: tez, lekin hodisa, hit testing va a11y — sizda.
  • React Flow: nodes + edges, useNodesState/useEdgesState, onConnect + addEdge, nodeTypes (komponentdan tashqarida), Handle. Ota-elementga balandlik shart. +56.1 KB, CSP 0.
  • Konva/react-konva: Stage → Layer → shakllar, draggable, onDragEnd. +95.4 KB va Trusted Types ostida ochilmadi.
  • Canvas — ko'rinish, haqiqat HTML'da: aria-label, ro'yxat, klaviatura uchun tugmalar.

Keyingi dars: Kichik, lekin har joyda kerak bo'ladigan kutubxonalar — autosize, OTP, drawer, debounce, nanoid: har birini o'lchab, "o'zim yozamanmi yoki o'rnatamanmi?" savoliga javob beramiz.

Manbalar

  • React Flow: Learn, Accessibility, Common errors — reactflow.dev (@xyflow/react 12.12.0)
  • Konva: React (react-konva) — konvajs.org/docs/react (konva 10.7.1, react-konva 19.3.0)
  • Excalidraw: Integration — docs.excalidraw.com
  • MDN: Canvas API, SVG — developer.mozilla.org
  • MDN: Trusted Types API, require-trusted-types-for — developer.mozilla.org
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Node-diagramma va canvas React'da: React Flow, Konva va qachon SVG, qachon canvas — IlmHamroh