關(guān)于如何使用原生HTML + JS + CSS繪制簡單折線柱狀圖

前言

CSS確實很重要,且有點奇技淫巧,看起來規(guī)則十分簡單,但是創(chuàng)意更重要,如何用css構(gòu)造出自己想要的效果,寫的代碼好看優(yōu)雅十分重要。
在看了不借助Echarts等圖形框架原生JS快速實現(xiàn)折線圖效果并自己重新實現(xiàn)了以后,實在是感慨CSS的強大之處,并作出記錄。

正文

先上結(jié)果圖:


結(jié)果

總結(jié)下自己覺得關(guān)于幾點比較難以理解的點:

1. 如何實現(xiàn)以下效果:

一個DIV

以上是由一個div配合其after偽元素完成的

  • 中間的橫線其實很簡單,在我之前關(guān)于背景漸變的文章里有提到,直接 上代碼:
<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>
  • 難點在于如何實現(xiàn)側(cè)邊欄的數(shù)字排列?
    先上結(jié)論:
  1. 從0 - 100其實是在一個盒子里,這個盒子的高度應(yīng)該是由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>

最后解釋:
我們應(yīng)該讓每一個元素占據(jù)一行,且這一行的高度和背景橫線之間的間距相等然后讓其中的文字居中顯示,這樣就有6行文字分別與背景線對齊了。
所以我們要做的第一點就是寫出6行文字:即代碼中的

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

content定義了內(nèi)容,‘\a' 為換行,同時設(shè)置 white-space: pre;保持文字的空格符和換行,說白了就是讓每個數(shù)字間換行,于是就有了從上至下排列的 100 80 60 40 20 0這樣一列數(shù)字。

上一步完成后就需要保證每一行的高度為橫線間距相等在本文中即為:50px。怎么做呢?其實在我的之前一篇文章中的關(guān)于CSS:line-height中有了答案,在沒有height屬性下,我們通過line-height來控制盒子的高度,即:

line-height: 50px;

這樣每一行都是50px的高度,再將盒子整體往上移動25px就做到了使得背景橫線與line-height的中線處于同一高度,即數(shù)字被橫線縱向?qū)Π敕指睢?/p>

完成了坐標(biāo)系的繪制后,應(yīng)該實現(xiàn)柱狀圖的繪制

單個柱狀圖怎么繪制

如何實現(xiàn)下面的這個效果呢?

柱狀圖

幾個點注意一下:

  • 如何再底部顯示月份?
  • 如何繪制中間的圓點?

直接上代碼:

<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定位自然繪出了文字

至于中間的點的問題:
畫出一個這個樣式的點不稀奇,如何讓它居中呢?參考我的另一篇文章:關(guān)于CSS:關(guān)于absolute定位
即讓bounding-box的寬度等同于父元素高度,然后我們讓圓點的margin為auto自然就居中啦,表現(xiàn)代碼如上,不做多余解釋,需要讀者自行嘗試代碼。

多個柱狀圖的圓點間怎么連線

需要實現(xiàn)下面的效果:


兩個

這里就真的只能用到j(luò)s了,因為要手動計算距離和旋轉(zhuǎn)的角度啊

總結(jié)關(guān)鍵點:

  1. 動態(tài)計算出兩點之間的距離
  2. 計算出需要偏轉(zhuǎn)的角度
  3. 利用transform:rotate()來實現(xiàn)旋轉(zhuǎn)

以上都不是難點,需要注意的是,rotate的時候需要以左邊端點為中心進行旋轉(zhuǎn)。先上線段的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標(biāo)簽就是我們的線段啦,然后為線段設(shè)置背景顏色即可
其中的transform-origin:left即為設(shè)置旋轉(zhuǎn)中心點

最后只需要用js來動態(tài)的給每個柱子添加i標(biāo)簽,并設(shè)置其長度和旋轉(zhuǎn)角度就可以了,代碼如下:

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)
  }
  // 計算線段長度和旋轉(zhuǎn)弧度
  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)`
})

至此結(jié)束。

結(jié)語

以上實現(xiàn)方式真的只是拾人牙慧,能夠從張鑫旭前輩的代碼中學(xué)習(xí)到這么多東西真的感到敬畏。
以上完整代碼均在github中,歡迎指正學(xué)習(xí)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 問答題47 /72 常見瀏覽器兼容性問題與解決方案? 參考答案 (1)瀏覽器兼容問題一:不同瀏覽器的標(biāo)簽?zāi)J的外補...
    _Yfling閱讀 14,093評論 1 92
  • 1. 前言 前端圈有個“?!保涸诿嬖嚂r,問個css的position屬性能刷掉一半人,其中不乏工作四五年的同學(xué)。在...
    YjWorld閱讀 4,877評論 5 15
  • 今天學(xué)習(xí)內(nèi)容
    無所謂啦無所謂啊閱讀 182評論 0 0
  • 前段時間,我去點了一顆痣。 主要是想驗證一下那家店的技術(shù)如何:因為最終目的不是這顆,而是眉頭上那顆。自從我結(jié)婚后,...
    靜遠珞珞閱讀 1,476評論 2 2
  • 我和你的時間, 從未有過同步, 或是你快、或是我慢, 或是向前、或是后退, 本該一起的瞬間, 如流星般劃過天邊。 ...
    雅俗共賞Y閱讀 248評論 2 10

友情鏈接更多精彩內(nèi)容