Перейти к содержанию

Документ, фигура и координаты редактора

В этой серии мы создадим редактор «Схема». В нём можно расположить прямоугольники и эллипсы, подписать их, изменить цвет, сохранить документ и получить изображение. Такой инструмент подходит для небольшой иллюстрации учебного процесса: например, показать редактор материалов и библиотеку сайта. Сегодня определим данные, которые будут описывать эту иллюстрацию. После урока у вас будет документ, пригодный для рисования, сохранения и последующего редактирования.

Понадобится понимание объектов, массивов и модулей JavaScript. Итоговые исходники находятся в архиве серии. Это один законченный снимок проекта; фрагменты уроков объясняют его устройство. Пример подготовлен локально и ещё не запускался. Числа и результаты ниже служат ориентиром для самостоятельной проверки.

Что именно хранит схема

Начнём с обычной страницы размером 1000 на 700 условных единиц. Единица документа пока не равна пикселю экрана. Браузер может показать страницу шириной 500 пикселей, а экспортировать изображение шириной 2000. Положение фигур при этом останется прежним. Если сразу сохранить координаты в пикселях текущего окна, изменение размера браузера начнёт менять смысл документа.

Документ содержит номер формата schema, размеры страницы и массив shapes. Порядок массива одновременно задаёт порядок рисования: более поздняя фигура находится поверх более ранней. Для каждого элемента сохраняем идентификатор, тип, положение, размеры, цвет и подпись. Выделение и масштаб просмотра появятся позднее, но записывать их в этот объект мы не будем: они относятся к работе редактора, а не к самой иллюстрации.

Сокращённый пример документа выглядит так:

const document = {
  schema: 1,
  width: 1000,
  height: 700,
  shapes: [{
    id: 'editor', type: 'rect',
    x: 100, y: 120, width: 240, height: 120,
    fill: '#8756d8', label: 'Редактор'
  }]
};

Это данные для объяснения, а не замена всего model.mjs. В полном файле функция sampleDocument() добавляет ещё зелёный эллипс «Библиотека». У прямоугольника x и y обозначают верхний левый угол. Для эллипса мы используем такие же поля: они задают угол ограничивающего прямоугольника. Благодаря этому панель свойств не должна по-разному вычислять положение двух типов фигур.

Центр эллипса получается как x + width / 2, y + height / 2, а радиусы — как половины размеров. При x = 420, width = 240 центр по горизонтали равен 540. Это простая договорённость, но она будет участвовать в рисовании, определении попадания указателя и экспорте. Если хотя бы один модуль начнёт считать x центром, изображение и область выбора разойдутся.

Допустимые значения

Редактор принимает страницу от 100 до 2000 единиц по каждой стороне и не более 200 фигур. Эти пределы выбраны для учебной задачи. Они делают размер работы обозримым и позволяют хранить небольшие снимки истории. Такие числа не являются доказательством быстродействия на конкретном телефоне: производительность нужно будет измерять отдельно.

Фигура должна полностью помещаться на странице. Поэтому для горизонтального положения действует условие 0 ≤ x ≤ document.width - shape.width. Если ширина страницы равна 1000, а ширина прямоугольника 240, максимально допустимое значение x равно 760. Проверка просто на x ≤ 1000 разрешила бы вынести большую часть фигуры за правый край.

Все координаты должны быть конечными числами. Значения NaN, Infinity, строки и отсутствующие свойства не подходят. Дробные координаты допускаются: движение указателя и масштабирование могут давать их естественным образом. Тип ограничен двумя значениями, цвет — шестизначной HEX-записью, подпись — 120 символами. Это сохраняет небольшой и понятный набор возможностей.

Поле Значение для нашего примера Зачем проверять
schema 1 Отличить известный формат от будущего
id editor Связать выбор, команды и фигуру
type rect или ellipse Выбрать алгоритм геометрии
fill #8756d8 Не принимать произвольные стили
label Обычная строка Ограничить объём и способ вывода

Идентификаторы должны быть уникальными. Две фигуры с одинаковым id делают команду «передвинуть editor» неоднозначной. Проверяем повтор через Set при чтении всего массива. При добавлении в интерфейсе используем crypto.randomUUID(), но импорт всё равно проходит общую проверку: имя функции генерации не делает внешний JSON доверенным.

Проверка возвращает новый объект

Функция validateDocument() из model.mjs проверяет вход и собирает результат из разрешённых полей. Она не возвращает исходный объект целиком. Это означает, что случайные поля вроде onclick, неизвестного URL изображения или сохранённой камеры не перейдут в рабочую модель. Мы не превращаем JSON в универсальный язык SVG.

return {
  schema: 1,
  width: value.width,
  height: value.height,
  shapes
};

Этот фрагмент показывает окончание проверки. До него функция уже проверила каждую фигуру и заново создала элементы массива. Такой подход полезен на границе приложения: после чтения файла, восстановления локального состояния и изменения свойств мы получаем одинаковую модель. Компонент рисования может работать с ней, не повторяя весь разбор JSON.

Проверка структуры не решает каждую задачу редактора. Например, она разрешает длинную подпись, которая визуально выйдет за узкую фигуру. В нашей первой версии текст не переносится автоматически. Это ограничение интерфейса следует описать отдельно; добавление переноса позднее потребует одинаковых правил для SVG и PNG.

Стабильный идентификатор также позволяет менять подпись без потери связи с выделением. Имя «Редактор» может стать «Статьи», но объект останется editor. Если использовать подпись как ключ, одинаковые названия или исправление текста нарушат команды. По той же причине индекс массива не подходит в качестве постоянного имени: перестановка слоёв меняет индекс, хотя фигура остаётся той же.

Номер формата не следует увеличивать при каждом косметическом изменении интерфейса. Он относится к устройству сохранённых данных. Добавление новой обязательной геометрии потребует решения о миграции старых файлов; новый цвет кнопки — нет. Такая дисциплина позволит отдельно развивать оболочку и переносимый документ.

Как разобрать результат

Просмотрите sampleDocument() и выпишите правые края обеих фигур. Для прямоугольника получится 340, для эллипса — 660. Оба значения меньше ширины страницы 1000. Затем мысленно замените x прямоугольника на 900: проверка должна отклонить документ, потому что 900 + 240 выходит за край. Повторите разбор с двумя одинаковыми идентификаторами и неизвестным типом triangle.

При самостоятельном запуске ожидается отказ для этих вариантов и принятие исходного документа. Важно проверять именно границы модели, а не только красивую картинку. Редактор с неверными данными может выглядеть исправно до первой отмены или загрузки файла. Теперь у нас есть единый договор для всех следующих модулей. В следующем уроке превратим его в SVG, сохранив данные источником изображения.

Оглавление серии