DevGang
Авторизоваться

Медиа-запросы в JS выполняются правильно

У каждого может возникнуть ситуация, связанная с прослушиванием ширины экрана приложения.

Существует множество методов достижения этого, но хотелось бы чего-то простого, многоразового и универсального, что не слишком влияло бы на производительность.

Прослушивание события resize в window и проверка innerWidth происходит слишком медленно, тем более что все, что связано с макетом, вызывает перепрошивку.

Во время поиска решений рассмотрим API window.matchMedia(), и представим, что это лучшее решение (оно также поддерживается во всех основных браузерах).

Единственное, что останется сделать, - это внедрить простое, многоразовое и универсальное решение. 

Нам хотелось бы иметь возможность subscribe на различные медиа-запросы из разных компонентов приложения, не создавая новых слушателей.

Решение состоит в том, чтобы создать static class (class со static методами и переменными), который сохранял бы список запросов и подписок для этих запросов.

Этот класс будет предоставлять 2 общедоступных метода: subscribe и unsubscribe.

Метод subscribe принимает функцию обратного вызова, которая получает boolean параметр, представляющий, соответствует ли запрос или нет, и фактический запрос в строковом формате (например: '(max-width: 768px)').

Затем он проверяет, есть ли уже подписка, созданная для этого запроса из какого-либо другого места:

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

В любом случае, он также мгновенно вызывает обратный вызов с текущим совпадением запроса.

export class MediaQueryListener {
    //...

    static subscribe = (cb: (matchesQuery: boolean) => void, query: string) => {
        if (!this._subscribers[query]) {
            this._subscribers[query] = {
                query: window.matchMedia(query),
                subscribers: [ cb ],
            };

            // keep the refs so we can remove the listeners in unsubscribe
            this._eventListenersRefs[query] = event => {
                this._subscribers[query]
                    .subscribers
                    .forEach(subscriber => subscriber(event.matches));
            };

            // create the listener only on first subscription for the query
            this._subscribers[query]
                .query
                .addEventListener('change', this._eventListenersRefs[query]);
        } else {
            this._subscribers[query].subscribers.push(cb);
        }

        // Call the callback with the current value of the media query
        cb(window.matchMedia(query).matches);
    };
}

Метод unsubscribe получает ту же функцию обратного вызова и запрос в строковом формате и удаляет обратный вызов у подписчиков этого запроса. Он также проверяет, не осталось ли больше подписчиков, и удаляет прослушиватель медиа-запросов, чтобы не осталось зависших прослушивателей.

export class MediaQueryListener {
    //...

    static unsubscribe = (cb: (matchesQuery: boolean) => void, query: string) => {
        if (!this._subscribers[query]) {
            return;
        }

        this._subscribers[query].subscribers = this._subscribers[query].subscribers.filter(subscriber => subscriber !== cb);

        if (this._subscribers[query].subscribers.length === 0) {
            this._subscribers[query].query.removeEventListener('change', this._eventListenersRefs[query]);

            delete this._subscribers[query];
            delete this._eventListenersRefs[query];
        }
    };
}

Это базовая оболочка поверх matchMedia api. Вероятно, существует несколько пакетов, которые так или иначе достигают одной и той же цели.

#JavaScript
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу