天天看點

純CSS3實作柱狀圖的3D立體動畫效果

純CSS3實作柱狀圖的3D立體動畫效果

首先,我們看一看要實作的效果:

純CSS3實作柱狀圖的3D立體動畫效果

今天這篇文章所實作的動畫效果起源于一個小小的想法,這個想法來自于另一個網站的一篇文章,它介紹了如何在網頁中使用CSS、圖檔和JavaScript建立立體的柱狀圖。在閱讀了那篇文章之後,我想挑戰一下,嘗試使用純CSS來實作相同的效果。一開始的難點在于建立一個六面半透明的立方體,而後面的難點在于如何建立一個完整的帶有動畫效果的3D柱狀圖。

下面,我們就一起來看一下如何解決這些難點。

讓我們先列舉一些要實作的要求,我們所實作的柱狀圖應該是:

  • 背景獨立(即柱狀圖與背景互不影響)
  • 自适應的(柱子數量的多少不會影響布局)
  • 可縮放(如矢量圖一樣)
  • 易于定制(顔色、尺寸和比例)

計劃是任何項目中最重要的一個部分。是以我們要先制定一個計劃。

在實際編碼之前,我通常會列出項目中我會遇到的潛在挑戰和解決這些挑戰的方案,然後重複這個過程,直到我得到一個看起來可以執行的政策的東西。下面是我為這個項目提出的挑戰清單和解決方案:

挑戰1 - 帶有可伸縮核心的柱子

我們知道:

  • 一個柱狀圖是由6個面組成的立體圖形
  • 這個柱狀圖的核心是可以垂直伸縮的,并且有一個選項可以隐藏它

是以,我們需要:

  • 一個div,生成柱狀圖的三個面(背部、底部、左側)
  • 一個div,生成柱狀圖的另三個面(正面、頂部、右側)
  • 一個div,生成核心柱體的三個面,與上面的第二個div類似,但是它的z-index值要小
  • 一個div,作為容器,用于定位以上的三個元件,并且在右下角實作一個實色的背景
  • 一個div,帶有overflow: hidden的容器,用于核心柱體的高度為0時,隐藏它。

總共有五個div。

你可能想知道為什麼我們需要兩個容器?嗯,這是一個不好解釋的問題,但我會嘗試着說明清楚。

每個柱體我們需要至少一個容器(以保證前三個div的位置),由于我們的柱體核心是可伸縮的,是以我們使用百分比來操縱核心的高度,這就要求容器的高度等于條形圖Y軸的高度。

這看起來很好,但是,有另外一個問題,應該有一個選項可以隐藏移動中的核心,這意味着它應該“低于柱體”并且隐藏。你可能會說有一個解決方法 - overflow: hidden,是的,但是它不适用于這裡的容器,因為它的高度要比實際的柱體高度短,這就是我們為什麼要添加另一個容器的原因。

希望我說清楚了,下面我們繼續。

挑戰2 - 坐标軸

坐标軸應該:

  • 是一個三維立體坐标軸,它包含3個面(背景面,X軸面,Y軸面)
  • 背景獨立
  • 自适應柱體的數量和屬性(width,height等)
  • 外側有X軸與Y軸的文字标簽

是以,我們需要:

  • 一個無序清單
  • X軸标簽的每個清單項内有一個span元素
  • 每個清單項中有一個柱體
  • Y軸标簽内包含一個無序清單

實作

現在我們有了一個總體的計劃,讓我們把它轉換成代碼。

請注意,文章中的代碼沒有寫浏覽器字首。在實際的項目中請不要省略。

挑戰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 – 生成柱狀圖的三個面(背部、底部、左側)
  • bar-inner – 最重要的部分 – 柱子核心
  • bar-foreground – 生成柱狀圖的另三個面(正面、頂部、右側)

首先,讓我們設定容器的樣式。

/* Bar wrapper容器 - 當核心低于柱體高度時隐藏核心,必需的 */
.bar-wrapper {
  overflow: hidden;
}
/* Bar container容器 - 這家夥是柱形圖裡真正的家長——子元素都是相對于它定位的。*/
.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的寬度設定為12.5em。這個數字是柱體的正面和右側寬度的總和-在我們的示例中,它是10+2.5=12.5。

我們還使用border屬性來建立三角形,并将其放置在 .bar-container的右下角,以確定核心的側邊在垂直移動時能被“切割”。我們使用:before僞類來生成這個元素。

下面我們來設定.bar-background:

/* 背面 */
.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%;


  /* 僅傾斜Y軸 */
  transform: skew(0deg, -45deg);
}      

如你所見,我們将.bar-background向上和向右移動2.5em。當然,我們把左後面和底面傾斜45度。請注意,:after僞元素中将第一個傾斜值設定為0deg,第二個設定為-45度,這樣隻傾斜元素的Y軸。

接着來設定.bar-foreground:

/* 前面 */
.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;
}


/* 右前面 */
.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-background的樣式一樣,隻是方向不同。

其中,部分的樣式同時應用在了.bar-foreground和.bar-inner元素上,因為它們的樣子是完全相同的。

好了,下面我們繼續設定核心的樣式。

.bar-inner {
  z-index: 2; /* 在.bar-background的上面 */
  top: auto; /* 重置 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);
}      

好了,柱體的樣式就設定好了,接下來我們來看坐标軸。

挑戰2 - 坐标軸

<ul class="graph-container">
  <li>
    <span>2011</span>
    <-- 此處顯示柱狀圖圖的HTML标記 -->
  </li>
  <li>
    <span>2012</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-container {
  position: relative; 
  display: inline-block; 
  padding: 0; 
  list-style: none; /* 去除清單元素自帶的小黑點 */


  /* 背景 */
  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度,是以右下角有一個空白區域。

坐标軸的X軸樣式:

/* X軸 */
.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度,然後向左移動一點,以確定它的位置正确。

坐标軸的Y軸樣式:

/* Y軸 */
.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;
}
/* X軸的文字标簽 */
.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);
}      

這裡有兩個要注意的點。首先,我們使用浮動将柱體水準排列。通常情況下都應該非常小心地使用浮動,它會帶來高度塌陷等布局問題。是以,在這裡你可以嘗試變為設定display:inline-block來實作。

第二,我們在最後一個柱體上添加了一些右邊距。這樣我們就可以確定給坐标軸底部留出足夠的空間,試着去掉它,你就會明白我的意思。

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軸線條正常樣式 */
.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軸側線 */
.graph-marker-container > li:before {
  width: 3.55em;
  height: 0;
  bottom: -1.22em;
  left: -.55em;
  z-index: 2; 
  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; 
  width: 3.5em; 


  font-size: 1.5em;
}      

如你所見,我們将文字标記容器的寬度設定為100%,使得背景線能夠覆寫整個坐标軸,使用虛線邊框設定Y軸線條的樣式并定位span元素,使文字标簽位于坐标軸的外側。使用: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()用于傾斜和旋轉元素,它們組合起來使元素模拟産生三維立體的效果。
  • :before和:after僞元素可以保持HTML标記相對幹淨
  • :nth-last-child()和:not是針對特定清單項的僞類,可以避免向HTML中添加額外的類/id。
  • linear-gradient和background-position一起使用可以實作背景的部分填充
  • rgba()可以實作具有透明度的顔色
  • border屬性可以建立三角形形狀

感謝閱讀。