← HTML & CSS/CSS для печати и доступности#44 из 383← ПредыдущийСледующий →+10 XP
Полезно по теме:Гайд: старт в frontendПрактика: DOM и событияТермин: DOMМаршрут: старт с нуля

CSS для печати и доступности

CSS — это не только экраны. Media queries позволяют адаптировать стили для принтера, пользователей с нарушениями зрения, высококонтрастных тем, уменьшенных анимаций.

@media print

@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;
  }
}

page-break-* и break-*

/* Современные свойства (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 — настройки страницы

@page {
  size: A4;
  margin: 2cm 1.5cm;
}

@page :first {
  margin-top: 3cm;
}

@page :left {
  margin-left: 2.5cm;
}

@page :right {
  margin-right: 2.5cm;
}

prefers-reduced-motion — уважаем вестибулярные нарушения

/* По умолчанию — анимации есть */
.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;
  }
}

prefers-color-scheme — системная тема

:root {
  --bg: #ffffff;
  --text: #1a202c;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #1a202c;
    --text: #f7fafc;
  }
}

forced-colors — высококонтрастный режим Windows

@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;
  }
}

prefers-contrast

@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 для печати и доступности

CSS — это не только экраны. Media queries позволяют адаптировать стили для принтера, пользователей с нарушениями зрения, высококонтрастных тем, уменьшенных анимаций.

@media print

@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;
  }
}

page-break-* и break-*

/* Современные свойства (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 — настройки страницы

@page {
  size: A4;
  margin: 2cm 1.5cm;
}

@page :first {
  margin-top: 3cm;
}

@page :left {
  margin-left: 2.5cm;
}

@page :right {
  margin-right: 2.5cm;
}

prefers-reduced-motion — уважаем вестибулярные нарушения

/* По умолчанию — анимации есть */
.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;
  }
}

prefers-color-scheme — системная тема

:root {
  --bg: #ffffff;
  --text: #1a202c;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #1a202c;
    --text: #f7fafc;
  }
}

forced-colors — высококонтрастный режим Windows

@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;
  }
}

prefers-contrast

@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` предотвращает разрыв блока при печати.

Загружаем среду выполнения...
Загружаем AI-помощника...