Към съдържанието
wannacode

Responsive дизайн

Накратко: Responsive дизайнът прави страницата удобна на всеки екран: метатаг viewport, mobile first и медийни заявки, които сменят подредбата според ширината.

Проблем

Бутонът е извън екрана

Страницата на продукта е направена на монитор и там изглежда добре. На телефон купувач пише: „Не мога да поръчам, няма бутон“. Ето я на ширината на телефон, 360px:

HTML, CSS, JS

Бутонът „Поръчай“ е вдясно, извън екрана. Страницата се плъзга встрани, а повечето хора не се сещат да я плъзнат. Кодът е без грешка: снимката е 400px, колоната с текста е 360px и са една до друга. На 1000px широк монитор това се побира. На 360px не.

Идеята

Екраните са с различна ширина

Една и съща страница се отваря на телефон (360–430px), на таблет (около 800px) и на компютър (1000px и повече). Страница, която се нагажда към всяка ширина, се казва адаптивна (responsive).

Първата стъпка е в <head>:

<meta name="viewport" content="width=device-width, initial-scale=1">

Без нея телефонът се преструва на монитор: подрежда страницата на 980px и я смалява, докато се побере. Текстът става ситен, а CSS-ът ти никога не вижда истинските 360px. С нея ширината на страницата е ширината на екрана. В пясъчника този ред го слагаме ние, но в твоите страници го пишеш ти.

Втората стъпка е редът, в който пишеш CSS-а. Mobile first значи: основните правила са за най-тесния екран. Там всичко е едно под друго, а това почти винаги се побира. После за по-широките екрани добавяш промени с медийна заявка (media query):

.product {
display: grid;
gap: 16px;
}
@media (min-width: 700px) {
.product {
grid-template-columns: 1fr 1fr;
}
}

@media (min-width: 700px) е условие: „ширината е поне 700px“. Правилата вътре важат само когато условието е вярно. Иначе браузърът ги прескача.

Третата стъпка са картинките. width="400" е хубаво за HTML-а: браузърът пази място, докато картинката се зарежда. На тесен екран обаче ѝ трябва таван:

img {
max-width: 100%;
height: auto;
}

max-width: 100% значи „не по-широка от кутията си“, а height: auto пази пропорциите, за да не се сплеска.

Виж го

Виж как се включват заявките

Прозорецът става по-широк от 360px до 1000px. Гледай линийката горе и редовете в кода.

body { margin: 0; padding: 16px; }
.cards {
display: grid;
grid-template-columns: 1fr;
gap: 16px;
}
@media (min-width: 600px) {
.cards { grid-template-columns: 1fr 1fr; }
}
@media (min-width: 900px) {
.cards { grid-template-columns: repeat(4, 1fr); }
}
Стъпка по стъпка, с думи
  1. Телефон, 360px. Основното правило е за най-тесния екран: една колона. Картата е 360 − 2 × 16 = 328px.
  2. Прозорецът става по-широк и колоната расте с него. При 599px условието min-width: 600px още е невярно и браузърът прескача правилата в него.
  3. При 600px условието е вярно. Правилото в него е след основното и печели: две колони по 276px. Картите се пренареждат.
  4. Още по-широко: двете колони растат до 425,5px. min-width: 900px още е невярно.
  5. При 900px и двете заявки са верни. Задават едно и също свойство, затова печели по-късната: четири колони по 205px.
  6. При 1000px не се включва нищо ново. Четирите колони растат до 230px.
  7. Браузърът проверява заявките наново при всяка смяна на ширината: обърнат телефон, по-тесен прозорец. Стесниш ли го, колоните се връщат по същия път.

Медийната заявка не е изчислена веднъж. Браузърът я проверява при всяка смяна на ширината и пренарежда страницата веднага. Двете заявки задават едно и също свойство със същата специфичност, затова при 900px печели по-късната. Това е каскадата, която вече познаваш.

Пробвай

Поправи го за телефон

Страницата е на 360px. Направи я mobile first: снимката и текстът да са един под друг и нищо да не излиза встрани. Снимката да не е по-широка от кутията си. От 700px нагоре снимката и текстът да са в две колони. Проверката гледа за хоризонтален скрол и на колко реда са снимката и колоната с текста.

HTML, CSS, JS

Пробвай

Сега за компютър

Тази страница вече е за телефон: всичко е в една колона. Тук ширината е 1000px. Добави медийна заявка, с която от 700px нагоре снимката и текстът са в две равни колони.

HTML, CSS, JS

Пробвай

Намери грешката

На компютър колоните са две, но страницата стои в една. Защо?

Избери реда с грешката.

Пробвай

Попълни

Каква дума трябва, за да важат двете колони от 700px нагоре?

@media (: 700px) {
  .product {
    grid-template-columns: 1fr 1fr;
  }
}

Идеята

Цветът на едно място

Зеленото на магазина е в CSS-а на четири места: цената, връзките, бутона и рамката. Магазинът сменя цвета си и някой трябва да ги намери всичките. Пропусне ли едно, страницата е на две бои.

CSS променливата (custom property) е стойност с име. Името започва с две тирета, а var() я чете:

:root {
--accent: #1a7f37;
}
button {
background: var(--accent);
}

:root е най-горният елемент, <html>. Променливите се наследяват като color, затова това, което е в :root, се вижда навсякъде в страницата. Цветът се сменя на един ред.

Същото дава тъмна тема. @media (prefers-color-scheme: dark) е вярно, когато човек е избрал тъмна тема на телефона или на компютъра. Вътре сменяш само стойностите на променливите. Правилата, които ги ползват, остават същите.

Виж го

Светла и тъмна

HTML, CSS, JS

Пясъчникът следва темата на браузъра ти, а не бутона за тема на този сайт. Смени темата на системата на тъмна и натисни „Изпълни“: фонът, текстът и зеленото се сменят, а CSS правилата за .card, .price и button са същите. Зеленото на тъмно е по-светло, за да се чете върху тъмния фон.

Пробвай

Нов цвят на магазина

Магазинът сменя зеленото #1a7f37 с оранжево #c2410c. Направи променлива --accent в :root със стойност #c2410c и я ползвай на четирите места, където сега е зеленото.

HTML, CSS, JS

Идеята

Състояния: посочен, на фокус, изключен

Бутонът не е само един вид. Той може да е посочен с мишката, да е на фокус от клавиатурата или да е изключен. CSS ги хваща с псевдокласове: дума с двоеточие след селектора.

button:hover {
background: #116329;
}
button:focus-visible {
outline: 3px solid var(--accent);
outline-offset: 2px;
}
button:disabled {
background: #9ca3af;
cursor: not-allowed;
}
  • :hover: мишката е върху елемента. На телефона мишка няма, затова важна информация не се крие зад :hover.
  • :focus-visible: елементът е на фокус и браузърът преценява, че фокусът трябва да се вижда. От клавиатурата винаги се вижда. При клик с мишката на бутон обикновено не се вижда. Затова outline: none на :focus е грешка: човек с клавиатура губи къде е.
  • :disabled: елементът е с атрибута disabled. Бутонът „Изчерпано“ трябва да изглежда изключен, не само да е.

И размерът. На телефон се натиска с пръст, а той е по-неточен от мишката. Бутон с височина 24px се изпуска. Дай на бутоните поне 44px височина: min-height: 44px.

Пробвай

Бутоните на телефона

Колега е махнал контура на фокуса, а бутоните са ниски. Направи така: бутоните са поне 44px високи (min-height); при фокус от клавиатурата се вижда контур 3px solid в цвета --accent; изключеният бутон „Изчерпано“ е сив (#9ca3af) с курсор not-allowed. Проверката натиска Tab и гледа бутона „Поръчай“.

HTML, CSS, JS
По-точно, за любопитните

Viewport по-точно. Без метатага мобилните браузъри подреждат страницата на 980px и я смаляват. В Chromium с емулиран телефон 360px innerWidth е 980, а @media (min-width: 700px) е вярно: телефонът получава подредбата за компютър. initial-scale=1 казва „без начално смаляване“. Не забранявай мащабирането (user-scalable=no, maximum-scale=1): хората със слабо зрение увеличават страницата с два пръста.

Точките на прекъсване. 600, 700, 900 не са свещени числа. Слагаш точка там, където подредбата ти започва да изглежда зле, а не по модела на някой телефон. Някои пишат заявките в em (min-width: 40em): тогава те следват и размера на шрифта, който човек е избрал.

Заявки не само за ширина. (hover: hover) е вярно, когато основното устройство за посочване може да посочва (мишка), а (pointer: coarse) е вярно при пръст. (prefers-reduced-motion: reduce) казва, че човек не иска движение: махни анимациите. (orientation: landscape) е за хоризонтален екран.

Променливите по-точно. var(--accent, #1a7f37) има резервна стойност, ако променливата я няма. Променлива може да се смени за част от страницата: .sale { --accent: #c2410c; } и всичко в .sale е оранжево. Стойността се проверява чак когато се ползва: --accent: 12px е позволено, но color: var(--accent) тогава е невалидно и цветът става наследения. Свойството color-scheme: light dark казва на браузъра да оцвети и своите части (полета, скрол) в тъмната тема.

Размер за пръста. WCAG 2.2 (критерий 2.5.8) иска цел поне 24 × 24px или достатъчно място около нея. Apple препоръчва 44 × 44pt, а Material Design 48 × 48dp.

Упражнение

Каталогът на телефон

Каталогът е направен за компютър: четири карти по 220px в един ред. Тук ширината е 360px. Направи го за телефон: картите в две колони, без хоризонтален скрол, картинките не по-широки от картата си, бутоните „Купи“ поне 44px високи (min-height). Зеленото #1a7f37 сложи в променлива --accent в :root и бутоните да я ползват. Ако искаш, добави и четири колони от 700px нагоре.

HTML, CSS, JS

Тест

Провери се

1. Пишеш mobile first. Кое правило е без медийна заявка?
2. @media (min-width: 600px) при прозорец 599px и при 600px?
3. В :root има --accent: #c2410c, а бутонът е с background: var(accent). Какъв цвят е бутонът?
4. Защо :focus-visible, а не :hover, за контура на бутона?
5. Колко голям да е бутонът на телефон?

Създаден от Иво Гергов като част от кампанията на Evo Tech за достъпно обучение по програмиране ·