Яндекс.Метрика

Дизайн-журнал №1. Актуальная информация для дизайнеров, веб дизайнеров, программистов и разработчиков сайтов.

Анимированный 3D график с помощью CSS3

7 ноября 2012 | Опубликовано в css | 2 Комментариев »

CSS tutorial previewВсё началось с небольшого эксперимента, в котором я хотел попробовать свои силы в создании 3D графика на чистом CSS. Как именно достичь этого, мы сейчас рассмотрим в данном уроке.

 

Для начала, вот несколько ключевых требований к нашему графику:

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

CSS tutorial

 

Планировка проекта это самая важная его часть.

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

Задача #1 — Столбец с двигающимся внутренним блоком

Что мы знаем:
— Столбец должен выглядеть как 3D куб с шестью сторонами
— Внутренний блок должен иметь возможность двигаться по вертикали. И еще должна быть опция для скрытия блока.

Что нам понадобится:
— 1 див для заднего корпуса из 3 сторон (задняя сторона, нижняя и левая)
— 1 див для переднего корпуса состоящего из 3 сторон (передняя сторона, верхняя и правая)
— 1 див для внутреннего блока сосотоящего из 3 сторон как и Передний корпус, но с нижним z-индексом
— 1 див контейнер для размещения всех трёх частей
— 1 див контейнер с «overflow: hidden» чтобы скрыть внутренний блок под столбцом в том случае, если он опускается до нуля.

Это дает нам в целом 5 дивов.

Вы можете спросить, зачем нам 2 контейнера? Ну что ж, я постараюсь объяснить. Нам необходим как минимум один контейнер для каждого столбца (чтобы держать передний корпус, задний корпус и внутренний блок). Мы так же знаем, что наш столбец должен быть маштабируемым, и поэтому мы используем проценты, чтобы управлять значением наполнения слобца. А это требует равной высоты между контейнером и одной из сторон столбца.
Теперь, вроде бы, все прояснилось, но нет, постойте-ка. Похоже, появилась другая проблема – должна быть возможность спрятать внутренний блок в движении, а это значит, что он должен опуститься «ниже слобца» и быть там невидим. Вы можете предположить, что для этого у нас уже есть решение – «overflow: hidden». И это так, но не для нашего контейнера, так как его высота ниже чем высота столбца. Вот почему мы добавляем еще один контейнер сверху и применяем «overflow: hidden». Надеюсь, что это объяснение помогло.

Двигаемся дальше.

Задача #2 – Держатель Графика

Держатель графика должен быть:
— представлен в 3D, с осями и тремя сторонами (фон, низ, левая сторона)
— независимость от фона
— адаптивность к количеству столбцов и их атрибутам (высота, ширина и т.д. )
— иметь внешние обозначения осей X и Y

Что нам потребуется:
— 1 неупорядоченный список
— 1 элемент внутри каждого элемента списка для обозначения оси X
— 1 столбец внутри каждого элемента списка
— 1 элемент списка с неупорядоченным списком внутри для обозначения оси Y

Но почему же неупорядоченный список? Разве не было бы содержательней использовать определяющий список для такого графика? Возможно, так бы оно и было, но не для данного задания, так как нам необходимо обернуть каждый столбец и его значение оси X в контейнере для их относительного позиционирования.

Звучит хорошо, но опять таки, почему же мы не используем элемент списка взамен второго контейнера столбца? Мы не можем этого сделать, так как нам необходимо разместить обозначение оси 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 на 12.5em. Это число является суммой широт передней и верхней сторон столбца (в нашем случае это 10+2.5=12.5). Мы так же используем рамки для придания формы треугольнику, и помещаем его в нижний правый угол .bar-container, для того чтобы убедиться, что нижняя сторона столбца «обрезается» при вертикальном движении. Здесь мы используем :before псевдо класс, чтобы сгенерировать этот элемент. В данном уроке мы будем часто использовать :before и :after псевдо классы.

Теперь давайте стилизуем задний корпус:

/* 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 градусов. Обратите внимание, что мы установили первое значение наклона на 0 градусов, а второе значение установили на -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 градусов и сдвигаем немного влево, чтоб поставить ее на правильное место.

Займемся стилизацией левой стороны нашего держателя графика.

/* 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() and transform: rotate() для того, чтобы трансформировать наши элементы таким образом, чтобы они создавали иллюзию 3D объекта
— :before и :after псевдо классы для генерирования элементов CSS и для содержания HTML кода в чистоте
— :nth-last-child() и :not псевдо классы для фокусировки определенных элементах списка и избегания добавления лишних классов в разметке
— линейный градиент вместе с фоновым – позиционирование на частичную заливку элемента фоном
— rgba() для цветов с альфа прозрачностью
— borders для создания треугольных форм.
Искренне надеюсь, что этот урок был вам интересен и полезен.

По материалам Tympanus




Комментарии

  1. Zedddy
    Thumb up Thumb down 0

    Спасибо, очень доходчиво расписано, а где можно увидеть готовый пример?

  2. lanc
    Thumb up Thumb down 0

    вот ссылка tympanus.net/codrops/2012...chart-with-css3/