Сегодня мы с вами займемся созданием анимированной диаграммы с помощью CSS3. Раньше мы делали нечто подобное с помощью Jquery, но пора наконец избавиться от громоздких плагинов с кучей ненужного функционала.
Запишем основные требования. Наша диаграмма должена быть:
- независимой от фона
- адаптивной (для любого количества столбиков)
- масштабируемой (как векторные графики)
- легко настраиваемой (цвет, размеры и пропорции)
Начнем по порядку.
Столбец с подвижным внутренним блоком.
Наш столбец должен быть представлен в виде прямоугольного параллелепипеда, состоящего из 6 сторон, при этом внутренний блок должен вертикально перемещаться. Также нужно предусмотреть возможность скрыть этот блок. Для выполнения этой задачи нам понадобится всего пять блочных элементов:
- 1 div для задней части, состоящей из 3-х сторон (задняя стенка, основание и левая стенка)
- 1 div для передней части, состоящей из 3-х сторон (передняя, верхняя и правая стенки)
- 1 div для внутреннего блока, состоящего из 3-х сторон наподобие передней части, но с меньшим значением свойства
z-index
- 1 контейнер для расположения всех трех частей относительно него и применения единой фоновой текстуры в правом нижнем углу
- 1 контейнер с
overflow: hidden
, чтобы спрятать внутренний блок под столбцом, когда он опускается вниз
Вы можете удивиться, зачем нам нужно два контейнера? Это не так просто понять, поэтому я объясню. Нам нужен как минимум 1 контейнер на столбик (для того чтобы относительно него мы могли расположить переднюю, заднюю и внутреннюю стенку). Кроме того наш столбик должен быть масштабируемым, и поэтому мы воспользуемся процентным соотношением для управления заполнением столбца. Для этого нам необходимо, чтобы высота нашего контейнера была равна высоте одной из сторон столба.
Далее, у нас должна быть кнопка, с помощью которой можно скрыть внутренний блок, тоесть наш блок должен опуститься «ниже столбика» и исчзнуть. Можно применить свойствоoverflow: hidden
?, но не для этого контейнера, так как его высота меньше настоящей высоты столбца. Добавляем еще один контейнер поверх него, и уже к нему применим overflow: hidden
. Вот так и получилось два контейнера.
Интерьер диаграммы на CSS3.
Пришло время поработать над интерьером для нашей столбчатой диаграммы чтобы было все красиво и функционально. Требования к нему довольно жсткие:
- раз у на все в 3D, то понадобятся три плоскости (задняя стенка, низ и левая стенка)
- не зависеть от фона
- не зависеть от количества столбиков и их характеристикам (высота, ширина и пр.)
- оси Х и Y должны быть снаружи с нанесенными на них соответствующими метками
Для этого нам потребуется:
- 1 неупорядоченный список ul
- 1 элемент внутри каждого элемента списка для ярлыков по оси X
- 1 столбец внутри каждого элемента списка
- 1 элемент списка с неупорядоченным списком внутри для ярлыков по оси Y
Неупорядоченный список, - спросите вы? Разве более семантическим не является использование списка определений dl для столбикового графика? Возможно, но мы не можем использовать его, так как нам необходимо обернуть каждый столбик и его ярлык на оси X одним контейнером для того, чтобы спозиционировать их относительно друг друга.
Более того, мы не можем использовать пункт списка вместо второго контейнера для столбика, так как нам необходимо расположить ярлыки оси X за пределами диаграммы и, так как мы знаем, что второй контейнер столбца скрывает любой контент, который больше или выходит за него, мы используем элементы списка для того, чтобы убедиться, что все элементы спозиционированы правильно.
Реализация
Настало время покончить с теорией и заняться кодированием. Код для столбика с двигающимся внутренним блоком выглядит так:
<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 - скрывает внутренний бар, когда он опускается ниже бара */
.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; /* мы должны определить ширину панели */
}
/* 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;
/* Используйте нижнюю рамку, чтобы сформировать треугольник */
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
задана ширина 12.5em. Это число равно сумме ширины передней (10em) и ширины правой (2.5em) сторон столбца. Мы так же используем границы для создания треугольной формы с помощью свойств border-style, border-width и border-color. Этот треугольник расположим в правом нижнем углу элемента .bar-container
для того, чтобы быть уверенными в том, что внутренняя сторона столбца «обрезается» при вертикальном перемещении.
А теперь приступим к оформлению задней стенки:
/* Задняя стенка */
.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;
}
/* Нижняя стенка */
.bar-background:before {
bottom: -2.5em;
right: 1.25em;
width: 10em;
height: 2.5em;
transform: skew(-45deg);
}
/* левая часть задней стенки */
.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 градусов. Обратите также внимание на строку transform: skew(0deg, -45deg);
: задав наклон по иси X равным 0 градусов, а по оси Y -45 градусов мы наклонили нашу нижнюю стенку вертикально.
Перейдем к оформлению передней стенки.
/* Передняя стенка */
.bar-foreground {
z-index: 3; /* будет выше .bar-background и .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);
}
/* Верхняя часть передней стенки */
.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; /* будет выше .bar-background */
top: auto; /* reset position top */
background-color: rgba(5, 62, 123, .6);
height: 0;
bottom: -2.5em;
color: transparent; /* спрячем текстовые значения */
transition: height 1s linear, bottom 1s linear;
}
/* Правая стенка */
.bar-inner:before {
background-color: rgba(5, 62, 123, .6);
}
/* Верхняя стенка */
.bar-inner:after {
background-color: rgba(47, 83, 122, .7);
}
Мы настроили наши столбики и теперь можем заняться внешним оформлением:
<ul class="graph-container">
<li>
<span>2013</span>
<-- HTML разметка столбика -->
</li>
<li>
<span>2014</span>
<-- HTML разметка столбика -->
</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>
Как уже говорилось ранее, мы используем неупорядоченный список и теги span для размещения меток по осям 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 градусов и немного смещаем влево на 1/2 высоты (т.к. трансформирование произошло относительно оси, проходящей через середину левого и правого края нашего нижнего блока). В результате этого смещения нижняя часть оказывается вплотную пристыкованной к левой стенке. Далее задаем 100% ширину для того, чтобы быть уверенными в том, что она заполняет все отведенное под не пространство. Теперь займемся левой стороной:
/* 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 градусов, и немного сместили его вниз на 1/2 ширины. Теперь займемся стилизацией наших столбиков:
/* 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);
}
Поясню что здесь происходит. Мы выстраиваем наши столбцы рядом друг с другом с помощью свойства float. После этого, мы добавляем отступ справа к последнему столбику диаграммы. Сделано это для того, чтобы показать правый нижний угол. Попробуйте убрать этот параметр, и вы сразу увидите, что из этого получится. И, в заключении, мы стилизуем наши метки по оси X.
OK, мы почти закончили. Теперь осталось стилизовать маркеры по оси Y и можно будет перевести дух:
/* Контейнер с маркерами */
.graph-container > li:last-child {
width: 100%;
position: absolute;
left: 0;
bottom: 0;
}
/* Маркеры по оси Y */
.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 */
.graph-marker-container li:after {
width: 100%;
bottom: 0;
left: 2.5em;
}
/* Подпись по оси Y */
.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 и расположить span-элемент таким образом, чтобы метки оси Y были вне диаграммы. С помощью :before и :after мы можем сделать это достаточно легко и не замусорить html код.
Итак, мы закончили стилизацию нашей диаграммы, но не указали такие важные переменные как размеры, цвета и уровень заполнения столбцов! Дело в том, что наша диаграмма будет настраиваемой, поэтому мы решили не путать переменные с остальным кодом, чтобы вы могли самостоятельно их изменить.
/* РАЗМЕРЫ */
/* Размер Графа */
.graph-container,
.bar-container {
font-size: 8px;
}
/* Высота столбиков */
.bar-container,
.graph-container:after,
.graph-container > li:last-child {
height: 40em;
}
/****************
* ОТСТУПЫ *
****************/
.graph-container > li .bar-container {
margin-right: 1.5em;
}
.graph-container > li:first-child {
margin-left: 1.5em;
}
.graph-container > li:nth-last-child(2) .bar-container {
margin-right: 1.5em;
}
/****************
* ЦВЕТА *
****************/
.bar-background {
background-color: rgba(160, 160, 160, .1);
}
.bar-background:before {
background-color: rgba(160, 160, 160, .2);
}
.bar-background:after {
background-color: rgba(160, 160, 160, .05);
}
.bar-foreground {
background-color: rgba(160, 160, 160, .1);
}
.bar-inner,
.bar-inner:before { background-color: rgba(5, 62, 123, .6); }
.bar-inner:after { background-color: rgba(47, 83, 122, .7); }
/*************************************
* Заполнение 3 столбиков цветом *
*************************************/
.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() и rotate() для трансформации наших элементов таким образом, чтобы вместе они создавали ощущение трехмерного пространства
- псевдо-классы :before и :after нужны для генерации элементов посредством CSS и для простоты разметки HTML
- псевдо-классы :nth-last-child() и :not нужны для определения конкретных пунктов списка и для того, чтобы избежать добавления дополнительных классов/id в разметку
- linear-gradient вместе с background-position нужны для частичного заполнения элемента фоном
- rgba() для полупрозрачного фона
- border: для создания фигур типа треугольника