Веб-разработка
June 13

Продвинутые Мастерские — Тренажёр HTML Academy

Вторая часть мастерских, которые мы начали в прошлый раз. Эта часть объективно сложная. Элементы с прикольными эффектами, трансформациями, динамикой и сделаем слайдер на CSS!

Здесь ещё активно будет использовать псевдоэлементы ::before и ::after — часть, можно сказать, про них и про их эксплуатацию.

Сложность еще сдабривается парочкой моментов, когда мы используем штуки, которые еще не изучали. Они будут аж в «Тонкостях» — это последний блок в сетке Тренажёров! Я уже смирился, к тому же вещи не сильно сложные. Но по бомбить немного всё-таки не грех.

Маска при наведении

Достаточно впечатляющий и хитрый приём, который я думал делается на JS или какими-то другими шаманскими штуками. Мы создали элементы необычной формы — шестиугольник с закруглёнными углами, с помощью изображения-маски. На самом деле всё проще чем даже звучит.

Маской является изображение с прозрачной фигурой в центре — в нашем случае это шестиугольник и круг, которым мы задали трансформацию scale() по наведению на блок.

<section class="shape">
  <a class="overlay" href="#">Маска</a>
  <div class="details">
    ...
  </div>
</section>

Контейнеру, в нашем случае это <section>, задаем изображение на фон — у нас это мокап сайта, задаём ему relative и скрываем всё выпадающее из контейнера через overflow:

.shape {
  position: relative;
  overflow: hidden;
  background: url("techmart.jpg") no-repeat 30% 0%;
  ...
}

Маска здесь свёрстана через ссылку <a> с классом .overlay — она абсолютно позиционирована absolute, занимает всю площадь контейнера, на фон помещается картинка с прозрачной областью в виде нужной фигуры.

Вся хитрость, в общем-то в этом. Дальше уже пляшем с трансформацией и наведением — задаём scale(1) базовый и какой-нибудь побольше при наведении на контейнер для эффекта увеличения:

.overlay {
  ...
  position: absolute;
  background-image: url("hexagon.svg"); /* картинка-маска */
  z-index: 1; /* располагается явно над контейнером */
  transition: transform 0.6s ease-out; /* плавный переход */
  transform: scale(1); /* базовый размер */
}

.shape:hover .overlay {
  transform: scale(1.07); /* увеличивается при наведении */
}

Бокс с описанием .details работает по схеме, которую мы разобрали в выпадающих меню. Насколько я понял, это вообще стандартная практика.

Выдвигающееся описание

Тоже очень интересный эффект, который я часто встречал. И тоже думал делается как-то на JS, а не на чистом CSS.

Сама вёрстка немного упоротая, хотя я уже не удивлюсь если это нормальная практика. Мы явно не задаём ни заголовок ни описание в выдвигающемся окне — вся эта информация указана в data-атрибутах ссылки.

<section class="works">
  <a class="caption-link" href="#" 
     data-title="Sunset" 
     data-description="Сайт туристического агентства, специализирующегося на незабываемых поездках в тёплые страны.">
     
    <img src="shot-1.jpg" alt="Sunset">
  </a>
</section>

То есть по вёрстке у нас буквально получается только контейнер, внутри которого одна ссылка <a>, внутри которой картинга <img>.

Здесь на сцену выходят псевдоэлементы — именно через них, а точнее через свойство content и функцию attr(), которая принимает в аргумент дата-атрибут, мы и выводим информацию в выдвигающемся окне.

.caption-link::before {
  content: attr(data-title);
}

.caption-link::after {
  content: attr(data-description);
}

Интересный подход, мне бы в голову не пришел. Итак, компоненты все у нас есть, теперь стилизуем и анимируем. Во-первых, оба элемента — и <a>, и <img>, у нас занимаю полностью весь контейнер, но находятся в разных состояниях.

Картинка <img> в базовом состоянии закрывает элемент, а при наведении смещается вправо на всю ширину блока:

.caption-link img {
  display: block;
  max-width: 100%;
  transition: transform 0.3s ease;
}

.caption-link:hover img {
  transform: translateX(100%);
}

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

.caption-link {
  position: relative;
  z-index: 1;
  display: block;
  overflow: hidden;
}

Ссылке <a> задаётся z-index, чтобы быть выше остальных элементов, позиция relative для позиционирования псевдоэлементов и через overflow мы скрываем выезжающие туда-сюда картинку и псевдоэлементы.

Псевдоэлементы же позиционируем абсолютно, стилизуем как нам надо и ставим базовую позицию смещённой влево, а по наведению возвращаем их в центр:

.caption-link::before,
.caption-link::after {
  ...
  position: absolute;
  z-index: -1; /* чтобы быть ниже самой ссылки */
  transition: transform 0.3s ease-in-out;
  transform: translateX(-80px);
}

.caption-link:hover::before,
.caption-link:hover::after {
  transform: translateX(0);
}

Эффектные ссылки

Продолжаем эксплуатировать псевдоэлементы, теперь сделаем несколько интересных эффектов с кнопками. Я часто видел такие «пойнтеры» делают в меню и вот, наконец, узнал как их можно сделать. Точнее один из эффектных вариантов.

Все это просто ссылки <a> с текстом и псевдоэлементами, нет смысла сюда писать вёрстку. Разберёмся только с тем, что происходит в CSS.

Первая кнопка, «Апельсин», очень простая — один псевдоэлемент, появляется и исчезает с помощью свойства opacity и двигается через translateY():

.effect-1 a::after {
  ...
  opacity: 0;
  transition: opacity 0.3s, transform 0.3s;
  transform: translateY(10px);
}

.effect-1 a:hover::after {
  opacity: 1;
  transform: translateY(0);
}

Вторая кнопка, «Виноград», использует уже два псевдоэлемента — позиционируем их отдельно, используем translate() и rotate() и не забываем задать свойство transform-origin для каждого, иначе они будут поворачиваться от центра.

.effect-2 a::before,
.effect-2 a::after {
  ...
  position: absolute;
  opacity: 0.2; /* 20% видимости */
  transition: all 0.3s;
}

.effect-2 a::before {
  top: 0; left: 0; /* прижимаем к левому-верхнему углу */
  transform-origin: 0 0; /* там же и origin */
  transform: rotate(90deg); 
}

.effect-2 a::after {
  right: 0;  bottom: 0; /* аналогично, правый нижний угол */
  transform-origin: 100% 100%; /* там же и origin */
  transform: rotate(90deg);
}

.effect-2 a:hover::before {
  left: 50%; /* перемещаем к центру блока*/
  opacity: 1; /* делаем не прозрачным */
  transform: rotate(0deg) translateX(-50%);
  /* поворачиваем обратно и прижимаем к верху */
}

.effect-2 a:hover::after {
  right: 50%;
  opacity: 1;
  transform: rotate(0deg) translateX(50%);
  /* всё аналогично, но в обратную сторону */
}

Третья кнопка, «Лайм», использует всего один атрибут, но другой эффект. Здесь, снова задействована функция attr () — через нее мы выводим ещё одно слово «лайм» поверх в виде псевдоэлемента.

По сути в базовом состоянии мы видим псевдоэлемент, а по наведению он исчезает и «открывает» текст самого элемента.

.effect-3 a::before {
  position: absolute;
  color: #ffffff;
  content: attr(data-hover);
  transition: transform 0.3s, opacity 0.3s;
}

.effect-3 a:hover::before {
  opacity: 0;
  transform: scale(0.9);
}

Четвёртая кнопка, «Киви» — это, в общем-то, такой же прием, что и с первой кнопкой, только псевдоэлемента теперь два и еще изменяется цвет текста при наведении.

Даже код копировать не буду — это реально тоже самое. Видимо, дали нам её просто для закрепления всего пройденного.

Закруглённые внутрь углы

Честно говоря, мне в голову такая идея ни разу не приходила. С трудом могу представить ситуации где нужно это, кроме вариантов стилизации под почтовую марку, наверное.

Но в этом примере нам еще и объясняют работу интересного свойства clip. И есть ощущение, что задача была придумана в принципе ради него.

Свойство clip по своей сути работает как crop-tool в Photoshop — мы выделяем область которая будет видна, а остальные части, не попавшие в выделение скрываются.

Выделение происходит с помощью функции rect(top, right, bottom, left), которая принимает четыре аргумента — границы сверху, справа, снизу и слева — именно в таком порядке.

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

Итак, зачем нам вообще это свойство? С его помощью мы сделаем открытку с вогнутыми углами. Для начала сверстаем заготовку:

<blockquote class="outer">
  <div class="inner">
    <p>Любую теорию можно согласовать с любым фактом, 
       если принять некоторые дополнительные допущения.</p>
    <footer>
      <cite>— Хантер С. Томпсон</cite>
    </footer>
  </div>
</blockquote>

Да, это блок цитаты и внутри него блок для стилизации. Верстаем их в виде такого толстого креста — один больше по вертикали, второй по горизонтали.

Теперь нам нужны сами вогнутости. Здесь опять выходят на сцену псевдоэлементы! Создаем кольцо с помощью толстой рамки border и позиционируем её в углу.

А с помощью clip() отрезаем только один угол:

Делаем это четыре раза — по два псевдоэлементы ::before и ::after, обоим элементам .outer и .inner.

Перекрашиваем их в белый и получаем натурально вогнутые углы!

Слайдер на CSS

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

В нашем случае слайдер — это один длинный элемент с картинками <img> внутри, который двигается по горизонтали с помощью translateX(), который находится внутри элемента с overflow: hidden;.

Основная сложность в этой задаче — это «запрограммировать» переключение слайдера используя только CSS. Для этого мы используем радиокнопки, псевдокласс :checked и селектор последующих элементов ~.

<div class="slider-block">
  <input type="radio" id="btn-1" name="toggle" checked>
  <input type="radio" id="btn-2" name="toggle">
  <input type="radio" id="btn-3" name="toggle">

  <div class="container">
    <div class="slider">
      <img src="img-1.jpg" alt="Первое изображение">
      <img src="img-2.jpg" alt="Второе изображение">
      <img src="img-3.jpg" alt="Третье изображение">
    </div>
  </div>
</div>

Здесь у нас три радио кнопки, связанные между собой через name. Между собой кнопки и слайдер в вёрстке связаны только общим родителем.

Вся «магия» происходит в CSS — мы задаём очень хитрый селектор, который применяет свои свойства «проверяя» наличие атрибута checked через псевдокласс у кнопок, а далее через селектор последующих элементов ~ указываем изменения в свойстве слайдера.

#btn-1:checked ~ .container .slider {
  transform: translate(0);
}

#btn-2:checked ~ .container .slider {
  transform: translate(-450px);
}

#btn-3:checked ~ .container .slider {
  transform: translate(-900px);
}

Расшифровывая, например, второй селектор:

Задать трансформацию по горизонтали на 450 px влево слайдеру .slider, находящемуся в контейнере .container, который идет после выбранной :checked второй кнопки #btn-2.

Каждый раз меняя выбор, то есть по факту переставляя атрибут checked, мы применяем разные селекторы, а соответственно разную позицию слайдера.

В тренажёре немного усложнено ради стилизации кнопок — они сделаны через <label> связанные с радиокнопками через for="", а сами кнопки скрыты.

Breadcrumbs или «хлебные крошки»

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

Финальный результат выглядит вот так:

На первый взгляд всё просто — несколько элементов с эффектами по наведению. Мы же уже делали меню в прошлой части! Здесь нюанс в нумерации — она сделана не через <ol>, как можно предположить, а используя функцию counter() и смежные с ней свойства counter-reset и counter-increment.

Функцию счётчика counter(), насколько я понял, можно использовать только как контент content для псевдоэлементов, хотя странно — функция же вернёт просто строку и, по идее, можно вписывать куда угодно. Как-нибудь проверим!

Для начала использования функции нужно задать свойство counter-reset какому-либо контейнеру, где функция будет использоваться. Свойство принимает имя счётчика, которое потом будет использоваться в работе — читай переменная. Также необязательно можно задать его начальное значение, по умолчанию оно равно нулю.

.breadcrumbs { /* контейнер хлебных крошек */
  ...
  counter-reset: flag;
}

Сами кнопки у нас это ссылки <a>, а нумерация — их псевдоэлемент ::before. Именно ему мы записываем счётчик counter(flag) как контент, а свойством counter-increment мы увеличиваем счетчик — это своего рода свойство-приращение +1 к функции.

.breadcrumbs a::before {
  ...
  content: counter(flag); /* выведет значение счётчика*/
  counter-increment: flag; /* прирастит к счетчику +1 */
}

При этом counter-increment работает в селекторе одновременно с content, то есть в нашем случае отобразиться значение 1, так как приращение уже сработало в CSS-правиле. Для нумерации с нуля, нужно было либо задать counter-reset: -1;, либо, например, отдельно задавать первый элемент :first-child без приращения.

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

Второй особенностью здесь является реализация стрелок — это псевдоэлементы ::after с трансформацией и особенностью свойства border-radius.

Оказалось, что border-radius тоже может принимать комплекс значений — каждый угол отдельно. Начиная от левого верхнего по часовой стрелке, можно указать отдельно закругление всем углам бокса. В тренажёре мы так и поступили — закруглили псевдоэлементу ::after один угол и повернули на 45 градусов:

.breadcrumbs a::after {
  ...
  border-radius: 50px 0 0 0; /* 50px левому верхнему углу, остальные 0 */
  transform: rotate(-45deg);
}

Дальше уже стилизация, эффекты наведения и остальные отступы — уже не столь интересно в контексте задачи.

Испытания

Испытаний всего два — первое испытание в основном на свойство clip. За нас всё сверстали, нам нужно только подставить правильные селекторы.

Я на нём попыхтел, но когда решил пойти сначала от списка — стало сильно легче. Честно, разбираться вот в делениях графика такое себе, но вообще интересно было. Вспомнили, кстати, про селекторы по атрибуту, я сначала тоже голову поломал — думал уже через nth-child даже.

Второе испытание какое-то для этой части прям несправедливо лёгкое. Тоже уже всё сверстано и надо подставить селекторы, но тут буквально два класса и их псевдоэлементы.


Вот и прошли мы Мастерские. В конце последней части там еще было два урока про отметку на карте, но я решил не конспектировать, так как там просто про border-radius тот же самый.

Вообще, я думал всё уместиться в одну статью, но я, как обычно, не умею в коротко. Пусть так.

Спасибо большое за внимание, надеюсь, что это будет хоть кому-то полезно и интересно!

Ссылки на другие статьи по HTML Academy:

Знакомство с Веб-разработкой
Знакомство с HTML и CSS
Знакомство с JavaScript
Знакомство с PHP
Таблицы и подробно о формах
Наследование, каскады и селекторы
Блочная модель, поток и сетка на float
Гибкие флексбоксы display: flex
Удобные сетки на гридах display: grid
Пропуск блока «Погружение»
Позиционирование и двумерные трансформации
Теневое искусство и линейные градиенты
CSS-фильтры и Кекстаграм
Мастерские
Продвинутые Мастерские ← Вы здесь

Остальные статьи можно посмотреть у меня на главной странице блога.

Также мои соц. сетки, которые я продолжаю вести:

Мой Twitter
Мой Telegram
Мой Паблик ВК

Заходите куда удобно вам и подписывайтесь! Еще раз спасибо за внимание!