Практика

Настало время практики. попробуем сверстать простенький сайт, который, возможно, послужит вам первым резюме. Сразу оговорюсь по поводу картинок — в этом примере используется генерация людей нейросетью, этих людей никогда не существовало. Если вы увидите что-то необычное или пугающее, просто обновите страницу, и, возможно, в этот раз нейросеть сгенерирует что-то более человечное. Описание на русском.

Вы можете изменять размер вьюпорта (мобильный вид / планшетный / ноутбук / монитор), потянув за правый нижний угол или используя переключатели сверху. По итогу, должно получиться примерно так:

Разметка

Итак, приступим к вёрстке. Первым делом необходимо создать HTML-разметку. И сразу же подключим необходимые нам шрифты с Google Fonts. Мы будем использовать шрифт Open Sans. Кроме того, мы будем использовать иконки из пака Font awesome. Ну и сразу же создадим и подключим файл стилей. Назовём его, к примеру,style.css.

<!DOCTYPE html>
<html lang="ru">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Портфолио</title>
    <link rel="stylesheet" href="styles.css" />

    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,600&display=swap&subset=cyrillic" />
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.10.2/css/all.min.css" />
  </head>
  <body>

  </body>
</html>

Возможно, у вас возникли некоторые вопросы по нашей отправной точке. Постараюсь ответить на них последовательно.<html lang="ru"> в этой строке мы указываем язык страницы. <meta charset="UTF-8" /> означает, что кодировка страницы UTF-8. <meta name="viewport" content="width=device-width, initial-scale=1.0" /> указывает, что сайт является адаптивным и самостоятельно подстроится под дисплей любого размера.

Сразу подумаем, как должен выглядеть будущий сайт как на мобильных устройствах, так и на устройствах с большим дисплеем. Судя по макету, максимальная ширина сайта — 1200px, а если дисплей шире, то сайт выстраивается посередине. Самый простой способ сделать так — использовать какой-то внешний контейнер. Внутри этого контейнера будет блок с краткой информацией о человеке и блок с основным контентом.

<body>
  <main class="container">
    <div class="info">...</div>
    <div class="content">...</div>
  </main>
</body>

Теперь посмотрим, что у нас есть в блоке с информацией (.info). Там есть фото, его имя, и три пары заголовок-контент. Попробуем это воссоздать.

<div class="info">
  <div class="photo">
    <img ... />
  </div>
  <h1>...</h1>
  <section>
    <h2>...</h2>
    ...
  </section>
  <section>
    <h2>...</h2>
    ...
  </section>
  <section>
    <h2>...</h2>
    ...
  </section>
</div>

Блок с основным контентом представляет собой четыре пары заголовок-список. Так и напишем в коде:

<div class="content">
  <section>
    <h2>...</h2>
    <ul>...</ul>
  </section>
  <section>
    <h2>...</h2>
    <ul>...</ul>
  </section>
  <section>
    <h2>...</h2>
    <ul>...</ul>
  </section>
  <section>
    <h2>...</h2>
    <ul>...</ul>
  </section>
</div>

Начальная стилизация

Приступим, наконец, к стилям. Прежде всего, нужно определиться с цветами, шрифтами и прочим. Используем CSS-переменные и занесём туда все цвета макета, примем border-box в качестве правила хорошего тона (об этом читай в статье Блочная модель и box-sizing), установим для странички основным шрифтом Open Sans, кегль шрифта — 16px (берём за основу кегль основного текста в макете), цвет шрифта сразу же установим согласно цвету основного текста в макете. Также обнулим значение margin по умолчанию для body.

:root {
  --back: #404042;
  --accent: #e61d31;
  --middle: #bbb;
  --light: #f2f2f2;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: 'Open Sans', sans-serif;
  font-size: 16px;
  color: var(--back);
}
  

Как мы и говорили раньше, максимальный размер контейнера — 1200px. Блоку с информацией присвоим тёмный фон, согласно макету, светлый цвет текста, внутренний отступ (можете подобрать любое значение, лишь бы потом не забыть сделать как по макету). Блоку с основным контентом тоже присвоим какой-то внутренний отступ и белый фон.

.container {
  max-width: 1200px;
}

.info {
  background-color: var(--back);
  color: var(--light);
  padding: 1rem;
}

.content {
  background-color: white;
  padding: 1rem;
}
  

И сразу же зададим стили для абзацев и ссылок:

p {
  font-size: 1rem;
  margin: 0 0 0.5em;
}

a {
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

a .fab,
a .fas {
  margin-right: 0.5em;
}

Вёрстка блоков

Существует два основных подхода к вёрстке адаптивных (подстраивающихся под устройство) сайтов: mobile-first и desktop-first. Первый считается общепризнанным стандартом, так как позволяет, теоретически, упростить вычисление CSS для мобильных устройств. Дело в том, что мобильные телефоны считаются более слабыми по сравнению с компьютерами. В случае, когда мы используем подход mobile-first, сайт изначально выглядит так, как нужно мобильным устройствам, а если ширина экрана это позволяет, подключает дополнительные CSS-правила, позволяющие расположить элементы, используя большую ширину экрана, нежели у телефона. И так, постепенно наращивая количество правил, включающихся по мере роста ширины экрана, раскладка становится подходящей для широких экранов компьютера. В подходе desktop-first происходит наоборот — сначала сайт ведет себя как сайт для полноценных компьютеров, а если ширины экрана недостаточно, подключает стили, изменяющие раскладку элементов на раскладку, подходящую для мобильных устройств. От этой дополнительной вычислительной нагрузки на мобильных устройствах принято отказываться по двум причинам:

  1. как было сказано выше, мобильные устройства не так производительны;
  2. более половины пользователей интернета используют, в первую очередь, мобильные устройства.

Именно поэтому мы будем сначала верстать под мобильные устройства, а затем постепенно увеличивать экран.

Блок с информацией

Как видно на макете, цвета ссылок, а так же толщина шрифта ссылок и абзацев в блоке с тёмным фоном отличаются от этих параметров в блоке с основным контентом. Добавим для этого дополнительные стили:

.info a {
  color: var(--light);
  font-weight: 300;
}

.info h2 {
  font-weight: 300;
}

Фото профиля

Приступим к вёрстке отдельных блоков. Как было сказано выше, автор использует фотографии несуществующих людей, сгенерированные нейросетью. Ссылку на сгенерированную фотографию можно увидеть в коде. Обязательно указываем атрибут alt с описанием, что это за изображение.

<div class="info">
  <div class="photo">
    <img src="https://thispersondoesnotexist.com/image" alt="Фото профиля" />
  </div>
  ...
</div>
    
.photo {
  padding: 1rem;
}

.photo img {
  width: 100%;
  display: block;
  border-radius: 50%;
  filter: grayscale(1) contrast(1.2);
}

Здесь мы указываем просто оформляем блок с фото. Чтобы квадратное фото сделать круглым, нужно задать border-radius: 50%. Однако, если фото не будет квадратным, то рекомендую указать значение этого свойства в пикселях, дабы не получить овал. В этом проекте автор использует неподготовленные для сайта фотографии, поэтому было принято решение обработать их согласно специфике сайта. Для этого используется CSS Filter, о которых мы говорили в главе Градиенты, тени и фильтры.

Заголовок

Вёрстка этого блока довольно проста: имя человека и его позиция является заголовком первого уровня. Чтобы отделить перенести должность на другую строку, нужно вынести её в отдельный тег и использоватьdisplay: block.

<h1>
  Дэми Браун
  <small>Junior front-end developer</small>
</h1>
h1 {
  font-size: 2rem;
  font-weight: 300;
  margin: 0 0 0.2em;
  line-height: 1;
  text-align: center;
  color: var(--accent);
  text-transform: uppercase;
}

h1 small {
  display: block;
  font-size: 1rem;
  color: white;
  font-weight: 300;
  padding: 1em 0;
}

Секция с контактной информацией

Эта секция состоит из заголовка и списка ссылок с иконками. Вёрстка так же предельно проста:

<section class="contacts">
  <h2>Контакты</h2>
  <ul>
    <li>
      <a href="//linkedin.com" target="_blank" rel="noreferrer">
        <i class="fab fa-linkedin" aria-hidden="true"></i>
        LinkedIn
      </a>
    </li>
    ...
  </ul>
</section>
    

Задачей CSS является просто немного подвигать элементы и задать размеры иконкам:

.contacts .fab,
.contacts .fas {
  font-size: 1.5em;
  vertical-align: middle;
}

.contacts ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

.contacts li {
  margin-bottom: 1.5em;
}

h2 {
  font-size: 1.5rem;
  padding: 0 0 0.7em;
  margin: 2.5em 0 1em;
  border-bottom: 2px solid var(--middle);
  text-transform: uppercase;
  font-weight: 600;
  position: relative;
}

h2::after {
  content: '';
  display: block;
  position: absolute;
  bottom: -2px;
  left: 0;
  width: 4rem;
  border-bottom: 0.6rem solid var(--accent);
}
    
Заголовок

Заголовок второго уровня имеет необычный элемент красного цвета под собой. Он спозиционирован относительно заголовка и представляет собой красный прямоугольник, находящийся на той же высоте, что и border заголовка.

Пара слов о ссылках

Оформление ссылок — это важная задача, игнорировать которую ни в коем случае нельзя, ведь это косвенно влияет на приток клиентов. К примеру, если у нас будет указан номер телефона, по нажатию на который ничего не произойдёт, мало какой клиент начнёт переписывать его в свой телефон для вызова.

Для оформления ссылок на сторонние ресурсы, необходимо добавлять два атрибута у ссылок: target и rel. Атрибут target позволяет управлять поведением открытия страницы по ссылке. К примеру, значение этого атрибута _blank откроет страницу в новой вкладке. Именно его и нужно использовать для указания ссылок на сторонние ресурсы. Кроме того, необходимо указывать атрибут rel со значением noreferrer или nofollow. Этот атрибут говорит поисковым машинам, что ссылка ведёт на сторонний ресурс, индексировать который нам не обязательно.

Оформление номера телефона обязательно сопровождается ссылкой, атрибут href которой начинается с tel. Телефон указывается в международном формате. К примеру,

<a href="tel:+12345678900" rel="noreferrer">
  +1(234) 567-89-00
</a>
    

Чтобы оформить ссылку на почту, используйте этот формат:

<a href="mailto:mail@gmail.com" rel="noreferrer">
  mail@gmail.com
</a>
    

Также можно оформлять ссылки на чат, к примеру, в WhatsApp, Telegram или Viber:

<a href="https://wa.me/1234567890" rel="noreferrer"> WhatsApp (+1234567890)</a>
<a href="tg://resolve?domain=YOUR_NICKNAME" rel="noreferrer"> Telegram (@YOUR_NICKNAME)</a>

Секции навыков и знания языков

Эта секция представляет собой заголовок и пары название-освоение. Ничего сложного:

<section class="skills">
  <h2>Навыки</h2>

  <figure class="skill">
    <figcaption class="title">HTML</figcaption>
    <svg class="progress" aria-label="50%">
      <rect class="bar" style="width: 50%"></rect>
    </svg>
  </figure>

  ...
</section>
.skills .skill {
  display: grid;
  grid-template-columns: 1fr 2fr;
  gap: 1rem;
  align-items: center;
  margin: 0 0 1em;
}

.skills .skill .title {
  font-weight: 300;
}

.progress {
  background-color: var(--middle);
  width: 100%;
  height: 4px;
}

.progress .bar {
  fill: var(--accent);
  height: 100%;
}

Разберёмся подробнее. Каждая фигура<figure class="skill">является гридом. Внутри находится название<figcaption class="title">и сама фигура, в данном случае, являющаяся inline-svg:<svg class="progress">. По умолчанию SVG имеем какой-то размер, поэтому мы устанавливаем его своим:

.progress {
  width: 100%;
  height: 4px;
}
    

А чтобы перекрасить сам столбик прогресса, мы должны воспользоваться свойствомfill, так как SVG-элементы не имеют свойстваbackground-color.

Основной контент

В этой части практики мы поговорим о блоке с контентом. Он состоит из четырёх секций: опыт, образование, интересы и проекты.

Секции "Опыт" и "Образование"

В секции "Опыт", помимо заголовка, находится список мест работы. Элементы списка содержат позицию как сотрудника компании, даты начала и окончания трудового договора, название компании, достижения и обязанности сотрудника.

Секция "Образование" представляет собой точно такую же вёрстку, но другую информацию: вместо позиции — получаемую учёную степень; вместо дат трудового договора — даты начала и конца обучения; вместо названия компании — название ВУЗа; вместо обязанностей — кратко о полученных знаниях.

<div class="content">
  <section>
    <h2>Опыт</h2>

    <ul class="experience">
      <li>
        <div class="short-info">
          <div class="position">Junior front end developer</div>
          <div class="date-range">
            <time>Сентябрь 2019</time>
            —
            <time>По настоящее время</time>
          </div>
          <div class="company">ООО «Рога и копыта»</div>
        </div>
        <div class="responsibilities">
          <p>
            Lorem, ipsum dolor sit amet consectetur adipisicing elit. Deserunt minima temporibus ratione quae
            dolorem accusamus doloribus consectetur dolor in aut at vel repellat rerum praesentium voluptate
            libero, eveniet pariatur? Quos?
          </p>
        </div>
      </li>
      ...
    </ul>
  </section>

  <section>
    <h2>Образование</h2>
    ...
  </section>
  ...
</div>
.experience li {
  display: block;
  margin-bottom: 2rem;
}

.experience .short-info {
  margin-bottom: 1rem;
}

.experience .position {
  font-weight: 600;
}

.experience .date-range time {
  font-size: 0.7rem;
}

Надеюсь, этот блок не доставит вам больших проблем в понимании.

Интересы

Этот блок состоит из списка иконок. Как мы понимаем, для незрячих людей, использующих скринридер, иконки это ничто. Поэтому им необходимо задать описание. Кроме того стоит подумать о том, как объяснить человеку, что скрывается за иконкой футбольного мяча? Футбол? Спорт? Мяч? Что-то ещё? Убить двух зайцев нам поможет атрибутtitle. Посмотрим, как это будет выглядеть:

<section>
  <h2>Интересы</h2>

  <ul class="interests">
    <li>
      <i class="fas fa-futbol" title="Спорт"></i>
    </li>
    <li>
      <i class="fas fa-camera-retro" title="Фотография"></i>
    </li>
    <li>
      <i class="fas fa-guitar" title="Музыка"></i>
    </li>
    <li>
      <i class="fas fa-suitcase-rolling" title="Путешествия"></i>
    </li>
  </ul>
</section>
.interests {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  font-size: 3rem;
  opacity: 0.8;
}

.interests li {
  margin-right: 1rem;
}

Простое оформление списка в одну строку и немного стилей для размещения иконок.

Секция "Проекты"

Последняя часть разметки — секция "Проекты". Она содержит заголовок и список проектов, представляющих собой иконку, заголовок проекта с ссылкой на страницу проекта и описание.

<section>
    <h2>Проекты</h2>

    <ul class="projects">
      <li>
        <i class="fas fa-file-code" aria-hidden="true"></i>
        <h3>
          <a href="#">
            Проект
          </a>
        </h3>
        <p>Описание проекта</p>
      </li>
      ...
    </ul>
  </section>
h3 {
  font-size: 1.2rem;
  margin: 0 0 0.5em;
}

.projects {
  list-style: none;
  margin: 0;
  padding: 0;
}

.projects li {
  margin-bottom: 1em;
  position: relative;
  padding-left: 3em;
}

.projects a {
  color: var(--back);
}

.projects .fas {
  position: absolute;
  top: 0.2em;
  left: 0;
  font-size: 2.5em;
  opacity: 0.8;
}

Так как название и описание проекта находится в отдельных тегах, для слабовидящих людей иконка не несёт никакой смысловой нагрузки, поэтому её можно скрыть атрибутом aria-hidden="true". Для зрячих же людей, спозиционируем картинку относительно элемента списка, представляющего собой проект. Мы могли бы также воспользоваться и flex-контейнером.

Адаптивность и отзывчивость

К этому моменту у нас уже есть полностью функционирующая мобильная версия проекта. Однако, на этом этапе, она выглядит хорошо только до размера экрана примерно в 500px.Нам нужно создать медиа-выражение, изменяющее расположение блоков при достижении ширины экрана 500px. Всё, что должно произойти к этому моменту — .info должен оказаться в левой части экрана, а блок .content — в правой.

Как мы помним, HTML выглядит так:

<body>
  <main class="container">
    <div class="info">...</div>
    <div class="content">...</div>
  </main>
</body>
    

Чтобы эти блоки расположились рядом, нужно прочитать статью Многоколоночные макетыи использовать один из представленных там методов. Для примера, я использую гриды:

@media (min-width: 500px) {
   {
    display: grid;
    grid-template-columns: 220px 1fr;
  }
}
    

Э тот код читать нужно так: если ширина экрана более 500 пикселей, то блок .containerстанет гридом с раскладкой 200px 1fr.

Увеличим ширину экрана ещё. На отметке примерно в 700px мы снова увидим, что вёрстка выглядит не очень хорошо. Создадим ещё одно правило:

@media (min-width: 700px) {
  body {
    background: linear-gradient(135deg, rgba(240, 240, 240, 1) 50%, rgba(225, 225, 225, 1) 50%);
  }

   {
    grid-template-columns: 2fr 3fr;
  }

  .info,
  .content {
    padding: 3rem;
  }
}
    

При достижении ширины экрана в 700px, установим на фон страницы градиент, изменим раскладку грида блока.containerи зададим большие отступы для его дочерних блоков.

Последние две точки адаптивности посвятим блокам.experienceи блоку.container.

@media (min-width: 900px) {
  .experience li {
    display: grid;
    grid-template-columns: 2fr 3fr;
    gap: 2rem;
  }
}

@media (min-width: 1200px) {
   {
    margin: 5vh auto;
    box-shadow: 30px 50px 40px 20px rgba(0, 0, 0, 0.3);
  }
}

Заключение

Таким образом, нам удалось сверстать простенькую страничку, которая может стать вашим стартом и первым проектом в области Font end разработки.