CSS — это не только экраны. Media queries позволяют адаптировать стили для принтера, пользователей с нарушениями зрения, высококонтрастных тем, уменьшенных анимаций.
@media print {
/* Скрываем ненужное при печати */
nav, .sidebar, .ads, .cookie-banner, button {
display: none !important;
}
/* Оптимизируем типографику */
body {
font-size: 12pt;
color: black;
background: white;
}
/* Ссылки — показываем URL */
a::after {
content: " (" attr(href) ")";
}
/* Избегаем разрыва внутри элементов */
figure, table, pre {
break-inside: avoid;
}
/* Заголовки не отрываются от контента */
h1, h2, h3 {
break-after: avoid;
}
}/* Современные свойства (CSS Fragmentation) */
.chapter {
break-before: page; /* Начать на новой странице */
break-after: page; /* После — новая страница */
break-inside: avoid; /* Не разрывать внутри */
}
/* Устаревшие, но широко поддерживаемые */
.page-break {
page-break-before: always;
page-break-after: always;
page-break-inside: avoid;
}@page {
size: A4;
margin: 2cm 1.5cm;
}
@page :first {
margin-top: 3cm;
}
@page :left {
margin-left: 2.5cm;
}
@page :right {
margin-right: 2.5cm;
}/* По умолчанию — анимации есть */
.button {
transition: transform 0.3s, opacity 0.3s;
}
.spinner {
animation: spin 1s linear infinite;
}
/* Если пользователь просит меньше движения */
@media (prefers-reduced-motion: reduce) {
.button {
transition: none;
}
.spinner {
animation: none;
/* Или заменить на более простую: */
opacity: 0.6;
}
/* Универсальное правило */
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}:root {
--bg: #ffffff;
--text: #1a202c;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #1a202c;
--text: #f7fafc;
}
}@media (forced-colors: active) {
/* Windows High Contrast Mode */
.button {
border: 2px solid ButtonText; /* Системный цвет */
color: ButtonText;
background: ButtonFace;
}
/* Убираем box-shadow (не виден в forced-colors) */
* {
box-shadow: none !important;
text-shadow: none !important;
}
}/* Canvas, CanvasText, ButtonFace, ButtonText, Highlight, HighlightText */
@media (forced-colors: active) {
.selected-item {
background: Highlight;
color: HighlightText;
}
}@media (prefers-contrast: more) {
/* Пользователь хочет больше контраста */
body {
--border-color: #000000;
--text-secondary: #333333;
}
}Определение медиа-предпочтений пользователя и применение адаптивных стилей
// Определяем предпочтения пользователя через matchMedia
function getMediaPreferences() {
const queries = {
darkMode: '(prefers-color-scheme: dark)',
reducedMotion: '(prefers-reduced-motion: reduce)',
highContrast: '(forced-colors: active)',
print: 'print',
moreContrast: '(prefers-contrast: more)',
}
const prefs = {}
for (const [key, query] of Object.entries(queries)) {
prefs[key] = window.matchMedia(query).matches
}
return prefs
}
// Применяем адаптации к элементу
function applyAdaptations(element, prefs) {
// Устанавливаем data-атрибуты для CSS-хуков
element.dataset.theme = prefs.darkMode ? 'dark' : 'light'
element.dataset.motion = prefs.reducedMotion ? 'reduced' : 'full'
element.dataset.contrast = prefs.highContrast || prefs.moreContrast ? 'high' : 'normal'
// Применяем стили напрямую
if (prefs.darkMode) {
element.style.background = '#1a202c'
element.style.color = '#f7fafc'
} else {
element.style.background = '#ffffff'
element.style.color = '#1a202c'
}
if (prefs.reducedMotion) {
element.style.transition = 'none'
element.style.animation = 'none'
}
if (prefs.highContrast) {
element.style.border = '2px solid currentColor'
element.style.outline = '2px solid currentColor'
}
}
// Демонстрация
const prefs = getMediaPreferences()
console.log('Предпочтения пользователя:', prefs)
const demo = document.createElement('div')
demo.style.cssText = `
padding: 16px;
border-radius: 8px;
font-family: sans-serif;
transition: all 0.3s;
margin: 8px;
`
demo.textContent = 'Адаптивный элемент'
document.body.appendChild(demo)
applyAdaptations(demo, prefs)
console.log('data-theme:', demo.dataset.theme)
console.log('data-motion:', demo.dataset.motion)
console.log('data-contrast:', demo.dataset.contrast)
// Подписываемся на изменение предпочтений
const darkMQ = window.matchMedia('(prefers-color-scheme: dark)')
darkMQ.addEventListener('change', (e) => {
console.log('Тема изменилась на:', e.matches ? 'dark' : 'light')
applyAdaptations(demo, { ...prefs, darkMode: e.matches })
})
// Подписываемся на reduced-motion
const motionMQ = window.matchMedia('(prefers-reduced-motion: reduce)')
motionMQ.addEventListener('change', (e) => {
console.log('Reduced motion:', e.matches)
if (e.matches) {
demo.style.transition = 'none'
}
})
console.log('Попробуй изменить системную тему или настройки анимации')CSS — это не только экраны. Media queries позволяют адаптировать стили для принтера, пользователей с нарушениями зрения, высококонтрастных тем, уменьшенных анимаций.
@media print {
/* Скрываем ненужное при печати */
nav, .sidebar, .ads, .cookie-banner, button {
display: none !important;
}
/* Оптимизируем типографику */
body {
font-size: 12pt;
color: black;
background: white;
}
/* Ссылки — показываем URL */
a::after {
content: " (" attr(href) ")";
}
/* Избегаем разрыва внутри элементов */
figure, table, pre {
break-inside: avoid;
}
/* Заголовки не отрываются от контента */
h1, h2, h3 {
break-after: avoid;
}
}/* Современные свойства (CSS Fragmentation) */
.chapter {
break-before: page; /* Начать на новой странице */
break-after: page; /* После — новая страница */
break-inside: avoid; /* Не разрывать внутри */
}
/* Устаревшие, но широко поддерживаемые */
.page-break {
page-break-before: always;
page-break-after: always;
page-break-inside: avoid;
}@page {
size: A4;
margin: 2cm 1.5cm;
}
@page :first {
margin-top: 3cm;
}
@page :left {
margin-left: 2.5cm;
}
@page :right {
margin-right: 2.5cm;
}/* По умолчанию — анимации есть */
.button {
transition: transform 0.3s, opacity 0.3s;
}
.spinner {
animation: spin 1s linear infinite;
}
/* Если пользователь просит меньше движения */
@media (prefers-reduced-motion: reduce) {
.button {
transition: none;
}
.spinner {
animation: none;
/* Или заменить на более простую: */
opacity: 0.6;
}
/* Универсальное правило */
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}:root {
--bg: #ffffff;
--text: #1a202c;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #1a202c;
--text: #f7fafc;
}
}@media (forced-colors: active) {
/* Windows High Contrast Mode */
.button {
border: 2px solid ButtonText; /* Системный цвет */
color: ButtonText;
background: ButtonFace;
}
/* Убираем box-shadow (не виден в forced-colors) */
* {
box-shadow: none !important;
text-shadow: none !important;
}
}/* Canvas, CanvasText, ButtonFace, ButtonText, Highlight, HighlightText */
@media (forced-colors: active) {
.selected-item {
background: Highlight;
color: HighlightText;
}
}@media (prefers-contrast: more) {
/* Пользователь хочет больше контраста */
body {
--border-color: #000000;
--text-secondary: #333333;
}
}Определение медиа-предпочтений пользователя и применение адаптивных стилей
// Определяем предпочтения пользователя через matchMedia
function getMediaPreferences() {
const queries = {
darkMode: '(prefers-color-scheme: dark)',
reducedMotion: '(prefers-reduced-motion: reduce)',
highContrast: '(forced-colors: active)',
print: 'print',
moreContrast: '(prefers-contrast: more)',
}
const prefs = {}
for (const [key, query] of Object.entries(queries)) {
prefs[key] = window.matchMedia(query).matches
}
return prefs
}
// Применяем адаптации к элементу
function applyAdaptations(element, prefs) {
// Устанавливаем data-атрибуты для CSS-хуков
element.dataset.theme = prefs.darkMode ? 'dark' : 'light'
element.dataset.motion = prefs.reducedMotion ? 'reduced' : 'full'
element.dataset.contrast = prefs.highContrast || prefs.moreContrast ? 'high' : 'normal'
// Применяем стили напрямую
if (prefs.darkMode) {
element.style.background = '#1a202c'
element.style.color = '#f7fafc'
} else {
element.style.background = '#ffffff'
element.style.color = '#1a202c'
}
if (prefs.reducedMotion) {
element.style.transition = 'none'
element.style.animation = 'none'
}
if (prefs.highContrast) {
element.style.border = '2px solid currentColor'
element.style.outline = '2px solid currentColor'
}
}
// Демонстрация
const prefs = getMediaPreferences()
console.log('Предпочтения пользователя:', prefs)
const demo = document.createElement('div')
demo.style.cssText = `
padding: 16px;
border-radius: 8px;
font-family: sans-serif;
transition: all 0.3s;
margin: 8px;
`
demo.textContent = 'Адаптивный элемент'
document.body.appendChild(demo)
applyAdaptations(demo, prefs)
console.log('data-theme:', demo.dataset.theme)
console.log('data-motion:', demo.dataset.motion)
console.log('data-contrast:', demo.dataset.contrast)
// Подписываемся на изменение предпочтений
const darkMQ = window.matchMedia('(prefers-color-scheme: dark)')
darkMQ.addEventListener('change', (e) => {
console.log('Тема изменилась на:', e.matches ? 'dark' : 'light')
applyAdaptations(demo, { ...prefs, darkMode: e.matches })
})
// Подписываемся на reduced-motion
const motionMQ = window.matchMedia('(prefers-reduced-motion: reduce)')
motionMQ.addEventListener('change', (e) => {
console.log('Reduced motion:', e.matches)
if (e.matches) {
demo.style.transition = 'none'
}
})
console.log('Попробуй изменить системную тему или настройки анимации')Создай страницу статьи с полноценными стилями для печати. В `@media print` скрой навигацию и кнопки, покажи URL ссылок через `::after`, задай `break-inside: avoid` для блоков кода. Добавь поддержку `prefers-color-scheme: dark` для тёмной темы и `prefers-reduced-motion: reduce` для отключения анимаций.
Тёмная тема: `--bg: #1a202c`, `--text: #f7fafc`, `--border: #4a5568`. `prefers-reduced-motion`: `transition: none`, `animation-duration: 0.01ms`. В `@media print`: скрывай `.nav, .btn` через `display: none`, задавай `font-size: 12pt`, `color: black`, `background: white`. `break-inside: avoid` предотвращает разрыв блока при печати.