За един по-добър CSS, част 2

Jun 20, 2017 20:01 · 1380 words · 7 minute read

В първата част ви позанимах с някои основополагащи идеи и конвенции за по-ефективно използване на CSS.

Това бяха все неща, които според мен носят голяма доза подобрение в ефективността на разработка и поддръжката на един front-end.

Всеки от нас, по една или друга причина, е започнал да пише код. В тези първи месеци на опознаването ни с машините, основната ни цел е била “да го накараме да работи”.

Функционалността е необходимо условие за една програма. Свиквайки да пишем работещ код, обаче, осъзнаваме, че коректността е само първата стъпка от създаването на добра система. Неща като леснота за четене, поддръжка и време за запознаване с кода, оказва се, са голяма част от работата ни.

There are only two hard things in Computer Science: cache invalidation and naming things. – Phil Karlton

Всеки от нас, когато е започвал да пише код, е смятал имената на функциите за маловажни. После осъзнаваме, че дали функцията ще се казва helper или calculateAverage има огромно значение за четимостта и поддръжката.

Да си поговорим за имена на CSS класове.

<div class="dialog">
  <h1 class="label-success">...</h1>

  <p class="text-small scrollable">...</p>

  <button class="btn btn-danger">...</button>
  <button class="btn btn-success">...</button>
</div>

Заради CSS фреймуърци като bootstrap сме свикнали да виждаме подобен на горния код.

Ако сте използвали такъв вероятно си представяте как изглежда диалогът. Той съдържа заглавие, някакъв малък текст и два бутона - един червен и един зелен. Ще си кажем - супер - значи имената са си свършили работата. Дори без да гледаме CSS кода разбираме как изглежда нещото.

Обаче, ако твърдим, че имената на класовете трябва да подсказват стиловете, тогава къде теглим чертата между red-button и red-button-with-black-text-and-dark-on-hover? Нали последното е най-описателно? Или пък започваме да пишем много малки класове като red-background, black-color и white-on-black-hover?

Очевидно, идеята на класовете не е да дублира CSS декларациите. Защото иначе каква е разликата между <div style="color: red;"></div> и <div class="red-text"></div>? Чисто синтактична. А всички можем да се съгласим, че inline стиловете миришат.

Идеята за CSS се заражда с осъзнаването, че кодът на една страница може концептуално да се раздели на съдържание и стил. HTML е отговорен за съдържанието, а CSS - за стила. Това разделение е интуитивно и носи много ползи - най-голямата от които е, че двете имат възможност да се развиват независимо. Горните имена прехвърлят отговорността за стилизирането от CSS към HTML.

Семантика + Антисемантика = 0

Каква е алтернативата тогава?

Семантични класове:

<div class="terms-of-service-dialog">
  <h1 class="welcome-message">...</h1>

  <p class="terms-of-service">...</p>

  <button class="decline">...</button>
  <button class="accept">...</button>
</div>

Имената вече описват не стила, а предназначението на елементите. “Но тези имена носят по-малко информация!”. Не. Просто гледаме на грешното място.

.welcome-message { color: green; }
.terms-of-service { font-size: 11px; overflow-y: auto; }
.decline { background-color: red; }
.accept { background-color: green; }

Имената вече носят по-голяма стойност, но в CSS кода. Тук те ни показват кои елементи стилизират. Още, когато се наложи да променим стиловете им, можем да го направим без да пипаме имената в HTML-а.

Цветя и рози

Всичко е цветя и рози в простия пример, но когато започнем да използваме само семантични имена, се появява проблем. Започваме да дублираме код.

/* terms-of-service.css */
.decline { background-color: red; }
.decline:hover { background-color: black; color: white; }

/* profile.css */
.delete-account { background-color: red; border-radius: 50%; }
.delete-account:hover { background-color: black; color: white; }

Какво можем да направим, ако в приложението често имаме червени бутони, които стават черни при hover?

Какво бихме направили в “истински” програмен код, за да се справим с дублирането? Бихме извадили общото в нова функция, която бихме извикали в оригиналните две. За съжаление, CSS стандартът не предвижда възможност за използване на едно CSS правило в друго. Това е и основната причина всички CSS фреймуърци да ви налагат несемантични класове.

За щастие, можем да се възползваме от препроцесори като SCSS:

/* _styles.scss */
@mixin danger-button {
  background-color: red;
  &amp;:hover { background-color: black; color: white; }
}

/* terms-of-service.scss */
@import 'styles';

.decline { @include danger-button; }

/* profile.css */
@import 'styles';

.delete-account { @include danger-button; border-radius: 50%; }

Разбира се, можем да използваме миксини и за функционални неща като “центрирай децата на този елемент”, “забрани селектирането” и т.н. Разликата между семантичните (свързани с приложението) и несемантичните (“utility” стилове) може да се подчертаве ако ги разделим в два файла (аз обикновено правя _styles.scss и _mixins.scss). Първите може да се очаква, че ще се променят, тъй като са свързани с приложението. Вторите, веднъж написани, нямат нужда от промяна. Получава се нещо като mini-framework, който изниква естествено.

Забележка: Не винаги дублирането на код е нещо лошо. Понякога еднаквите стилове са просто случайност и обединението им може да доведе до по-трудна поддръжка. В този пример може би е спорно дали decline TOS и delete account бутоните са достатъчно близки, че да се обединят така.

В случай, че още не сте убедени

В обобщение, бонусите от семантични класове са големи. Ще си позволя да извадя подсписък с причини от тази статия:

  • Правят HTML-а по-четим.
  • По-удобни са за responsive стилове. full-width ще е width: 100% винаги, докато welcome-message може да прилага различен стил при различни екрани.
  • По-лесно се намират стиловете, приложени върху конкретен елемент. Виждайки welcome-message знаем къде да намерим стила и дори можем да направим бързо търсене с текстовия редактор.
  • Бонус точки за изолацията - welcome-message се отнася само до този елемент. Ако го променим сме сигурни, че няма да се промени нищо друго в приложението.
  • Можем да ги използваме в end-to-end тестовете, за да селектираме елементи.
  • Премахват нуждата от задължителна промяна на HTML-а, когато искаме да променим стила.
  • По-лесни са за дебъгване. Всичко, което влияе на определен елемент е в едно или две CSS правила, не пръснато в няколко борещи се за надмощие.
  • Миксините са доста по-лесни за override-ване, защото не се налага да мислим за специфичност.

За десерт

Често по код ревюта изникват някои дребни “проблеми”, може би граничещи с издребняване. Някак не си заслужават собствен пост, затова ще ги оставя тук:

  • Излишните суфикси са излишни. Чести нарушители са -container и -wrapper. Всеки елемент може да съдържа друг - означава ли това, че всеки един клас трябва да завършва на -container? Дали не е по-добре, когато имаме елемент, ограждащ списък с потребители, да го кръстим users, вместо user-container? Разбира се - има изключения.

  • Когато използваме HTML елемент извън стандартните - браузърите, по подразбиране, му слагат display: inline. Тоест, всеки компонент, който създаваме с Angular (всички версии) е inline, освен ако не му кажем друго. Тъй като браузърите са умни това често не създава проблеми - просто в inline елемент слагаме block елементи и работи. Понякога, обаче, това е проблематично за layout-а. Най-дребният страничен ефект е когато отворим DOM inspector-а. Тогава виждаме някакъв елемент с 0 височина, вътре в който има други с по-голяма такава.

Затова не забравяйте да сложите display на всички custom елементи - спестява доза главоблъскане, когато се окаже проблем.

  • Разликата между div и button не е само в името и подразбиращия се стил. С JS можем закачим click handler и на двата вида елементи. Но ако елементът е button, това дава допълнителна информация на браузъра. В резултат, screen reader-ите ще знаят, че това е бутон и ще го опишат като такъв. Още, на touchscreen устройства, ако тапнете близо до бутона, браузърът най-вероятно ще се сети, че сте искали да кликнете на него. Вместо на близкия до него div.

Това са две причини, поради които е по-добре да използваме семантични елементи като button за кликваеми елементи, вместо просто div-ове. Същото се отнася и за heading-и (h1, h2 и т.н.).

  • Във връзка с предната точка - div и span имат различна семантика. Разликата е, че div е блоков елемент, докато span е inline такъв. Ако се наложи някъде да има display: block на span, то най-вероятно искате да използвате div. Същото важи и за div с display: inline. И в двата варианта работи, но когато видим span в HTML-а, очакваме да се държи като inline елемент.

  • Всички сме били в следната ситуация: Дават ни дизайн с бутони, изписани с главни букви. Казваме “ОК” и пишем <button>NEXT</button>. Мисията изпълнена. Пропуснахме само три “дребни” детайла:

  1. Главните букви ще бъдат прочетени от един screen reader като абревиатура. Тоест, ще произнесе всяка буква поотделно. Не искаме това.
  2. Рано или късно ще забравим да задържим shift за някой бутон.
  3. Чупим границата между стил и съдържание. Това, че текстът е с главни букви е част от неговия стил. Може да го искаме сега, но да се промени впоследствие.

text-transform: uppercase винаги е по-добра идея.