При работе с адаптивной версткой часто возникает необходимость использовать одни и те же breakpoints как в CSS, так и в JavaScript. Хранить breakpoints отдельно в JavaScript и отдельно в CSS это далеко не лучший подход. Причина в тому, что это приводит к дублированию: breakpoints прописываются одновременно в CSS и JS, создавая две точки входа. Если breakpoints изменятся в одном месте (например в CSS), в другом месте (JavaScript) их могут забыть обновить.

Чтобы избежать таких рисков, мы будем использовать единую точку входа, где breakpoints определяются в одном месте в СSS(SCSS) стилях, и затем передаются в JavaScript в виде объекта.

Определяем breakpoints в SCSS

В SCSS создаём map с breakpoints. Map в СSS это подобие ассоциативного массива в Javascript, простыми словами объект с ключами и значениями

$grid-breakpoints: (   
	xs: 0,   
	sm: 576px,   
	md: 768px,   
	lg: 992px,   
	xl: 1200px,  
	xxl: 1400px 
);

Передаём breakpoints в CSS через псевдоэлемент

Для передачи данных в JS мы воспользуемся ::before и встроим строку с breakpoints в content. Но для начала нам надо преобразовать map в строку в строку. Для этого напишем SCSS-функцию для преобразования map

@function map-to-string($map) {   
	$result: "";   
	@each $key, $value in $map {     
		$result: "#{$result}#{$key}: #{$value}, ";   
	}   
	@return $result; 
}

Затем применим её к body::before:

body {   
	&::before {     
		content: map-to-string($grid-breakpoints);     
		display: none;   
	}
}

Извлекаем строку из СSS в JavaScript

В JS мы можем получить значение content из псевдо-элемента ::before с помощью js метода getComputedStyle и записать в переменную.

const rawBreakpoints = getComputedStyle(document.body, '::before').getPropertyValue('content').replace(/\"/g, '').trim();

Преобразуем строку с breakpoints в объект

Что бы мы могли удобно взаимодествовать с нашими breakpoints в JS и надо преобразовать в объект. Парсим строку и превращаем её в объект:

const breakpointsArray = rawBreakpoints.split(', ').map(item => item.split(': '));  
const breakpointsObject = Object.fromEntries(breakpointsArray);

Теперь переменная breakpointsObject содержит объект со всеми breakpoints из SCSS:

console.log(breakpointsObject); // { xs: "0", sm: "576px", md: "768px", lg: "992px", xl: "1200px", xxl: "1400px" }

Используем matchMedia для отслеживания, соответствует ли ширина экрана нашим breakpoints

Использовать свойство matchMedia и передаем в него нужные нам breakpoints из объекта breakpointsObj. Отслеживаем изменения медиазапроса: когда ширина экрана начинает соответствовать определённому breakpoint, запускается callback-функция с нужной для нас JavaScript-логикой для определенного breakpoint.
В нашем примере эту логику содержит функция handleMinLg. А применяется эта функция когда медиазапрос соответствует значению записаного в breakpointsObject.lg. В данном случае это минимальная ширина 992px

const breakpointMinLg = window.matchMedia(`(min-width: ${breakpointsObject.lg})`);  
  
handleMinLg(breakpointMinLg); // запускаем при первой загрузке страницы
breakpointMinLg.addEventListener('change', handleMinLg); 
  
function handleMinLg (e) { 
	// если возвращается true, значит ширина экрана соответствует заданному медиа-запросу
    if (e.matches) {  	
		console.log('Ширина экрана больше или равна 992px');
    }  
}

Резюмирую

С помощью этого подхода:

  • Мы избегаем дублирование breakpoints между CSS и JavaScript;
  • Поддержка становится проще и безопаснее;
  • У нас единая точка входа для breakpoints, которые используестя как в СSS так и JavaScript.

Это чистое и устойчивое решение для синхронизации адаптивных точек между СSS и JavaScript.