Accessibility

Доступность в CesiumJS опирается на фундаментальное противоречие между WebGL-рендерингом и семантическим веб-интерфейсом: сцена отображается через <canvas>, который сам по себе не содержит доступного DOM-дерева, что делает невозможным прямое восприятие объектов экранными дикторами и классическими средствами навигации без дополнительного слоя абстракции.

WebGL в CesiumJS работает как отрисовка пикселей, а не структурированных элементов интерфейса. Это означает:

  • отсутствует семантика элементов сцены (нет DOM-узлов для зданий, объектов, ландшафта);
  • невозможно стандартное фокусирование на объектах карты;
  • события мыши не имеют прямого аналога для assistive technologies;
  • визуальные изменения не транслируются в доступную модель автоматически.

Поэтому доступность в CesiumJS строится не вокруг самой сцены, а вокруг вспомогательных интерфейсов и программной семантики.

Роль ARIA-слоя и вспомогательного DOM

Доступность достигается через дополнительный HTML-слой поверх canvas:

  • панели управления камерой;
  • списки объектов (entities);
  • поисковые интерфейсы;
  • информационные карточки.

Каждый интерактивный элемент должен иметь корректные ARIA-атрибуты:

  • role="button" для кастомных кнопок;
  • aria-label для описания действий;
  • aria-expanded для раскрывающихся панелей;
  • aria-live для динамически обновляемых данных.

Пример структурирования UI:

<div id="toolbar">
  <button aria-label="Приблизить камеру">Zoom In</button>
  <button aria-label="Отдалить камеру">Zoom Out</button>
</div>

<div id="infoPanel" aria-live="polite"></div>

Cesium UI по умолчанию не полностью ориентирован на WCAG-совместимость, поэтому кастомизация интерфейса является ключевым элементом.

Навигация с клавиатуры

Камера и управление сценой должны быть доступны через клавиатуру независимо от мыши.

Основные направления:

  • стрелки — перемещение камеры;
  • + / - — масштабирование;
  • Shift + стрелки — изменение наклона;
  • Home — возврат к глобальному виду;
  • Tab — переключение элементов интерфейса.

Пример привязки кастомных клавиш:

const handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);

document.addEventListener("keydown", (e) => {
  switch (e.key) {
    case "+":
      viewer.camera.zoomIn(10000);
      break;
    case "-":
      viewer.camera.zoomOut(10000);
      break;
    case "ArrowUp":
      viewer.camera.pitch += 0.05;
      break;
  }
});

Ключевая проблема — синхронизация состояния камеры и доступного описания происходящего. Без этого пользователь вспомогательных технологий не понимает, что изменилось.

Управление камерой и доступность

Камера в CesiumJS является центральным элементом взаимодействия, но она не имеет семантического эквивалента в DOM.

Для повышения доступности важно:

  • транслировать изменения камеры в текстовые описания;
  • сообщать о смене высоты, масштаба, региона;
  • ограничивать слишком резкие изменения вида.

Пример обновления доступного статуса:

viewer.camera.moveEnd.addEventListener(() => {
  const height = viewer.camera.positionCartographic.height;

  document.getElementById("infoPanel").textContent =
    `Высота камеры: ${Math.round(height)} м`;
});

При этом важно избегать избыточных обновлений, чтобы screen reader не перегружался постоянными уведомлениями.

Работа с сущностями и семантическим представлением

В CesiumJS сущности (entities) являются основным способом добавления семантики к геометрии.

Каждая сущность должна иметь:

  • имя (name);
  • описание (description);
  • идентификатор (id).

Это позволяет формировать доступное представление сцены вне canvas.

viewer.entities.add({
  id: "building-1",
  name: "Главное здание",
  description: "Офисный центр в центральной части города",
  position: Cesium.Cartesian3.fromDegrees(71.4304, 51.1282),
  point: { pixelSize: 10 }
});

Далее эти данные используются для:

  • списка объектов;
  • поиска;
  • доступных уведомлений при выборе.

Поддержка screen reader через selection feedback

При выборе объекта необходимо явно уведомлять assistive technologies.

viewer.selectedEntityChanged.addEventListener((entity) => {
  const panel = document.getElementById("infoPanel");

  if (!entity) {
    panel.textContent = "Объект не выбран";
    return;
  }

  panel.textContent = `${entity.name}: ${entity.description}`;
});

Без этого события визуальная интеракция остаётся полностью недоступной.

Фокус и управление DOM-слоем

Cesium canvas часто перехватывает события мыши и клавиатуры, поэтому требуется явное управление фокусом:

  • избегание “потери фокуса” при клике в сцену;
  • возврат фокуса на панель управления;
  • изоляция canvas от tab-потока, если он не управляем с клавиатуры.
viewer.canvas.setAttribute("tabindex", "0");

viewer.canvas.addEventListener("focus", () => {
  console.log("Canvas в фокусе");
});

В сложных интерфейсах canvas часто исключается из tab-навигации полностью, а управление выносится в UI.

Контраст и визуальные ограничения

3D-сцены CesiumJS подвержены проблемам:

  • низкий контраст объектов на фоне terrain;
  • зависимость от освещения и времени суток;
  • визуальные шумы из-за текстур.

Для повышения доступности применяются:

  • режимы высокого контраста;
  • отключение атмосферы и пост-эффектов;
  • упрощённые материалы.
viewer.scene.highDynamicRange = false;
viewer.scene.globe.enableLighting = false;

Также важно учитывать пользователей с дальтонизмом: цвет не должен быть единственным маркером состояния объектов.

Динамические данные и временная ось

CesiumJS поддерживает временную модель сцены, что усложняет доступность из-за постоянных изменений.

Проблема:

  • объекты появляются и исчезают;
  • геометрия изменяется во времени;
  • визуальные события не фиксируются пользователем вспомогательных технологий.

Решение:

  • текстовое логирование событий;
  • временные метки;
  • управление скоростью анимации.
viewer.clock.multiplier = 0; // пауза для анализа состояния

Picking и доступное представление выбора

Механизм picking (выбор объектов под курсором) необходимо дублировать через список:

const handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);

handler.setInputAction((movement) => {
  const picked = viewer.scene.pick(movement.position);

  if (picked && picked.id) {
    viewer.selectedEntity = picked.id;
  }
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);

Однако этого недостаточно — требуется параллельный список объектов с возможностью выбора через клавиатуру.

Доступные списки объектов

Создание отдельного DOM-представления сцены:

<ul id="entityList" role="listbox"></ul>
const list = document.getElementById("entityList");

viewer.entities.values.forEach((e) => {
  const item = document.createElement("li");
  item.setAttribute("tabindex", "0");
  item.textContent = e.name;

  item.addEventListener("click", () => {
    viewer.selectedEntity = e;
  });

  list.appendChild(item);
});

Такой подход превращает 3D-сцену в доступную структуру данных.

Управление уведомлениями и ARIA live regions

Динамические изменения сцены должны транслироваться через aria-live:

  • мягкие обновления (polite);
  • критические изменения (assertive).
<div id="status" aria-live="polite"></div>
document.getElementById("status").textContent =
  "Загружены новые тайлы местности";

Чрезмерное использование assertive-режима приводит к перегрузке экранных дикторов и должно избегаться.

Ограничения нативной доступности Cesium UI

Стандартный интерфейс Cesium включает элементы управления, не всегда соответствующие требованиям доступности:

  • недостаточная маркировка кнопок;
  • отсутствие полной клавиатурной навигации;
  • ограниченные ARIA-описания.

Поэтому промышленная практика заключается в:

  • замене UI на кастомный;
  • отключении встроенных контролов;
  • построении доступного слоя поверх сцены.
viewer.animation.container.style.display = "none";
viewer.timeline.container.style.display = "none";

Семантическое дублирование 3D-сцены

Основной принцип доступности в CesiumJS — дублирование визуальной сцены в структурированные данные:

  • список объектов;
  • текстовые описания;
  • лог событий;
  • альтернативные представления маршрутов и геометрии.

3D-сцена остаётся визуальным слоем, а доступность реализуется через параллельную информационную модель, синхронизированную с состоянием камеры, сущностей и пользовательских действий.