Демо: https://lucamug.github.io/elm-scroll-resize-events/
Это небольшой пример о событиях прокрутки и изменения размера в Elm через порты.
Я создал два порта в Elm, один для получения данных о позиции прокрутки, высоте страницы и размере области просмотра. Все это целые числа, как описано в этом определении типа.
type alias ScreenData = { scrollTop : Int , pageHeight : Int , viewportHeight : Int , viewportWidth : Int }
Порт просто определяется как
port scrollOrResize : (ScreenData -> msg) -> Sub msg
Основной скрипт подписывается на этот порт с помощью
subscriptions : Model -> Sub Msg subscriptions model = Sub.batch [ scrollOrResize OnScroll ]
Итак, теперь каждый раз, когда Javascript отправляет данные через этот порт, OnScroll
сообщение отправляется функции update
.
Затем функция обновления просто обновит модель данными:
OnScroll data -> ( { model | screenData = Just data }, Cmd.none )
В Javascript есть небольшая функция для дросселирования и подавления событий scroll
и resize
. Без дросселирования и устранения неполадок эти события запускаются много раз каждую секунду и могут ухудшить взаимодействие с пользователем.
Функция
var scrollTimer = null; var lastScrollFireTime = 0; var minScrollTime = 200; var scrolledOrResized = function() { if (scrollTimer) {} else { var now = new Date().getTime(); if (now - lastScrollFireTime > (3 * minScrollTime)) { processScrollOrResize(); lastScrollFireTime = now; } scrollTimer = setTimeout(function() { scrollTimer = null; lastScrollFireTime = new Date().getTime(); processScrollOrResize(); }, minScrollTime); } };
Первое условие if if (scrollTimer)
проверяет наличие инициализированного таймера (setTimeout). В этом случае функция просто выйдет.
Второе условие if if (now — lastScrollFireTime > (3 * minScrollTime))
проверяет, прошло ли много времени с момента последнего вызова функции. Если это так, вероятно, это начальный момент нового действия прокрутки, поэтому мы хотим сразу же отправить данные в Elm, а затем установить таймер.
Последний раздел - установка таймера. Это сработает через определенный период времени, и в течение этого периода функция не будет предпринимать никаких действий из-за первого условия if.
Для связи с Elm используется следующая функция:
var processScrollOrResize = function() { var screenData = { scrollTop: parseInt(_window.pageYOffset || _html.scrollTop || _body.scrollTop || 0), pageHeight: parseInt(Math.max(_body.scrollHeight, _body.offsetHeight, _html.clientHeight, _html.scrollHeight, _html.offsetHeight)), viewportHeight: parseInt(_html.clientHeight), viewportWidth: parseInt(_html.clientWidth), }; app.ports.scrollOrResize.send(screenData); }
Эта функция использует некоторые уловки для получения данных из разных браузеров. Затем отправьте эти данные через порт scrollOrResize
.
После того, как данные получены Elm и функция обновления обновила модель, отображается новый вид. Представление использует новые данные для отображения полосы вверху с указанием процента прокрутки и модального окна в центре с данными.
В Elm есть еще один порт, который используется для прокрутки страницы вверх.
port scrollTop : Int -> Cmd msg
Javascript подписывается на этот порт с помощью
app.ports.scrollTop.subscribe(elmScrollTop);
Это просто выполнило бы эту функцию
var elmScrollTop = function(position) { _window.scroll({ top: position, left: 0, behavior: 'smooth' }); };
window.scroll
находится на стадии Рабочий проект, поэтому я загружаю для него полифилл.
Чтобы запустить демонстрацию локально, вы можете выполнить следующие команды:
$ git clone https://github.com/lucamug/elm-scroll-resize-events $ cd elm-scroll-resize-events $ elm-live --output=elm.js src/Main.elm --open --debug