Шаблон нової симуляції
Кожна симуляція на mysimulator.uk будується за однаковим каркасом:
самодостатня папка з index.html, ініціалізацією
рендерера Three.js, циклом рендерингу, безпечним до зміни розміру
вікна, та записом метаданих, що підключає її до сітки на головній
сторінці, індексу пошуку та посилань зі статей. Цей довідник —
канонічна заготовка, яку слід копіювати, починаючи нову симуляцію
з нуля.
1. Структура файлів і папок
Симуляції розміщуються в корені сайту, по одній папці на кожен
id симуляції, за тим самим шаблоном, що й наявні
записи, наприклад /fluid/index.html і
/galaxy/index.html. Нова симуляція під назвою,
скажімо, vortex має виглядати так:
/vortex/
index.html // самодостатній: розмітка + inline <style> + inline <script type="module">
thumb.jpg // опційно: прев'ю 1200×630 для og:image / картки в сітці
Симуляції не проходять окремий крок збирання —
сайт не має бандлера. Імпортуйте Three.js зі спільної import map
CDN, оголошеної в shared/components.js, точно так
само, як у наявних симуляціях, щоб модуль резолвився однаково на
кожній сторінці.
id (vortex, а не
Vortex-Field). Він стає назвою папки, JSON
id та частиною канонічного URL — тримайте всі три
синхронізованими.
2. Заготовка HTML head
Скопіюйте цей блок head без змін і заповніть заголовок, опис і канонічний шлях. Кожній сторінці симуляції потрібен канонічний URL, зображення OG та meta viewport, налаштований під повноекранні WebGL-полотна:
<!doctype html>
<html lang="en" data-theme="dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<link rel="canonical" href="https://www.mysimulator.uk/vortex/" />
<title>Vortex Field — 3D Simulations</title>
<meta name="description" content="..." />
<meta property="og:image" content="https://www.mysimulator.uk/vortex/thumb.jpg" />
<style>
html, body { margin: 0; overflow: hidden; background: #000; }
#canvas-holder { position: fixed; inset: 0; }
</style>
</head>
Якщо симуляція також має супровідну статтю в
content/articles/, пов'яжіть її через
articleId у записі метаданих (див.
розділ 7) замість дублювання пояснення на
сторінці самої симуляції.
3. Ініціалізація рендерера
Кожна симуляція ініціалізує одні й ті самі три компоненти — сцену, камеру, рендерер — перш ніж торкатися логіки, специфічної для конкретної симуляції. Тримайте параметри рендерера однаковими для всіх симуляцій, щоб поведінка продуктивності лишалася передбачуваною:
import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
const scene = new THREE.Scene();
scene.fog = new THREE.FogExp2(0x000000, 0.02);
const camera = new THREE.PerspectiveCamera(
60, innerWidth / innerHeight, 0.1, 2000
);
camera.position.set(0, 8, 24);
const renderer = new THREE.WebGLRenderer({
antialias: true,
powerPreference: "high-performance",
});
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(Math.min(devicePixelRatio, 2)); // обмеження DPR — див. розділ 8
document.getElementById("canvas-holder").appendChild(renderer.domElement);
const controls = new THREE.OrbitControls
? new OrbitControls(camera, renderer.domElement)
: null;
if (controls) { controls.enableDamping = true; controls.dampingFactor = 0.06; }
"high-performance". На ноутбуках з гібридною
відеокартою це спрямовує браузер до дискретного GPU, що важливо
для симуляцій із великою кількістю частинок.
4. Цикл рендерингу та зміна розміру
Використовуйте єдиний цикл requestAnimationFrame,
керований годинником дельта-часу, щоб симуляція йшла з однаковою
фізичною швидкістю незалежно від частоти кадрів, і завжди
захищайтеся від нульового розміру/фонових вкладок:
const clock = new THREE.Clock();
let running = true;
function animate() {
if (!running) return;
requestAnimationFrame(animate);
const dt = Math.min(clock.getDelta(), 0.05); // обмеження великого dt після перемикання вкладки
updateSimulation(dt);
controls?.update();
renderer.render(scene, camera);
}
addEventListener("resize", () => {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
});
document.addEventListener("visibilitychange", () => {
if (document.hidden) { clock.stop(); }
else { clock.start(); }
});
animate();
5. Керування та взаємодія
Картка на головній сторінці показує короткий рядок
controls (наприклад, «Click+drag to splash»,
«Drag to rotate») — він має точно відповідати тому, що
насправді робить симуляція. Поширені патерни взаємодії, що вже
використовуються на сайті:
-
Орбітальна камера:
OrbitControlsз демпфуванням для пасивних/фонових симуляцій (галактики, орбіти, ландшафти). - Силове поле під курсором: рейкастинг курсора на площину землі та застосування імпульсу притягання/відштовхування до сусідніх частинок — використовується у симуляціях рідини, зграй і роїв частинок.
-
Клавіатурні перемикачі: зарезервуйте цифрові
клавіші
1–4для перемикання візуальних пресетів/режимів кольору; зарезервуйтеSpaceдля паузи/відновлення.
Завжди показуйте видиму підказку прямо на полотні (невеликий оверлей у лівому нижньому куті, прозорість ~60%), що описує основне керування, оскільки більшість відвідувачів ніколи не відкривають dev tools і не читають статтю.
6. Очищення та знищення ресурсів
Симуляції завантажуються як окремі сторінки, а не як маршрути
SPA, тому повне знищення ресурсів рідко потрібне — але
інстансовані геометрії, render targets і GPU compute-текстури,
що використовуються симуляціями частинок, достатньо великі, щоб
мати значення при pagehide, і будуть обов'язковими,
якщо симуляцію колись вбудують у iframe/прев'ю:
addEventListener("pagehide", () => {
scene.traverse((obj) => {
obj.geometry?.dispose();
if (Array.isArray(obj.material)) obj.material.forEach((m) => m.dispose());
else obj.material?.dispose();
});
renderer.dispose();
});
7. Схема simulations.json
Реєстрація симуляції — це те, що робить її видимою в сітці на
головній сторінці, в індексі пошуку та у фільтрах категорій.
Додайте один запис у shared/data/simulations.json
(англійська та українська назва/опис зберігаються поряд в одному
об'єкті — окремого локалізованого JSON-файлу для цього немає):
{
"id": "vortex",
"title": "Vortex Field",
"titleUk": "Вихрове Поле",
"emoji": "🌀",
"category": "physics",
"path": "vortex/index.html",
"description": "...",
"descriptionUk": "...",
"tags": ["Three.js", "GPGPU", "Curl Noise"],
"difficulty": 3,
"status": "ready",
"featured": false,
"dateAdded": "2026-07-03",
"articleId": null,
"fps": 60,
"controls": "Click+drag to splash",
"tech": "3D"
}
Примітки щодо полів:
-
categoryмає бути одним із значень, уже перелічених уshared/data/categories.json(physics,space,nature,algorithms, …) — додавання нової категорії потребує окремої зміни цього файлу і виходить за межі подачі однієї симуляції. -
difficulty— ціле число від 1 до 5, що використовується для сортування та бейджа складності; базуйте його на необхідних попередніх знаннях, а не на довжині коду. -
statusдорівнює"ready", коли симуляція розгорнута та стабільна; використовуйте"beta", якщо вона опублікована, але ще нестабільна на слабких GPU. -
articleIdпосилається на відповідний slug уcontent/articles/, коли існує детальна стаття; інакше залиштеnull.
8. Бюджет продуктивності
Сайт орієнтований на 60 кадрів/с на ноутбуці середнього класу та прийнятну частоту кадрів на мобільних пристроях. Застосовуйте ці бюджети, обираючи кількість частинок і складність шейдерів:
Визначайте слабкі пристрої простою евристикою (час рендерингу
кадру за перші ~2 секунди, або
navigator.hardwareConcurrency у поєднанні з шириною
viewport) і перемикайтеся на пресет зі зменшеною кількістю
частинок, замість того щоб постачати два окремі шляхи коду.
9. Доступність та мобільні пристрої
-
Поважайте
prefers-reduced-motion: якщо він встановлений, або призупиніть безперервне автообертання камери, або зменшіть швидкість симуляції приблизно на 70%, замість того щоб вимикати симуляцію повністю. -
Підтримуйте мишу і дотик для основної взаємодії — pointer-події
(
pointerdown/pointermove) покривають обидва варіанти без дублювання слухачів. -
Саме полотно не має текстової альтернативи; переконайтеся, що
запис картки симуляції на головній сторінці
(
title/descriptionу JSON) повністю описує те, що показує симуляція, оскільки саме цей текст бачать програми читання з екрана та пошукові системи. - Ніколи не блокуйте фокус клавіатури на полотні — відвідувачі мають зберігати можливість переходити табом до спільних посилань навбару/футера.
10. Чек-лист перед подачею
Перш ніж відкривати pull request для нової симуляції, перевірте:
- ☐ Папку +
index.htmlстворено в корені сайту - ☐ Встановлено канонічний URL, заголовок, опис і зображення OG
-
☐ Рендерер використовує
powerPreference: "high-performance"та обмежений pixel ratio -
☐ Підключено обробники зміни розміру та
visibilitychange -
☐ Підказка керування на полотні відповідає рядку
controlsу записі метаданих -
☐ Запис додано до
shared/data/simulations.jsonіз заповненимиtitle/titleUkтаdescription/descriptionUk - ☐ Працює з цільовою частотою кадрів у профілі з обмеженням/слабким пристроєм у DevTools, а по можливості — і на реальному мобільному пристрої
- ☐ Поважає
prefers-reduced-motion -
☐ (Опційно) додано супровідну статтю в
content/articles/та пов'язано черезarticleId