Вчера меня здорово удивил хм... клиент. Требований к проекту озвучил на 4 листа, а вознаграждение... ну раз 5 в ресторане с девушкой скромно посидеть. И долго мне пришлось убеждать его что не типовые сайты за одну ночь не делаются и стоят весьма прилично. И когда мне удалось отстоять свою точку зрения, я задумался на тему "как много людей не знают как делаются сайты". Поэтому я решил написать небольшую статью на эту тему, структурировав этапы создания и раскрутки сайта в интересную и красочную схему:
Давайте сделаем ее вместе! Создадим неупорядоченный список, c классом timeline
. Добавим к нему несколько элементов с классом event
. У каждого такого события будет переключатель, пустая метка, превьюшка и контейнер для контента. Этот контейнер будет трансформирован в пространстве, поэтому присвоим ему класс content-perspective
. Отметим, что все наши переключатели имеют одно и тоже имя. Таким образом, мы обозначаем что все они принадлежат к одной группе и активным может быть только один из них.
<ul class="timeline">
<li class="event">
<input type="radio" name="tl-group" checked/>
<label></label>
<div class="thumb user-4"><span>Этап 1</span></div>
<div class="content-perspective">
<div class="content">
<div class="content-inner">
<h3>Заголовок</h3>
<p>Некий текст. Бла-бла-бла</p>
</div>
</div>
</div>
</li>
<li class="event">
<input type="radio" name="tl-group"/>
<!-- ... -->
</li>
<!-- ... -->
</ul>
Миниатюры с изобрежениями этапов работ по созданию сайта будут иметь класс thumb
и дополнительный класс от step-1
до step-10
(по числу миниатюр). Тег span будет использован для хранения шага выполнеия работ по созданию нашего сайта))).
CSS
Обнулим отступы для всех элементов.
*,
*:after,
*:before {
box-sizing: border-box;
padding: 0;
margin: 0;
}
Давайте добавим кастомный шрифт. Хотя он и имеет всего четыре символа, он нам очень пригодится, ведь в нем хранятся две стрелки и две галочки, обозначающие два состояния: активен/не активен.
@font-face {
font-family: 'fontawesome-selected';
src: url("font/fontawesome-selected.eot");
src:
url("font/fontawesome-selected.eot?#iefix") format('embedded-opentype'),
url("font/fontawesome-selected.woff") format('woff'),
url("font/fontawesome-selected.ttf") format('truetype'),
url("font/fontawesome-selected.svg#fontawesome-selected") format('svg');
font-weight: normal;
font-style: normal;
Для тегов с классом timeline
установим отступы и зададим position: relative
.timeline {
position: relative;
padding: 30px 0 50px 0;
font-family: 'Gorditas', Arial, sans-serif;
}
Создадим линию, используя псевдоэлементы. У нее будет фон в полоску и абсолютное позиционирование.
.timeline:before {
content: '';
position: absolute;
width: 5px;
height: 100%;
top: 0;
left: 165px;
background: url(data:image/gif;base64,R0lGODlhBQAFAIABAHl5ef///yH5BAEAAAEALAAAAAAFAAUAAAIIjAMHibz2TAEAOw==);
}
Каждый элемент с классом event
будет иметь относительное позиционирование, отступы снизу чтобы между ними было свободное пространство и поле справа. Это сделано для того, чтобы элементы не налезали друг на друга справа когда происходит 3D вращение.
.event {
position: relative;
margin-bottom: 80px;
padding-right: 40px;
}
Теперь давайте разберемся, что там у нас слева. По нашему глубокому замыслу слева должна показываться наша миниатюра, поэтому зададим ей абсолютное позиционирование и пусть она будет круглой, для чего зададим ей радиус скруления равным 50%.
.thumb {
position: absolute;
width: 100px;
height: 100px;
box-shadow:
0 0 0 8px rgba(65,131,142,1),
0 1px 1px rgba(255,255,255,0.5);
background-repeat: no-repeat;
border-radius: 50%;
transform: scale(0.8) translateX(24px);
}
Мы также добавили трансформирование. Уменьшим миниатюру, чтобы потом увеличить когда будет выбран соответствующий переключатель. Так как масштабирование немного сдвинет элемент, то мы можем либо задать transform-origin, либо сдвинуть элемент по горизонтали с помощью свойства translateX. Мы сделаем последнее и сдвинем на 24px. Зачем его вообще требовалось двигать? Кому он мешает - спросите вы? У нас получится небольшой псевдо-элемент с зигзагообразным фоном, привязанный к миниатюре, и мы хотим, чтобы он соприкасался с переключателем:
.thumb:before {
content: '';
position: absolute;
height: 8px;
z-index: -1;
background: transparent
url(data:image/gif;base64,R0lGODlhCgAIAIABACgoKP///yH5BAEAAAEALAAAAAAKAAgAAAINjAOnyJv2oJOrVXrzKQA7);
width: 51px;
top: 42px;
left: 100%;
margin-left: 8px;
}
Давайте зададим стили для нашего тега span
. Зададим абсолютное позиционирование и поместим ее под нашу превьюшку:
.thumb span {
color: #41838e;
width: 100%;
text-align: center;
font-weight: 700;
font-size: 15px;
text-transform: uppercase;
position: absolute;
bottom: -30px;
}
Теперь зададим фон для всех наших десяти этапов (мы также можем использовать картинки вместо фоновых изображений). Я распишу стили только для первых трех этапов. Стили для оставшихся семи вам не составит труда написать самим или подсмотреть в окончательном варианте.
.step1 {
background-image: url(images/step1.jpg);
}
.step2 {
background-image: url(images/step2.jpg);
}
.step3 {
background-image: url(images/step3.jpg);
}
Для переключателя мы сделаем маленькую хитрость. Мы хотим, чтобы переходы и изменения активировались при нажатии на переключатель, поскольку мы можем поставить галочку и использовать родственные элементы. Есть много различных способов сделать это, один из них - это нажатие на метку, которое автоматически приводит к активации соответствующего элемента формы. Но мы не будем пользоваться этой фичей, вместо этого мы поместим переключатель на метку и сделаем его прозрачным. Таким образом, нажимая на метку, вы на самом деле нажимаете на переключатель). Метка в данном случае используется потому, что мы не можем привязать наши псевдо-элементы к тегу input
Назначим одинаковые высоту и ширину для меток и переключателей и поместим их в одно и то же место.
.event label,
.event input[type="radio"] {
width: 24px;
height: 24px;
left: 158px;
top: 36px;
position: absolute;
display: block;
}
Поскольку наш переключатель должен находиться над остальными элементами (не забывайте, что мы должны воздействовать им на соседные элементы) мы назначим ему более высокий z-index, чем нашей метке. Если мы этого не сделаем, она будет на соответствующим ей переключателе.
.event input[type="radio"] {
opacity: 0;
z-index: 10;
cursor: pointer;
}
Теперь создадим небольшой псевдоэлемент для нашего маркера, который будет содержать маленькую иконку из импортированного нами ранее шрифта и установим ее на нужное нам место с помощью абсолютного позиционирования:
.event label:after {
font-family: 'fontawesome-selected';
content: '\e702';
background: #fff;
border-radius: 50%;
color: #41838E;
font-size: 26px;
height: 100%;
width: 100%;
left: -2px;
top: -3px;
line-height: 24px;
position: absolute;
text-align: center;
}
Давайте переместимся на правую сторону нашего гениального бизнес-плана: контента. Нам нужен контейнер с установленной перспективой, который будет иметь отступ слева, перемещающий его в сторону:
.content-perspective {
margin-left: 230px;
position: relative;
perspective: 600px;
}
Для того чтобы "соединить" контент с переключателем, будем использовать псевдоэлемент ввиде линии:
.content-perspective:before {
content: '';
width: 37px;
left: -51px;
top: 45px;
position: absolute;
height: 1px;
z-index: -1;
background: #fff;
}
Очень важно установить псевдо-класс для соответствующего контейнера, поскольку мы не хотим, чтобы он вращался, как все остальные внутренние элементы.
Итак, контент будет вращаться на 10 градусов. Если мы установим transform-origin в левом верхнем наружном углу, будет казаться, что его правая сторона вращается назад.
.content {
transform: rotateY(10deg);
transform-origin: 0 0;
transform-style: preserve-3d;
}
У внутреннего контента будет белый фон и тень. Нам нужен дополнительный элемент, чтобы избежать глюков:
.content-inner {
position: relative;
padding: 20px;
color: #333;
border-left: 5px solid #41838e;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
background: #fff;
}
Сделаем наш заголовок в теге h3 посимпатичнее:
.content-inner h3 {
font-size: 26px;
padding: 5px 0 5px 0;
color: #41838e;
}
Этот абзац будет играть важную роль, поскольку будет изменять высоту. Поскольку мы не можем анимировать высоту до значения auto
. Мы будем анимировать максимальное значение высоты max-height. Обратите внимание, что это только обеспечивает необходимый переход, если значение максимальной высоты близко к реальной высоте. Значит, мы должны будем настроить максимальную высоту так, чтобы она не была слишком большой. Это не очень хорошо, т.к. мы делаем адаптивный дизайн. Конечно, никто не захочет тратить на это лишнее время, ведь эта проблема фактически сводится к установке самой высоты. Для нашего эксперимента нам не понадобится JavaScript, так что это лучший вариант. Мы также добавим медиа-запросы, чтобы наш код не развалился.
Итак, мы установим максимальную высоту на 0 пикселей и сделаем параграф прозрачным (баловство с прозрачностью приводило к появлению рэндомных вспышек, поэтому мы воспользуемся таким способом):
.content-inner p {
font-size: 1.8px;
max-height: 0px;
overflow: hidden;
color: rgba(0,0,0,0);
text-align: left;
}
Наша цель – увеличить параграф и поставить степень прозрачности RGBA на единицу, чтобы добиться эффекта проявления.
Давайте добавим небольшую стрелку слева, используя соответствующий значок в нашем шрифте:
.content-inner:before {
font-family: 'fontawesome-selected';
content: '\25c2';
font-weight: normal;
font-size: 54px;
line-height: 54px;
position: absolute;
width: 30px;
height: 30px;
color: #41838e;
left: -22px;
top: 19px;
z-index: -1;
}
Теперь мы установим различные переходы для соответствующих элементов. Сначала контент должен увеличиваться, и только потом должны активироваться все остальные эффекты. Поэтому нам нужно добавить задержку для этих элементов.
Давайте добавим для наших миниатюр (а также находящегося в них тега span) и заголовков задержку, равную 0.2 секунды:
.thumb,
.thumb span,
.content-inner h3 {
transition: all 0.6s ease-in-out 0.2s;
}
Во внутреннем контейнере будет происходить изменение тени:
.content-inner {
transition: box-shadow 0.8s linear 0.2s;
}
Трансформация контента будет происходить с задержкой:
.content {
transition: transform 0.8s cubic-bezier(.59,1.45,.69,.98) 0.2s;
}
Параграф, внутри внутреннего контейнера будет изменять свой цвет и максимальную высоту:
.content-inner p {
transition: max-height 0.5s linear, color 0.3s linear;
}
Далее, мы описываем, что будет происходить при нажатии на наш переключатель. А произойдет вот что: в превдо-элементе маркера поменяется значок, цвет текста и тень:
.event input[type="radio"]:checked + label:after {
content: '\2714';
color: #F26328;
box-shadow: 0 0 0 5px rgba(255, 255, 255, 0.8);
}
Давайте также изменим при нажатии цвет линии и заголовок контента:
.event input[type="radio"]:checked ~ .content-perspective:before {
background: #F26328;
}
.event input[type="radio"]:checked ~ .content-perspective .content-inner h3 {
color: #F26328;
}
Контент будет вращаться:
.event input[type="radio"]:checked ~ .content-perspective .content {
transform: rotateY(-5deg);
}
У внутреннего контейнера изменится цвет обводки и тень:
.event input[type="radio"]:checked ~ .content-perspective .content-inner {
border-color: #F26328;
box-shadow: 10px 0px 10px -6px rgba(0, 0, 0, 0.1);
}
Параграф, в котором находится описание проводимых нами работ, станет более прозрачным и изменит свою максимальную высоту:
.event input[type="radio"]:checked ~ .content-perspective .content-inner p {
max-height: 260px; /* Add media queries */
color: rgba(0,0,0,0.6);
transition-delay: 0s, 0.6s;
}
В этом коде задержка установлена для того, чтобы изменение цвета происходило не сразу. Теперь поменяем цвет стрелок:
.event input[type="radio"]:checked ~ .content-perspective .content-inner:before {
color: #F26328;
}
Масштаб активной миниатюры станет 1:1, а ее обводка и тень изменятся:
.event input[type="radio"]:checked ~ .thumb {
transform: scale(1);
box-shadow:
0 0 0 8px rgba(242,99,40,1), /* обводка */
0 1px 1px rgba(255,255,255,0.5); /* тень */
}
Текст под миниатюрой поменяет цвет:
.event input[type="radio"]:checked ~ .thumb span {
color: #F26328;
}
И мы заменим голубую зигзагообразную линию оранжевой:
.event input[type="radio"]:checked ~ .thumb:before {
background: transparent
url(data:image/gif;base64,R0lGODlhCgAIAIABAPJlKP///yH5BAEAAAEALAAAAAAKAAgAAAINjAOnyJv2oJOrVXrzKQA7);
}
Теперь мы должны убедиться, что все выглядит прекрасно на маленьких экранах. Для экранов шириной в 850 пикселей и ниже мы уменьшим размеры шрифтов и максимальную высоту параграфа:
@media screen and (max-width: 850px) {
.content-inner h3 {
font-size: 20px;
}
.content-inner p {
font-size: 14px;
text-align: justify;
}
.event input[type="radio"]:checked ~ .content-perspective .content-inner p {
max-height: 500px;
}
}
Для экранов с шириной 540 пикселей и ниже придется внести дополнительные изменения. Поскольку данных становится слишком много для такого маленького экрана, проведем перекомпоновку. Миниатюра останется на месте, но с правой стороны контент переместится под изображение. Переключатель будет расположен сверху миниатюры, и контент будет открываться нажатием на него. Мы избавимся от некоторых элементов, которые нам больше не понадобятся – например, метки и линии. Мы также изменим вращение контента так, чтобы он выпирал не справа, а снизу:
@media screen and (max-width: 540px) {
.timeline:before {
left: 50px;
}
.event {
padding-right: 0px;
margin-bottom: 100px;
}
.thumb {
transform: scale(0.8);
}
.event input[type="radio"] {
width: 100px;
height: 100px;
left: 0px;
top: 0px;
}
.thumb:before,
.event input[type="radio"]:checked ~ .thumb:before {
background: none;
width: 0;
}
.event label {
display: none;
}
.content-perspective {
margin-left: 0px;
top: 80px;
}
.content-perspective:before {
height: 0px;
}
.content {
transform: rotateX(-10deg);
}
.event input[type="radio"]:checked ~ .content-perspective .content {
transform: rotateX(10deg);
}
.content-inner {
border-left: none;
border-top: 5px solid #41838e;
}
.event input[type="radio"]:checked ~ .content-perspective .content-inner {
border-color: #F26328;
box-shadow: 0 10px 10px -6px rgba(0, 0, 0, 0.1);
}
.content-inner:before {
content: '\25b4';
left: 33px;
top: -32px;
}
.event input[type="radio"]:checked ~ .content-perspective .content-inner p {
max-height: 300px;
}
}
Значение максимальной высоты снова должно быть скорректировано, потому что теперь у нас будет немного больше места по горизонтали для абзаца с описанием.
Если вы хотите, чтобы можно было открыть больше одного элемента, просто попробуйте заменить радиокнопки чекбоксами.