Многие из нас привыкли к тому, что быстрый и стабильный интернет это данность в 2023 году. Однако все еще остаются сценарии, когда это не так: например, в дороге между населенными пунктами или в помещениях, которые по какой-то причине плохо пропускают сигнал.
Для нашего проекта combat-sport.club как раз актуальна ситуация, когда взвешивание спортсменов перед проведением соревнований нередко происходит в каком-нибудь подвальном помещении с плохой связью, и тяжелый SPA с большим количеством медиа может грузиться очень долго. В свою очередь это влияет и на возможность работать с платформой и в целом на удовлетворенность пользователей.
Можно считать это как продолжение серии моих статей про оптимизацию в целом: раз и два.
В этой статье я рассмотрю один из методов оптимизации сайта с помощью Network Information API. Это API с большим набором ��азличной информации о сети, но пока не с самой лучшей поддержкой среди браузеров. Тем не менее это не повод не использовать его для тех пользователей, чей браузер это поддерживает - а это около 73% глобальных пользователей. Примеры кода будут на Vue.
Компонент для картинок и сервис imagekit
Начну с того, что у нас на проекте есть компонент AppImage, который отвечает за все изображения, а для их хостинга используется сервис imagekit.io. Опуская не имеющие значения детали, в упрощенном виде AppImage выглядит вот так:
<script lang="ts" setup> import { computed } from 'vue' import { useMedia } from '@/composables/media.ts' interface IProps { imgId?: string src?: string height: number | string width: number | string ... } const props = withDefaults(defineProps<IProps>(), { imgId: '', src: '', ... }) const { getImageById } = useMedia() const srcAttrs = computed(() => { if (props.src) { return { src: props.src, } } return { src: getImageById(props.imgId, +props.width, +props.height, props.type), srcset: `${getImageById(props.imgId, +props.width * 2, +props.height * 2, props.type)} 2x`, } }) </script> <template> <img v-bind="srcAttrs" :height="height" :width="width" > </template>
В нем есть функция getImageById, которая обращается к сервису imagekit с id изображения и параметрами w и h для запроса этого изображения в определенных размерах:
const getImageById = ( id: string, width: number, height: number, ... ): string => { ... return `${BASE_URL}/${id}${width ? `?tr=w-${width}` : ''}${width && height ? `,h-${height}` : ''}` }
Помимо этого, у сервиса есть параметр q, который принимает значения от 1 до 100 и отвечает за качество отдаваемой картинки. Например, вот две картинки с q=90 и q=20, вес которых соответственно составляет 31kb и 3kb:

Именно этот параметр я и буду менять в зависимости от скорости интернета у пользователя. Если же вы подобным сервисом не пользуетесь, то хотя все будет сложнее, но все же вы можете отдельно подготовить картинки разного качества и подставлять их.
Используем effectiveType для определения скорости сети
Нам нужно написать сервис, который будет отслеживать информацию о сети и вслед за этим менять качество картинок. Назовем сервис handleNetwork и объявим переменные, которые нам нужны:
import { ref } from 'vue' export function handleNetwork() { const imagesQuality = ref(90) const effectiveType = ref(undefined) }
effectiveType это свойство Network Information API, доступное через navigator.connection.effectiveType. Оно может иметь несколько значений: slow-2g, 2g, 3g, 4g, каждое из которых соответствует определенной скорости интернета, даже если пользователь использует wifi или проводной интернет. Вот таблица с небольшим пояснением:

Именно на эти значения мы и будем ориентироваться, меняя переменную imagesQuality.
Для того чтобы проинициализировать и отслеживать изменения значения effectiveType нам нужно добавить слушатель на событие change для navigator.connection, а также создать функцию updateNetworkState, которая будет обрабатывать обновления.
Важно: обязательно добавляйте проверку на то, что браузер поддерживает это API с помощью простой проверки 'connection' in navigator:
function updateNetworkState() { ... } if (navigator && 'connection' in navigator) { navigator.connection.addEventListener('change', updateNetworkState) } updateNetworkState()
Теперь напишем функцию updateNetworkState. В ней тоже добавим проверку на поддержку API и конструкцию switch, которая будет задавать значения для imagesQuality:
function updateNetworkState() { if (!navigator || !('connection' in navigator)) return effectiveType.value = navigator.connection.effectiveType switch (effectiveType.value) { case ('slow-2g'): case '2g': imagesQuality.value = 1 break case '3g': imagesQuality.value = 20 break case '4g': imagesQuality.value = 90 break default: imagesQuality.value = 90 break } }
И добавляем return с imagesQuality, чтобы получить доступ к этой переменной в компонентах. В итоге получаем такой код:
import { ref } from 'vue' export function handleNetwork() { const imagesQuality = ref(90) const effectiveType = ref(undefined) function updateNetworkState() { if (!navigator || !('connection' in navigator)) return effectiveType.value = navigator.connection.effectiveType switch (effectiveType.value) { case ('slow-2g'): case '2g': imagesQuality.value = 1 break case '3g': imagesQuality.value = 20 break case '4g': imagesQuality.value = 90 break default: imagesQuality.value = 90 break } } if (navigator && 'connection' in navigator) { navigator.connection.addEventListener('change', updateNetworkState) } updateNetworkState() return { imagesQuality: imagesQuality.value, } }
Меняем качество изображений
Возвращаемся к компоненту AppImage и импортируем туда наш сервис. Здесь мы сделаем следующее:
1) Передадим переменную imagesQuality как новый параметр функции getImageById
2) Если imagesQuality меньше 20, то оставляем атрибут srcset с 2х изображением пустым, т.к. при медленном интернете нет смысла загружать картинки более высокого разрешения
<script lang="ts" setup> ... import { handleNetwork } from '@/services/network' ... const srcAttrs = computed(() => { const { imagesQuality } = handleNetwork() ... return { src: getImageById(..., imagesQuality), srcset: imagesQuality > 20 ? `${..., imagesQuality)} 2x` : '', } }) </script>
Обновленная функция getImageById с параметром quality:
const getImageById = ( id: string, width: number, height: number, quality?: number, ... ): string => { ... return `${BASE_URL}/${id}${width ? `?tr=q-${quality},w-${width}` : ''}${width && height ? `,h-${height}` : ''}` }
Готово! Теперь в зависимости от качества сети у нас будут подгружаться картинки разного качества. Убедиться в этом можно переключая эти значения во вкладке Network инструментов разработчика:

Оптимизируем background-image
Следующая возможность для оптимизации это отключение фоновых изображений, которые мы задаем через CSS, т.к. как правило фоновые изображения используются чисто в декоративных целях, не неся какой-то ценной информации - следовательно при медленном интернете ими можно пожертвовать и не загружать, используя вместо них заливку цветом.
Для этого допишем наш сервис, добавив туда 'флаг' isSlowConnection , который поможет нам определять, когда показывать или не показывать фоновые картинки:
const isSlowConnection = ref(false)
Далее дополним конструкцию switch, чтобы помимо качества картинок там менялось и значений нашей новой переменной:
switch (effectiveType.value) { case ('slow-2g'): case '2g': imagesQuality.value = 1 isSlowConnection.value = true break case '3g': imagesQuality.value = 20 isSlowConnection.value = true break case '4g': imagesQuality.value = 90 isSlowConnection.value = false break default: imagesQuality.value = 90 isSlowConnection.value = false break }
А ниже добавим переключение класса (назвать его можно как угодно, но лучше с каким-то особым префиксом, чтобы не столкнуться с конфликтом стилей) на body, с помощью которого мы сможем через CSS селектор определять нужные стили:
document.body.classList.toggle('cs-slow-connection', isSlowConnection.value)
Например, у нас есть фоновое изображение на странице соревнования. Напишем стили, чтобы при наличии класса cs-slow-connection вместо фоного изображения была просто заливка похожим цветом:
.event-header { &::before { background-image: url("/img/event-default-img.webp"); background-position: center; background-size: cover; } } body.cs-slow-connection { .event-header { &::before { background-color: #e4e4e4; background-image: none; } } }
Выглядеть это будет вот так:


И действительно, во втором случае у нас даже не будет запроса на загрузку изображения, что в какой-то степени облегчит и так тяжелую работу для медленного соединения.
Другие возможности
Я показал всего два примера использования Network Information API для оптимизации изображений, но возможности использования намного больше - можно загружать плейсхолдеры вместо видео, можно вообще не загружать изображения на 2g, можно попробовать не загружать какие-то неосновные шрифты, можно даже убирать или подменять целые компоненты... и так далее.
Но надеюсь, что хотя бы этими примерами вызвал интерес к этому API и обратил внимание на проблемы с плохим интернетом.
