关于如何使用原生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中,欢迎指正学习。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容

  • 问答题47 /72 常见浏览器兼容性问题与解决方案? 参考答案 (1)浏览器兼容问题一:不同浏览器的标签默认的外补...
    _Yfling阅读 13,725评论 1 92
  • 1. 前言 前端圈有个“梗”:在面试时,问个css的position属性能刷掉一半人,其中不乏工作四五年的同学。在...
    YjWorld阅读 4,422评论 5 15
  • 今天学习内容
    无所谓啦无所谓啊阅读 173评论 0 0
  • 前段时间,我去点了一颗痣。 主要是想验证一下那家店的技术如何:因为最终目的不是这颗,而是眉头上那颗。自从我结婚后,...
    静远珞珞阅读 1,318评论 2 2
  • 我和你的时间, 从未有过同步, 或是你快、或是我慢, 或是向前、或是后退, 本该一起的瞬间, 如流星般划过天边。 ...
    雅俗共赏Y阅读 212评论 2 10