Все началось с небольшого эксперимента из учебника +Nettuts, который рассказывает, как встроить 3D-граффики в HTML-страницы с помощью CSS, изображений и JavaScript. После прочтения урока я загорелся желанием превратить эту идею на чистом CSS и посмотреть, как далеко я могу её усовершенствовать. Первоначальная задача заключалась в создании классических полупрозрачных 3D блоков, с 6 сторонами. Наша задача состоит в том, чтобы создать 3D-график.
Запишем некоторые ключевые требования, которые должны быть в грффике.
- background-independent
- Адаптация (не зависимо от количество блоков)
- Масштабируемость (как вектор графика)
- Легко настраивается (цвета, размеры, пропорции)
Этап планирования является наиболее важной частью любого проекта.
Перед тем как начать кодировать, я обычно записываю все потенциальные проблемы. Вот список задач с решениями, которые я придумал для этого проекта:
Проблема № 1 – Подвижный внутренний блок
Что мы знаем:
- Блок должен быть представлен в виде 3D-окна, который состоит из 6 сторон
- Внутренний блок должен вертикально перемещаться в движении. Так же должна быть возможность скрыть этот блок
Что нам понадобиться:
- 1 div для резервного корпуса, состоящего из 3-х сторон (задняя сторона, нижняя сторона, левая сторона)
- 1 div на передний корпус, состоящий из 3-х сторон (передняя боковая, верхняя сторона, правая сторона)
- 1 div на внутренний блок, состоящий из 3-х сторон так, как передняя часть корпуса, но с меньшим z-index
- 1 div контейнер в который поместим все три части и поставим относительно сплошного участка фона в нижнем правом углу
- 1 div контейнер с overflow: hidden, чтобы скрыть внутренний блок в графике, когда он падает до нуля
Вы можете удивиться, зачем нам нужно два блока? Нам нужно по крайней мере один контейнер в графике. Мы знаем, что график должен быть масштабируемый, поэтому мы используем проценты, этим мы будем манипулировать значениями графика, наша высота блоков должна быть равна высоте одной из сторон графика.
Так же есть и другая проблема — должна быть возможность скрыть внутренний блок в движении, а значит, должны находится «под панелью» и скрывать их там. Вы можете подумать что решение для этого — overflow: hidden, не так ли? Да, но не для этого блока, его высота меньше, чем фактическая высота графика. Именно поэтому мы добавим еще один блок над нем и применим нему overflow: hidden.
Проблема № 2 – Фиксатор для графика
Фиксатор должен:
- быть представлен в 3D с 3-х сторон (сзади, снизу, слева)
- background-independent
- быть адаптивным, чтобы количество блоков и их атрибутов было одинаково (высота, ширина и т.д.)
- Должны быть Х и Y оси
Что нам понадобится:
- 1 неупорядоченный список
- 1 элемент внутри каждого элемента списка для меток оси Х
- 1 блок внутри каждого элемента списка
- 1 элемент неупорядоченного списка для меток оси Y
Хм, неупорядоченный список? Разве не более эффективно использовать список определений для графика? Конечно, это более эффективно, но мы не можем использовать его, потому что для каждого блока нам надо добавить собственные метки по оси X.
Хорошо, но почему не использовать элемент списка, а использовать второй контейнер блока? Мы не можем сделать это, потому что мы должны поставить метки по оси Х вне графика и так как верхняя часть блока скрывает данные которые переполняют его, мы будем использовать элементы списка, чтобы убедиться, что все элементы расположены правильно.
Реализация
Теперь у нас есть план, давайте преобразуем его в код.
Проблема № 1 – Подвижный внутренний блок
<div class="bar-wrapper">
<div class="bar-container">
<div class="bar-background"></div>
<div class="bar-inner">50</div>
<div class="bar-foreground"></div>
</div>
</div>
Давайте пройдемся по целям каждого элемента еще раз:
- bar-wrapper — скрывает .bar-inner когда он скользит вниз
- bar-container — позиционирует .bar-foreground, .bar-inner, .bar-foreground относительно фона, в нижнем углу
- bar-background — создание 3-х сторон блока: сзади, внизу, слева
- bar-inner — самая важная часть, — внутренний блок
- bar-foreground — создает 3 стороны блока: спереди, сверху, справа
Стиль блока:
/* Bar wrapper - hides the inner bar when it goes below the bar, required */
.bar-wrapper {
overflow: hidden;
}
/* Bar container - this guy is a real parent of a bar's parts - they all are positioned relative to him */
.bar-container {
position: relative;
margin-top: 2.5em; /* should be at least equal to the top offset of background casing */
/* because back casing is positioned higher than actual bar */
width: 12.5em; /* required, we have to define the width of a bar */
}
/* right bottom patch - make sure inner bar's right bottom corner is "cut" when it slides down */
.bar-container:before {
content: "";
position: absolute;
z-index: 3; /* to be above .bar-inner */
bottom: 0;
right: 0;
/* Use bottom border to shape triangle */
width: 0;
height: 0;
border-style: solid;
border-width: 0 0 2.5em 2.5em;
border-color: transparent transparent rgba(183,183,183,1);
}
Обратите внимание, что мы устанавливаем ширину .bar-container's на 12.5em. Это число является суммой значения ширины передней стороны и правой стороны — в нашем примере это 10 + 2,5 = 12,5
Мы также используем border, чтобы сформировать треугольник и поместить его в правом нижнем углу .bar-container, чтобы убедиться, что стороны внутреннего блока будут уменьшаться, когда он перемещается по вертикали. Мы используем псевдо-класс для создания этого элемента.
Стиль задней части блока:
/* Back panel */
.bar-background {
width: 10em;
height: 100%;
position: absolute;
top: -2.5em;
left: 2.5em;
z-index: 1; /* just for reference */
}
.bar-background:before,
.bar-background:after {
content: "";
position: absolute;
}
/* Bottom panel */
.bar-background:before {
bottom: -2.5em;
right: 1.25em;
width: 10em;
height: 2.5em;
transform: skew(-45deg);
}
/* Left back panel */
.bar-background:after {
top: 1.25em;
right: 10em;
width: 2.5em;
height: 100%;
/* skew only the Y-axis */
transform: skew(0deg, -45deg);
}
Как вы видите, мы двигаем корпус на 2.5em вверх и вправо. И, конечно, мы исказили левую и нижнюю стороны на 45 градусов. Обратите внимание, что мы устанавливаем первое значение наклона 0deg, а второе до 45 градусов, что позволяет нам, исказить этот элемент по вертикали.
Передняя часть блока:
/* Front panel */
.bar-foreground {
z-index: 3; /* be above .bar-background and .bar-inner */
}
.bar-foreground,
.bar-inner {
position: absolute;
width: 10em;
height: 100%;
top: 0;
left: 0;
}
.bar-foreground:before,
.bar-foreground:after,
.bar-inner:before,
.bar-inner:after {
content: "";
position: absolute;
}
/* Right front panel */
.bar-foreground:before,
.bar-inner:before {
top: -1.25em;
right: -2.5em;
width: 2.5em;
height: 100%;
background-color: rgba(160, 160, 160, .27);
transform: skew(0deg, -45deg);
}
/* Top front panel */
.bar-foreground:after,
.bar-inner:after {
top: -2.5em;
right: -1.25em;
width: 100%;
height: 2.5em;
background-color: rgba(160, 160, 160, .2);
transform: skew(-45deg);
}
Здесь ничего нового, так же как стили задней части блока, мы просто используем разные стороны. Только мы применили эти стили для передней части блока и внутренней. Почему бы и нет? Это то же самое, с точки зрения их формы.
Стиль внутреннего блока
.bar-inner {
z-index: 2; /* to be above .bar-background */
top: auto; /* reset position top */
background-color: rgba(5, 62, 123, .6);
height: 0;
bottom: -2.5em;
color: transparent; /* hide text values */
transition: height 1s linear, bottom 1s linear;
}
/* Right panel */
.bar-inner:before {
background-color: rgba(5, 62, 123, .6);
}
/* Top panel */
.bar-inner:after {
background-color: rgba(47, 83, 122, .7);
}
Проблема № 2 – Фиксатор графика (с осями)
<ul class="graph-container">
<li>
<span>2011</span>
<-- HTML markup of a bar goes here -->
</li>
<li>
<span>2012</span>
<-- HTML markup of a bar goes here -->
</li>
<li>
<ul class="graph-marker-container">
<li><span>25%</span></li>
<li><span>50%</span></li>
<li><span>75%</span></li>
<li><span>100%</span></li>
</ul>
</li>
</ul>
Как видите, мы используем неупорядоченный список и диапазон внутри элементов для размещения X-и Y-осей.
/** Graph Holder container **/
.graph-container {
position: relative; /* required Y axis stuff, Graph Holder's left and bottom sides to be positions properly */ display: inline-block; /* display: table may also work.. */ padding: 0; /* let the bars position themselves */
list-style: none; /* we don't want to see any default <ul> markers */
/* Graph Holder's Background */
background-image: linear-gradient(left , rgba(255, 255, 255, .3) 100%, transparent 100%);
background-repeat: no-repeat;
background-position: 0 -2.5em;
}
Мы используем линейный градиент для заполнения блоков и поднимем его на 2.5em. Почему? Поскольку нижний график (стиль для которого мы буде создавать далее) является выше на 2.5em и перекосом на 45 градусов, поэтому есть пустое пространство в правом нижнем углу.
Стиль нижней части:
/* Graph Holder bottom side */
.graph-container:before {
position: absolute;
content: "";
bottom: 0;
left: -1.25em; /* skew pushes it left, so we move it a bit in opposite direction */
width: 100%; /* make sure it is as wide as the whole graph */
height: 2.5em;
background-color: rgba(183, 183, 183, 1);
/* Make it look as if in perspective */
transform: skew(-45deg);
}
Мы наклоним на 45 градусов и переместим её немного влево, чтобы убедиться, что она расположена правильно. Мы дали ей 50% ширины, но убедитесь, что она заполняет промежуток перед первым блоком.
Стиль фиксатора:
/* Graph Holder left side*/
.graph-container:after {
position: absolute;
content: "";
top: 1.25em; /* skew pushes it up so we move it down a bit */
left: 0em;
width: 2.5em;
background-color: rgba(28, 29, 30, .4);
/* Make it look as if in perspective */
transform: skew(0deg, -45deg);
}
Здесь ничего особенного. Просто перекос элемента на 45 градусов, как обычно, и переместили его немного вниз.
Стиль для элементов списка которые держат наши блоки:
/* Bars and X-axis labels holder */
.graph-container > li {
float: left; /* Make sure bars are aligned one next to another*/
position: relative; /* Make sure X-axis labels are positioned relatively to this element */
}
/* A small hack to make Graph Holder's background side be wide enough
...because our bottom side is skewed and pushed to the right, we have to compensate it in the graph holder's background */
.graph-container > li:nth-last-child(2) {
margin-right: 2.5em;
}
/* X-axis labels */
.graph-container > li > span {
position: absolute;
left: 0;
bottom: -2em;
width: 80%; /* play with this one if you change perspective depth */
text-align: center;
font-size: 1.5em;
color: rgba(200, 200, 200, .4);
}
Итак здесь мы сделали: во-первых, мы составили наши блоки рядом друг с другом. Во-вторых, мы добавили правое поле до последнего блока. Таким образом, мы предоставляем достаточно места.
Хорошо, мы почти у цели. Последнее, что осталось добавить, это маркеры Y-оси.
/* Markers container */
.graph-container > li:last-child {
width: 100%;
position: absolute;
left: 0;
bottom: 0;
}
/* Y-axis Markers list */
.graph-marker-container > li {
position: absolute;
left: -2.5em;
bottom: 0;
width: 100%;
margin-bottom: 2.5em;
list-style: none;
}
/* Y-axis lines general styles */
.graph-marker-container > li:before,
.graph-marker-container > li:after {
content: "";
position: absolute;
border-style: none none dotted;
border-color: rgba(100, 100, 100, .15);
border-width: 0 0 .15em;
background: rgba(133, 133, 133, .15);
}
/* Y-axis Side line */
.graph-marker-container > li:before {
width: 3.55em;
height: 0;
bottom: -1.22em;
left: -.55em;
z-index: 2; /* be above .graph-container:after */
transform: rotate(-45deg);
}
/* Y-axis Background line */
.graph-marker-container li:after {
width: 100%;
bottom: 0;
left: 2.5em;
}
/* Y-axis text Label */
.graph-marker-container span {
color: rgba(200, 200, 200, .4);
position: absolute;
top: 1em;
left: -3.5em; /* just to push it away from the graph.. */
width: 3.5em; /* give it absolute value of left offset */
font-size: 1.5em;
}
Как видите, мы устанавливаем 100% ширину нашему фиксатору маркеров для того, использовали пунктирную границу, стиль нашей Y-оси линий и положение диапазона элементов таким образом, что метки Y-оси вне графика. С помощью :before и :after, мы можем оставить наш HTML более чистым.
Мы закончили со всеми стилями для нашего графика, но мы не поставили некоторые важные переменные — размеры, цвета и не заполнили значения в блоках! Мы сказали, что наш график будет настраиваемый, не так ли? Итак, я решил не смешивать переменные с остальной частью кода, так что вы сами их можете настроить.
/****************
* SIZES *
****************/
/* Size of the Graph */
.graph-container,
.bar-container {
font-size: 8px;
}
/* Height of Bars */
.bar-container,
.graph-container:after,
.graph-container > li:last-child {
height: 40em;
}
/****************
* SPACING *
****************/
/* spacing between bars */
.graph-container > li .bar-container {
margin-right: 1.5em;
}
/* spacing before first bar */
.graph-container > li:first-child {
margin-left: 1.5em;
}
/* spacing after last bar */
.graph-container > li:nth-last-child(2) .bar-container {
margin-right: 1.5em;
}
/****************
* Colors *
****************/
/* Bar's Back side */
.bar-background {
background-color: rgba(160, 160, 160, .1);
}
/* Bar's Bottom side */
.bar-background:before {
background-color: rgba(160, 160, 160, .2);
}
/* Bar's Left Back side */
.bar-background:after {
background-color: rgba(160, 160, 160, .05);
}
/* Bar's Front side */
.bar-foreground {
background-color: rgba(160, 160, 160, .1);
}
/* Bar's inner block */
.bar-inner,
.bar-inner:before { background-color: rgba(5, 62, 123, .6); }
.bar-inner:after { background-color: rgba(47, 83, 122, .7); }
/*************************************
* Bars Fill *
* Just an example of filling 3 bars *
*************************************/
.graph-container > li:nth-child(1) .bar-inner { height: 25%; bottom: 0; }
.graph-container > li:nth-child(2) .bar-inner { height: 50%; bottom: 0; }
.graph-container > li:nth-child(3) .bar-inner { height: 75%; bottom: 0; }
Заключение
Давайте пройдемся по некоторым признакам спецификации/методам CSS, которые мы рассмотрели в этой статье.
- transform: skew() и transform: rotate() при помощи этих свойств наши элементы создают иллюзию 3D объектов
- :before и :after использовали как псевдо-классы для создания элементов в CSS и сделали HTML разметку относительно чистой
- :nth-last-child() и :not псевдо-классы для конкретных элементов списка и избегания добавления дополнительных классов/идентификаторов для разметки
- linear-gradient вместе с background-position, чтобы частично заполнить элемент с фоном
- RGBA () для цвета с альфа-прозрачностью
- borders для создания формы треугольника
Автор: Lecaw