Часть 5: Динамическая архитектура и инициализация
Предыдущую статью вы можете прочитать здесь.
Проверка
Что ж, в демонстрации из прошлой статьи мы реагировали на событие нажатия кнопки мыши на виджетах, изменяя цвета виджетов и изменяя порядок виртуальных слои (что означает изменение CSSz-Index каждого холста — холст — это сердце, суть панели).
CSSz-Index?Прощаемся с HTML/CSS или нет? Помимо тегов html и body, библиотека использует под капотом только два типа HTML-элементов:
- div:только один раз для этапа
- холст: по одному для каждой панели
И для этих двух типов HTML-элементов библиотека использует базовые свойства CSS, такие как left, top, и z-Index. Просто вы не имеете к ним прямого отношения.
Только холст (внутри панели) получает события мыши, предоставляемые браузером. виджеты получают события мыши, созданные библиотекой.
myWidget.onmousedown = function (e) { console.log("mouse down") }
myWidget не является элементом HTML, но вы должны обращаться с ним так, как он есть. Кусок пирога ;)
Динамическая архитектура
Когда мы рассматриваем названия Статическая Архитектура и Динамическая Архитектура, мы не должны забывать, что мы говорим о движке, работающем внутри браузера, который имеет динамическую природу. Настоящая статическая архитектура не смогла бы создать две простые анимации последней демонстрации.
Прямой ответ
Мы создали эти анимации с помощью прямой реакции на событие мыши. Это просто и хорошо. Но этого будет недостаточно, и это может стать проблемой по мере роста приложения.
Прямого ответа недостаточно, когда нам нужна синхронизированная анимация, например мигающий курсор.
Прямой ответ — это проблема, когда… ну, лучший способ объяснить — использовать реальный случай (но упрощая внутренние детали).
Одной из особенностей BobSprite является постоянное предоставление обратной связи о пикселе, который находится под мышью (центр курсора): положение (X, Y), образец цвета и значения RGBA («непрозрачный» означает, что значение альфа-канала равно 255). Эта информация отображается в левом нижнем углу приложения.
Используя прямой ответ, мы бы сделали что-то вроде этого:
// simplified code picture.onmousemove = updatePixelInfo function updatePixelInfo(e) { const x = e.offsetX const y = e.offsetY const rgba = getPicturePixel(x, y) printPixelInfo(x, y, rgba) }
Хорошо, но нам также нужно перерисовать приложение, потому что курсор (черно-белая рамка) перемещается вместе с мышью:
// simplified code picture.onmousemove = mouseMoveHandler function mouseMoveHandler(e) { const x = e.offsetX const y = e.offsetY // repaintApp() updatePixelInfo(x, y) } function updatePixelInfo(x, y) { const rgba = getPicturePixel(x, y) printPixelInfo(x, y, rgba) }
ХОРОШО. Но теперь у нас есть проблема, потому что repaintApp работает медленно (на самом деле repaintApp — самая медленная функция в BobSprite), и пользователь может перемещать мышь очень быстро, перегружая repaintApp, что делает приложение менее отзывчивым, даже с небольшими зависаниями.
И мы еще не рисуем (что включает в себя гораздо больше обработки, включая запоминание), просто двигаем мышкой.
Кроме того, есть много клавиатурных команд, которые меняют картинку (например, «R» для поворота). Каждый из них должен вызывать repaintApp и updatePixelInfo. Но updatePixelInfo ожидает получения события мыши,и у нас есть событие клавиатуры.
Самая большая проблема с прямым ответом заключается в том, что он запускает цепочку вызовов функций.
Не имеет большого значения, если цепочка функций линейна (A › B › C › D) и не конфликтует с другой цепочкой функций.
Для инструмента рисования существуют конфликты, и цепочки функций становятся лабиринтными. Внедрение новой функции в приложение подразумевает разрыв и воссоздание старых цепочек функций (большой рефакторинг). Также существует очень важная проблема избыточности: запросто мы могли бы, например, вызвать repaintApp больше раз, чем необходимо.
Таким образом, в зависимости от типа вашего приложения, использование прямого ответа на события мыши и клавиатуры подразумевает, в наилучшем сценарии, создание обходных путей и создание беспорядка в нашем коде, >очень мало ремонтопригоден.
Косвенный ответ
Решение заключается в использовании шаблона косвенный ответ, который также обрабатывает анимацию, такую как мигающий курсор.
// simplified code var mouseX = -1 var mouseY = -1 var shallRepaint = false var shallUpdatePixelInfo = false picture.onmousemove = mouseMoveHandler function mouseMoveHandler(e) { mouseX = e.offsetX mouseY = e.offsetY // shallRepaint = true shallUpdatePixelInfo = true } function updatePixelInfo() { // no parameters! // const rgba = getPicturePixel(mouseX, mouseY) printPixelInfo(mouseX, mouseY, rgba) } function mainLoop() { // manageBlinkingWidgets() // if (shallRepaint) { repaintApp() shallRepaint = false shallUpdatePixelInfo = true } // if (shallUpdatePixelInfo) { updatePixelInfo() // no arguments! shallUpdatePixelInfo = false } // the browser provides this timer requestAnimationFrame(mainLoop) }
Несколько замечаний по поводу нового стиля кода:
- только одна функция вызывает дорогое приложение repaintAPP
- избыточность не больше не проблема; любая функция в любом месте может ПОМНИТЬ, что repaintApp следует вызывать без каких-либо проблем, потому что она просто устанавливает логическое значение (shallRepaint = true), самую недорогую процедуру в мире.
- кроме того, нет проблем с избыточностью для updatePixelInfo; он основан на FLAG, как repaintApp
- событие клавиатуры по-прежнему не знает положение мыши; и ему не нужно знать, потому что он не будет вызывать функцию (updatePixelInfo), он установит флаг (shallUpdatePixelInfo= true )
- mouseMoveHandler завершается без вызова какой-либо функции; мы обрабатываем событие мыши БЕЗ ЗАПУСКА ЦЕПИ ВЫЗОВОВ ФУНКЦИЙ!
- хотя requestAnimationFrame заставляет цикл выполняться в целом 60 раз в секунду, приложение является экономичным, поскольку использование флагов пропускает процедуры, которые не требуются в данный момент.
Это общая концепция. Библиотека использует requestAnimationFrame и предоставляет дескрипторы для подключения ваших обратных вызовов. Подробнее об этом в другой статье.
Инициализация
Когда мы создаем страницу HTML/CSS, нам не нужно особо заботиться о загрузке ресурсов. Мы просто объявляем «поместите эту фотографию здесь», «используйте шрифт ABC», «стилизуйте кнопки с помощью этого листа CSS»…
Браузер позаботится обо всем за нас. Хотя шрифт ABC не загружен, он использует некоторый шрифт-заполнитель. После загрузки шрифта ABC замещающий шрифт заменяется шрифтом ABC.
Эта стратегия иногда немного странная для пользователя. Через несколько секунд, пока кто-то читает страницу, шрифты (и даже макет) меняются. Я не жалуюсь. Это просто факт. На самом деле, я думаю, что это правильная стратегия. Плохая стратегия — не показывать страницу, пока не будут загружены все ресурсы.
Инициализация нашего приложения на основе холста отличается. Нам нужно сначала загрузить листы шрифтов, чтобы напечатать текст. То же самое происходит и с иконами. Это стратегия «все или ничего»:
- мы загружаем все перед отображением чего-либо, мы не заботимся о заполнении пробела (показ содержимого сразу, использование временных ресурсов)
- мы можем изменить цвет фона за пределами сцены, чтобы показать пользователю, что что-то происходит.
- мы очень внимательно относимся к количеству загружаемых файлов и размеру каждого файла; мы упаковываем изображения в один лист и упаковываем весь код JavaScript в один файл
Помните, мы создаем специальное приложение. Новичок не должен заходить на эту страницу первым. Он должен попасть на главную страницу сайта. Он может подождать 2 секунды.
После загрузки изображений:
- шрифты и иконки распакованы
- интерфейс смонтирован
- прослушиватели событий мыши/клавиатуры активированы
- основной цикл начинает работать
var numberOfResourcesToLoad = 0 function main() { // loadImages() // manages numberOfResourcesToLoad // recoverDataFromLocalStorage() // main2() } function main2() { // if (numberOfResourcesToLoad == 0) { afterLoadResources(); return } // setTimeout(main2, 30) } function afterLoadResources() { // initFonts() initIcons() initInterface() // initMouseListening() initKeyboardListening() mainLoop() }
Что дальше
Сегодня мы изучили более фундаментальные концепции страницы на основе холста. Мы еще не говорили (достаточно) об обработке событий клавиатуры. Кроме того, у нас нет демоверсии.
Прежде чем говорить об обработке событий клавиатуры и запуске демонстрации, нам нужны виджеты для ввода текста; потому что обработка событий клавиатуры связана с фокусом (какой виджет является целью нажатия клавиш). Виджет для ввода текста и его работа (включая мигающий курсор) — самая сложная часть движка. Простой демонстрации будет недостаточно.
Поэтому я решил, что следующее демо/статья:
- продемонстрирует фокус и обработку событий клавиатуры
- будет полноценным приложением с несколькими полезными функциями
- выпустит первую версию библиотеки, готовую к использованию
Это ссылка на следующую статью серии.
Дополнительные материалы на PlainEnglish.io. Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter и LinkedIn. Присоединяйтесь к нашему сообществу Discord.