Нумо рефакторити. Семантика та легкість сприйняття HTML
У сьогоднішньому прикладі я хочу провести рефакторинг фрагмента HTML, а саме — верхнього меню сайту. Для чистоти прикладу я видалила повторювані елементи та трохи очистила нерелевантні поточному завданню атрибути.
Disclaimer. Приклади для рефакторингу я беру із реальних джерел (сайтів, open-source проєктів, статей), змінюючи назви сутностей або інших унікальних компонентів. Я не знаю і не можу знати контекст, умови або час написання коду, тому всі мої виправлення є моїм особистим суб’єктивним сприйняттям того, що можна змінити в коді. Тому мої правки не містять жодної негативної оцінки першоджерела чи його творців.
Першоджерело
<div class="nav-links">
<div class="UITabs -with-icons">
<a class="UITabs-tab" href="/map" data-tab="map">
<div class="UITabs-content">
<div class="UITabs-icon">
<svg
class="ico" width="20" height="20"
viewBox="0 0 20 20" fill="#9e9e9e"
xmlns="http://www.w3.org/2000/svg"
>
<path fill-rule="evenodd" clip-rule="evenodd" d="...">
</path>
</svg>
</div>
Map
</div>
</a> <a class="UITabs-tab" href="/discount" data-tab="discount">
<div class="UITabs-content">
<div class="UITabs-icon">
<svg
class="ico" width="20" height="20"
viewBox="0 0 20 20" fill="#9e9e9e"
xmlns="http://www.w3.org/2000/svg"
>
<path fill-rule="evenodd" clip-rule="evenodd" d="...">
</path>
</svg>
</div>
Discount
</div>
</a> <div class="UITabs-tab" data-tab="search">
<div class="UITabs-content">
<div class="UITabs-icon">
<svg
class="ico" fill="#9e9e9e" height="24"
viewBox="0 0 24 24" width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="..."></path>
<path d="M0 0h24v24H0z" fill="none"></path>
</svg>
</div>
Search
</div>
</div> <div class="UITabs-indicator" style="transform..."></div>
</div>
</div>
Перше, на що я звернула увагу, це велика кількість тегу <div>
. Нічого поганого в цьому тезі немає, але, як і все добре, він використовується занадто часто.
Наступне — кількість вкладень, які явно не несуть ніякого семантичного змісту. Глибокі вкладення роздувають розмір HTML-файлу, збільшують час парсингу CSS та загально погіршують читабельність.
Також — іконки, які знаходяться в HTML. Це теж не критично, але зберігання іконок залежить від стратегії кешування, наявності CDN, списку підтримуваних браузерів і т.д. У даному випадку, іконки не будуть завантажуватися окремо від HTML-файлу, а отже будуть відображені одразу, без стрибків контенту, але збільшать розмір файлу, та сповільнять його завантаження та парсинг. Очевидно, не буває однозначно правильного або неправильного рішення в цьому питанні, але з точки зору семантики, на мій погляд, іконки забруднюють код, вони важливі тільки з візуальної точки зору, на відміну від зображень. Візуальну частину сторінки краще винести в CSS.
Давайте підберемо інші семантично вірні теги, зменшимо кількість вкладень та очистимо від зайвих елементів цей код. Підемо по порядку.
Контейнер
Оскільки це меню, більш доцільним для контейнера буде тег <nav>
, який використовується для елементів навігації.
Тепер питання — навіщо нам два вкладених один в одного теги <div>
? Схоже, що .nav-links
містить стилі позиціонування щодо свого батьківського елемента, в той же час .UITabs
— справжній контейнер навігації. Я не бачу сенсу розділяти стилі на два різних теги, тому пропоную замінити їх на один тег <nav>
з двома класами.
<nav class="UITabs -with-icons nav-links">
<a .../> <a .../> <div .../>
<div .../>
</nav>
Пункти меню
Питань до тегів посилань немає, але знову присутня зайва вкладеність, клас .UITabs-content
. І знову це розділення тегів за їх стилями, але в цьому випадку, ймовірно, це спроба дотримуватись методології типу BEM. Немає сенсу відокремлювати стилі блоку .UITabs-tab
від його єдиного прямого нащадка – .UITabs-content
. Тим більше, в обох класах встановлюються відступи, перевизначаючи один одного. Як і в разі контейнера, пропоную об’єднати ці два теги в один.
<a class="UITabs-tab" href="/map" data-tab="map">
<div class="UITabs-icon">...</div>
Map
</a>
Крім посилань, пунктом меню є <div>, що невірно з семантичної точки зору, так як пункт меню за визначенням є елементом взаємодії користувача, тобто повинен бути або посиланням, або кнопкою. У нашому випадку це кнопка пошуку, отже так і запишемо.
<button class="UITabs-tab" data-tab="search">
<div class="UITabs-icon">...</div>
Search
</button>
Іконки
Як я вже писала вище, я пропоную винести іконки з HTML, їм краще місце в CSS, а з урахуванням того, що їх не повинно бути зовсім, вони будуть встановлені в псевдоелементі.
<style>
.UITabs-tab.-map-icon::before {
content: url(/map-icon.svg); //first option
}
.UITabs-tab.-discount-icon::before {
content: url(data:image/svg+xml; utf8, <svg>...</svg>); //second
}
...
</style><a class="UITabs-tab -map-icon" href="/map" data-tab="map">
Map
</a>
Інші елементи
Останній пункт, який викликає питання, є .UITabs-indicator
. Фактично, це підкреслення активного пункту меню, до якого застосовуються стилі за допомогою скрипту. Мені здається, це зайва ускладненість, враховуючи, що підкреслення можна легко реалізувати за допомогою властивості border
в CSS. Або, якщо форма підкреслення нестандартна, можна використовувати псевдоелемент ::after
або властивість box-shadow
для стилізації.
<style>
.UITabs-tab.active {
border-bottom: 4px solid currentColor;
}
</style>
Результат
<style>
.UITabs-tab.-map-icon::before {
content: url(/map-icon.svg);
}
.UITabs-tab.-discount-icon::before {
content: url(data:image/svg+xml; utf8, <svg>...</svg>);
}
.UITabs-tab.-search-icon::before {
content: url(data:image/svg+xml; utf8, <svg>...</svg>);
}
.UITabs-tab.active {
border-bottom: 3px solid currentColor;
}
...
</style><nav class="UITabs -with-icons nav-links">
<a class="UITabs-tab -map-icon" href="/map" data-tab="map">
Map
</a> <a class="UITabs-tab -discount-icon" href="/discount" data-tab="discount">
Discount
</a> <button class="UITabs-tab -search-icon" data-tab="search">
Search
</button>
</nav>
На мій погляд, такий HTML є більш зрозумілим та семантично правильним. До речі, я не використовувала <div>
, хоча це й не було моєю метою, але так часто виходить, якщо дотримуватися семантики.
Дякую за те, що дочитали!
Побачимось наступного разу! 🤹🏻♀️