Создание анимированного 3D графика на CSS3

Все началось с небольшого эксперимента из учебника +Nettuts, который рассказывает, как встроить 3D-графики в HTML-страницы при помощи CSS, изображений и JavaScript. После прочтения урока я загорелся желанием превратить эту идею на одном лишь CSS и посмотреть, как далеко я могу её усовершенствовать. Первоначальная задача заключалась в создании классических полупрозрачных 3D блоков, с 6 сторонами. Наша задача состоит в том, чтобы создать 3D-график.

Сложность

css3 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.

Хорошо, но почему бы не использовать элемент li, а использовать второй контейнер? Мы не можем этого сделать, потому что мы должны поставить метки по оси Х вне графика и так как верхняя часть блока скрывает данные которые переполняют его, мы будем использовать элементы списка, чтобы убедиться в том, что все элементы расположены правильно.

Реализация

Теперь у нас есть план, давайте преобразуем его в код.

Проблема № 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 {
  overflow: hidden;
}

.bar-container {
  position: relative;
  margin-top: 2.5em; 
  width: 12.5em; 
}

.bar-container:before {
  content: "";
  position: absolute;
  z-index: 3;
 
  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's на 12.5em. Это число является суммой значений ширины передней стороны и правой стороны - в нашем примере это 10 + 2,5 = 12,5

Мы также используем рамки, чтобы сформировать треугольник и поместить его в правом нижнем углу .bar-container, для того, чтобы стороны внутреннего блока уменьшались, когда он перемещается по вертикали. Мы использовали псевдо-класс для создания этого элемента.

Стиль задней части блока:

.bar-background {
  width: 10em;
  height: 100%;
  position: absolute;
  top: -2.5em;
  left: 2.5em;
  z-index: 1; 
}
 
.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%;
 
  transform: skew(0deg, -45deg);
}

Передвигаем корпус на 2.5em вверх и вправо. Исказили левую и нижнюю стороны на 45 градусов. Обратите внимание, что мы установили первое значение наклона 0deg, а второе в 45 градусов, что позволяет нам, исказить этот элемент по вертикали.

Передняя часть блока:

.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;
}
 
.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;
  top: auto; 
  background-color: rgba(5, 62, 123, .6);
  height: 0;
  bottom: -2.5em;
  color: transparent; /* hide text values */
 
  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);
}

Проблема № 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-container:before {
  position: absolute;
  content: "";
 
  bottom: 0;
  left: -1.25em; 
 
  width: 100%;
 
  height: 2.5em;
  background-color: rgba(183, 183, 183, 1);
 
  transform: skew(-45deg);
}

Наклоним на 45 градусов и переместим её немного влево, чтобы убедиться, что она расположена правильно. Мы дали ей 50% ширины, но убедитесь, что она заполняет промежуток перед первым блоком.

Стиль фиксатора:

.graph-container:after {
  position: absolute;
  content: "";
 
  top: 1.25em; 
  left: 0em;
 
  width: 2.5em;
  background-color: rgba(28, 29, 30, .4);
 
  transform: skew(0deg, -45deg);
}

Здесь ничего особенного. Просто перекос элемента на 45 градусов, как обычно, и переместили его немного вниз.

Стиль для элементов списка которые держат наши блоки:

.graph-container > li {
  float: left; 
  position: relative; 
}

.graph-container > li:nth-last-child(2) {
  margin-right: 2.5em;
}

.graph-container > li > span {
  position: absolute;
  left: 0;
  bottom: -2em;
  width: 80%; 
  text-align: center;
 
  font-size: 1.5em;
  color: rgba(200, 200, 200, .4);
}

Итак что мы здесь сделали: во-первых, поставили наши блоки рядом друг с другом. Во-вторых, добавили правую рамку до последнего блока.

Хорошо, мы почти у цели. Последнее, что осталось добавить, это маркеры Y-оси.

.graph-container > li:last-child {
  width: 100%;
  position: absolute;
  left: 0;
  bottom: 0;
}
 
.graph-marker-container > li {
  position: absolute;
  left: -2.5em;
  bottom: 0;
  width: 100%;
  margin-bottom: 2.5em;
  list-style: none;
}
 
.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);
}
 
.graph-marker-container > li:before {
  width: 3.55em;
  height: 0;
  bottom: -1.22em;
  left: -.55em;
  z-index: 2; 
  transform: rotate(-45deg);
}
 
.graph-marker-container li:after {
  width: 100%;
  bottom: 0;
  left: 2.5em;
}
 
.graph-marker-container span {
  color: rgba(200, 200, 200, .4);
  position: absolute;
 
  top: 1em;
  left: -3.5em; 
  width: 3.5em; 
 
  font-size: 1.5em;
}

Как видите, мы устанавливаем 100% ширину нашему фиксатору маркеров, для этого использовали пунктирную линию для оси Y и расположили диапазон элементов таким образом, что все метки 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); }
 
.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  для создания формы треугольника

Перевод статьи Animated 3D Bar Chart with CSS3

Тэги: 3D animatechart

Вход

Уважаемый пользователь! Мы обнаружили, что вы используете AdBlock и вынуждены скрыть часть материалов на нашем сайте. Siteacademy существует и развивается за счет доходов от рекламы. Просим внести наш сайт в список исключений или отключить Блокировщик рекламы на нашем сайте.