Часть 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 секунды.

После загрузки изображений:

  1. шрифты и иконки распакованы
  2. интерфейс смонтирован
  3. прослушиватели событий мыши/клавиатуры активированы
  4. основной цикл начинает работать
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.