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. Чотири правила
Ці чотири обмеження відрізняють навмисне генеративне мистецтво від «випадкового шуму із зайвими кроками»:
- Обмежте палітру. Обирайте hue з вузького діапазону (тут 200–260, сині у фіолетові) замість повного кола кольорів — обмежена палітра виглядає навмисною.
- Обмежте мотив. Одна форма, жменька трансформацій (поворот, масштаб, віддзеркалення) — а не інша випадкова форма щоразу.
- Завжди засівайте вашу випадковість. Засіяний PRNG перетворює «випадкове» на «обране» — можна регенерувати, порівнювати й курувати результати, а не втрачати кожен хороший результат в момент оновлення сторінки.
- Залиште простір для дихання. Не кожна клітина повинна бути заповнена чи максимально насичена — варіюйте товщину лінії, додавайте порожні клітини з певною ймовірністю, або згасайте прозорість до країв.
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).
Часті запитання
Чого я навчуся в цьому уроці?
Створіть повноцінний скетч генеративного мистецтва менш ніж за 50 рядків чистого Canvas 2D: рандомізована сітка повернутих дуг, без бібліотек, без білду — лише тег canvas і скрипт.
Які теми розглядаються в цьому уроці?
Цей урок охоплює такі теми: Скелет Canvas, Генератор випадкових чисел із seed, Сітка, Один мотив: повернута дуга, Чотири правила, Повний код (50 рядків).
Скільки часу займає цей урок?
Цей урок займає приблизно 20 хв.
Які попередні знання потрібні?
Це урок рівня «Початківець» — окрема попередня підготовка, крім базового JavaScript, не потрібна.