Довідник · Гід контриб'ютора · Three.js · WebGL
📅 Липень 2026 ⏱ ≈ 16 хв 🎯 Середній рівень

Шаблон нової симуляції

Кожна симуляція на 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 — тримайте всі три синхронізованими.

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; }
Power preference: завжди запитуйте "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») — він має точно відповідати тому, що насправді робить симуляція. Поширені патерни взаємодії, що вже використовуються на сайті:

Завжди показуйте видиму підказку прямо на полотні (невеликий оверлей у лівому нижньому куті, прозорість ~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"
}

Примітки щодо полів:

8. Бюджет продуктивності

Сайт орієнтований на 60 кадрів/с на ноутбуці середнього класу та прийнятну частоту кадрів на мобільних пристроях. Застосовуйте ці бюджети, обираючи кількість частинок і складність шейдерів:

Обмеження pixel ratio: min(devicePixelRatio, 2) Бюджет частинок (десктоп): до ~150 000 інстансованих точок, або ~20 000 симульованих твердих тіл Бюджет частинок (мобільні, viewport < 768px): зменшити кількість у ~4 рази і вимкнути тіні Draw calls: тримати нижче ~50 на кадр — використовувати InstancedMesh / об'єднання BufferGeometry, а не окремий меш на кожну частинку Shadow maps: вимкнено за замовчуванням; вмикати лише для hero/featured симуляцій, з обмеженням 1024×1024

Визначайте слабкі пристрої простою евристикою (час рендерингу кадру за перші ~2 секунди, або navigator.hardwareConcurrency у поєднанні з шириною viewport) і перемикайтеся на пресет зі зменшеною кількістю частинок, замість того щоб постачати два окремі шляхи коду.

9. Доступність та мобільні пристрої

10. Чек-лист перед подачею

Перш ніж відкривати pull request для нової симуляції, перевірте:

Рецензенти завантажать симуляцію з CPU-обмеженням 6× і перевірять на пропущені кадри перед злиттям — плануйте бюджет частинок відповідно, а не підганяйте його вже після відгуку.