纯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 - 带有可伸缩内核的柱子
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-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-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-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);}
<ul class="graph-container"><li><span>2011span><-- 此处显示柱状图图的HTML标记 -->li><li><span>2012span><-- 此处显示柱状图图的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>
/** 坐标轴容器 **/.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;}
/* 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);}
/* 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);}
.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);}
/* 文字标记的容器 */.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;}
/***************** 尺寸 *****************//* 图表的整体大小 */.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属性可以创建三角形形状

