Туторіал · Генеративне мистецтво · Canvas 2D · JavaScript
📅 Липень 2026 ⏱ ≈ 20 хв 🎯 Початківець

Generative Art у 50 рядків Canvas 2D

Не потрібен ні WebGL, ні шейдер, ні фреймворк, щоб створити генеративне мистецтво. Цей туторіал будує повноцінний, самодостатній скетч — рандомізовану, засіяну (seeded) сітку повернутих дуг, що складаються у неперервні потокові лінії — менш ніж за 50 рядків чистого Canvas 2D, що запускається відкриттям одного HTML-файлу.

1. Скелет Canvas

Кожен скетч Canvas 2D починається з тих самих чотирьох рядків: беремо елемент <canvas>, отримуємо його 2D контекст рендерингу і задаємо розмір. Зафіксуємо canvas у квадратному розмірі, щоб математика сітки лишалась простою:

const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
canvas.width = canvas.height = 600;
ctx.fillStyle = '#0a0e1a';
ctx.fillRect(0, 0, 600, 600); // темне тло
Без білду: Усе в цьому туторіалі запускається простим вставленням фінального скрипту в тег <script> у .html-файлі поряд з елементом <canvas id="c">. Без бандлера, без npm install.

2. Генератор випадкових чисел із seed

Math.random() не можна засіяти, тож кожне перезавантаження видає інший, невідтворюваний результат — проблема, якщо ви колись захочете зберегти конкретну композицію або зробити серію пронумерованих варіантів. Крихітний PRNG mulberry32 (алгоритм у публічному домені від Томмі Еттінгера) виправляє це у чотирьох рядках:

function mulberry32(seed) {
  return function() {
    seed |= 0; seed = seed + 0x6D2B79F5 | 0;
    let t = Math.imul(seed ^ seed >>> 15, 1 | seed);
    t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
    return ((t ^ t >>> 14) >>> 0) / 4294967296;
  };
}
const SEED = 1337;               // змінюйте це → новий, але відтворюваний результат
const rand = mulberry32(SEED);

Далі використовуйте rand() усюди замість Math.random(). Кожне значення, яке він повертає, повністю визначене через SEED, тож той самий seed завжди дає точно ту саму роботу — критично для колекцій генеративного мистецтва, де конкретний вивід має бути відтворюваним на вимогу.

3. Сітка

Розкладіть сітку N × N клітин на полотні. Замість обчислення абсолютних координат для кожної форми ми переміщуємо (translate) контекст малювання у центр кожної клітини перед малюванням — це робить код малювання незалежним від початку координат і значно спрощує міркування:

const N = 12;                       // сітка 12×12
const cell = 600 / N;

for (let row = 0; row < N; row++) {
  for (let col = 0; col < N; col++) {
    ctx.save();
    ctx.translate((col + 0.5) * cell, (row + 0.5) * cell);
    drawMotif(cell);   // визначено у наступному розділі
    ctx.restore();
  }
}

4. Один мотив: повернута дуга

Увесь скетч тримається на одному повторюваному мотиві: чвертькругова дуга від одного краю клітини до іншого, повернута на випадкове кратне 90°. Оскільки дуга завжди торкається середин двох сусідніх країв, сусідні клітини з'єднуються у неперервні потокові криві — класичний ефект тайлів Трюше, відкритий Себастьяном Трюше у 1704 році й перевідкритий кожним поколінням генеративних художників відтоді:

function drawMotif(cell) {
  const r = cell / 2;
  const turn = Math.floor(rand() * 4) * (Math.PI / 2); // 0, 90, 180 чи 270°
  ctx.rotate(turn);

  ctx.strokeStyle = `hsla(${200 + rand() * 60}, 70%, 65%, 0.9)`;
  ctx.lineWidth = 2;
  ctx.beginPath();
  ctx.arc(-r, -r, r, 0, Math.PI / 2);   // чверть кола у верхньому лівому куті
  ctx.stroke();
}
Чому це виглядає «продумано»: Кожна клітина використовує точно той самий мотив — змінюється лише поворот і відтінок. Саме це обмеження змушує фінальне зображення виглядати єдиною композицією, а не набором незв'язаних випадкових форм на сітці.

5. Чотири правила

Ці чотири обмеження відрізняють навмисне генеративне мистецтво від «випадкового шуму із зайвими кроками»:

  1. Обмежте палітру. Обирайте hue з вузького діапазону (тут 200–260, сині у фіолетові) замість повного кола кольорів — обмежена палітра виглядає навмисною.
  2. Обмежте мотив. Одна форма, жменька трансформацій (поворот, масштаб, віддзеркалення) — а не інша випадкова форма щоразу.
  3. Завжди засівайте вашу випадковість. Засіяний PRNG перетворює «випадкове» на «обране» — можна регенерувати, порівнювати й курувати результати, а не втрачати кожен хороший результат в момент оновлення сторінки.
  4. Залиште простір для дихання. Не кожна клітина повинна бути заповнена чи максимально насичена — варіюйте товщину лінії, додавайте порожні клітини з певною ймовірністю, або згасайте прозорість до країв.

6. Повний код (50 рядків)

Вставте це в HTML-файл з <canvas id="c" width="600" height="600"></canvas> — працює самостійно, без залежностей:

const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
canvas.width = canvas.height = 600;

// 1. Засіяний PRNG — той самий seed завжди дає ту саму роботу
function mulberry32(seed) {
  return function() {
    seed |= 0; seed = seed + 0x6D2B79F5 | 0;
    let t = Math.imul(seed ^ seed >>> 15, 1 | seed);
    t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
    return ((t ^ t >>> 14) >>> 0) / 4294967296;
  };
}
const SEED = 1337;
const rand = mulberry32(SEED);

// 2. Тло
ctx.fillStyle = '#0a0e1a';
ctx.fillRect(0, 0, canvas.width, canvas.height);

// 3. Єдиний мотив: чвертькругова дуга, випадково повернута
function drawMotif(cell) {
  const r = cell / 2;
  const turn = Math.floor(rand() * 4) * (Math.PI / 2);
  ctx.rotate(turn);
  ctx.strokeStyle = `hsla(${200 + rand() * 60}, 70%, 65%, 0.9)`;
  ctx.lineWidth = 2;
  ctx.beginPath();
  ctx.arc(-r, -r, r, 0, Math.PI / 2);
  ctx.stroke();
}

// 4. Сітка клітин, кожна малюється у власному локальному початку координат
const N = 12;
const cell = canvas.width / N;
for (let row = 0; row < N; row++) {
  for (let col = 0; col < N; col++) {
    ctx.save();
    ctx.translate((col + 0.5) * cell, (row + 0.5) * cell);
    drawMotif(cell);
    ctx.restore();
  }
}

↑ Це той самий скетч на 50 рядків вище, відрендерений наживо на цій сторінці (seed 1337).

Наступний крок: Замініть чвертькругову дугу на flow field на основі шуму Перліна — і отримаєте наступний туторіал серії — Анімація Flow Field із збереженням PNG.

Часті запитання

Чого я навчуся в цьому уроці?

Створіть повноцінний скетч генеративного мистецтва менш ніж за 50 рядків чистого Canvas 2D: рандомізована сітка повернутих дуг, без бібліотек, без білду — лише тег canvas і скрипт.

Які теми розглядаються в цьому уроці?

Цей урок охоплює такі теми: Скелет Canvas, Генератор випадкових чисел із seed, Сітка, Один мотив: повернута дуга, Чотири правила, Повний код (50 рядків).

Скільки часу займає цей урок?

Цей урок займає приблизно 20 хв.

Які попередні знання потрібні?

Це урок рівня «Початківець» — окрема попередня підготовка, крім базового JavaScript, не потрібна.