关于如何使用原生HTML + JS + CSS绘制简单折线柱状图

前言

CSS确实很重要,且有点奇技淫巧,看起来规则十分简单,但是创意更重要,如何用css构造出自己想要的效果,写的代码好看优雅十分重要。 在看了不借助Echarts等图形框架原生JS快速实现折线图效果并自己重新实现了以后,实在是感慨CSS的强大之处,并作出记录。

正文

先上结果图:

结果

总结下自己觉得关于几点比较难以理解的点:

1. 如何实现以下效果:

一个DIV
以上是由一个div配合其after伪元素完成的

  • 中间的横线其实很简单,在我之前关于背景渐变的文章里有提到,直接 上代码:
<style>
.chartX {
  width: 670px;
  height: 250px;
  position: relative;
  border-top: 1px solid #ccc;
  margin: 100px auto;
  background: linear-gradient(to top, #ccc 0, #ccc 1px, transparent 1px);
  background-size: 100% 50px;
  font-size: 0;
  text-align: center;
}
</style>
<div class="chartX">
</div>
复制代码
  • 难点在于如何实现侧边栏的数字排列? 先上结论:
  1. 从0 - 100其实是在一个盒子里,这个盒子的高度应该是由line-height来确定的
  2. 由于line-height具有垂直居中的特点,只要让line boxes排在左边就好了
  3. 利用absolute定位来定位该盒子

再上代码:

<style>
.chartX::after {
  content: '100 \a 80 \a 60 \a 40 \a 20 \a 0 ';
  line-height: 50px;
  white-space: pre;
  position: absolute;
  font-size: 12px;
  top: -25px;
  left: -2rem;
  text-align: right;
}
</style>
复制代码

最后解释: 我们应该让每一个元素占据一行,且这一行的高度和背景横线之间的间距相等然后让其中的文字居中显示,这样就有6行文字分别与背景线对齐了。 所以我们要做的第一点就是写出6行文字:即代码中的

content: '100 \a 80 \a 60 \a 40 \a 20 \a 0 ';
white-space: pre;
复制代码

content定义了内容,‘\a' 为换行,同时设置 white-space: pre;保持文字的空格符和换行,说白了就是让每个数字间换行,于是就有了从上至下排列的 100 80 60 40 20 0这样一列数字。

上一步完成后就需要保证每一行的高度为横线间距相等在本文中即为:50px。怎么做呢?其实在我的之前一篇文章中的关于CSS:line-height中有了答案,在没有height属性下,我们通过line-height来控制盒子的高度,即:

line-height: 50px;
复制代码

这样每一行都是50px的高度,再将盒子整体往上移动25px就做到了使得背景横线与line-height的中线处于同一高度,即数字被横线纵向对半分割。

完成了坐标系的绘制后,应该实现柱状图的绘制

单个柱状图怎么绘制

如何实现下面的这个效果呢?

柱状图

几个点注意一下:

  • 如何再底部显示月份?
  • 如何绘制中间的圆点?

直接上代码:

<style>
.result-bg {
  display: inline-block;
  position: relative;
  width: calc((100% - 16px * 13) / 12);
  height: 100%;
  background: #eee;
}
.result-bg::after {
  content: attr(data-month)'月';
  font-size: 12px;
  color: grey;
  position: absolute;
  bottom: -1rem;
  left: 0;
  right: 0;
}
.dot {
  border: 2px solid #97cd74;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #fff;
  position: absolute;
  left: 0;
  right: 0;
  top: 15px;
  margin: auto;
}
</style>
<div class="chartX">
    <div class="result-bg" data-month="1">
      <div class="result-bar" style="height: 82%">
        <div class="dot"></div>
      </div>
    </div>
<div>
复制代码

再来解释: 首先式底部文字的问题: 使用伪元素after的content属性 这里要普及以下,content属性中可以使用attr了,即获取元素的自定义属性值,所以才有了以上代码:

 content: attr(data-month)'月';
 <div class="result-bg" data-month="1">
复制代码

配合absolute定位自然绘出了文字

至于中间的点的问题: 画出一个这个样式的点不稀奇,如何让它居中呢?参考我的另一篇文章:关于CSS:关于absolute定位 即让bounding-box的宽度等同于父元素高度,然后我们让圆点的margin为auto自然就居中啦,表现代码如上,不做多余解释,需要读者自行尝试代码。

多个柱状图的圆点间怎么连线

需要实现下面的效果:

两个

这里就真的只能用到js了,因为要手动计算距离和旋转的角度啊

总结关键点:

  1. 动态计算出两点之间的距离
  2. 计算出需要偏转的角度
  3. 利用transform:rotate()来实现旋转

以上都不是难点,需要注意的是,rotate的时候需要以左边端点为中心进行旋转。先上线段的CSS代码:

<style>
.dot i {
  display: inline-block;
  box-sizing: border-box;
  position: absolute;
  left: 50%;
  top: 50%;
  margin-top: -1px;
  height: 2px;
  background: #97cd74;
  border-right: 3px solid #fff;
  border-left: 3px solid #fff;
  transform-origin: left;
  z-index: 1;
}
</style>
<div class="chartX">
    <div class="result-bg" data-month="11">
      <div class="result-bar" style="height: 82%">
        <div class="dot">
            <i></i>
        </div>
      </div>
    </div>
    <div class="result-bg" data-month="12">
      <div class="result-bar" style="height: 62%">
        <div class="dot">
            <i></i>
        </div>
      </div>
    </div>
<div>
复制代码

i标签就是我们的线段啦,然后为线段设置背景颜色即可 其中的transform-origin:left即为设置旋转中心点

最后只需要用js来动态的给每个柱子添加i标签,并设置其长度和旋转角度就可以了,代码如下:

const bars = document.querySelectorAll('.result-bar .dot')
bars.forEach((bar, index) => {
  const nextBar = bars[index + 1]
  if (!nextBar) {
    return
  }
  let elLine = bar.querySelector('i')
  if (!elLine) {
    elLine = document.createElement('i')
    elLine.setAttribute('line', '')
    bar.appendChild(elLine)
  }
  // 计算线段长度和旋转弧度
  let boundThis = bar.getBoundingClientRect(),
      boundNext = nextBar.getBoundingClientRect(),
      x1 = boundThis.left,
      y1 = boundThis.top,
      x2 = boundNext.left,
      y2 = boundNext.top,
      distance = Math.sqrt(Math.pow((x1 - x2), 2) + Math.pow((y1 - y2), 2)),
      radius = Math.atan((y2 - y1) / (x2 - x1))
  console.log(distance, radius)
  elLine.style.width = `${distance}px`
  elLine.style.transform=`rotate(${radius}rad)`
})
复制代码

至此结束。

结语

以上实现方式真的只是拾人牙慧,能够从张鑫旭前辈的代码中学习到这么多东西真的感到敬畏。 以上完整代码均在github中,欢迎指正学习。

猜你喜欢

转载自juejin.im/post/5bff5fc4518825275318935c
今日推荐