Оптимизация изображений для пользователей с медленным интернетом с помощью Network Information API

от автора

Многие из нас привыкли к тому, что быстрый и стабильный интернет это данность в 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;     }   } }

Выглядеть это будет вот так:

4g

4g
slow 3g

slow 3g

И действительно, во втором случае у нас даже не будет запроса на загрузку изображения, что в какой-то степени облегчит и так тяжелую работу для медленного соединения.

Другие возможности

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

Но надеюсь, что хотя бы этими примерами вызвал к этому интерес и обратил внимание на проблемы с плохим интернетом.


ссылка на оригинал статьи https://habr.com/ru/articles/748272/