前言
CSS 确实很重要, 且有点奇技淫巧, 看起来规则十分简单, 但是创意更重要, 如何用 CSS 构造出自己想要的效果, 写的代码好看优雅十分重要. 在看了不借助 Echarts 等图形框架原生 JS 快速实现折线图效果并自己重新实现了以后, 实在是感慨 CSS 的强大之处, 并作出记录.
正文
先上结果图:
总结下自己觉得关于几点比较难以理解的点:
1. 如何实现以下效果:
以上是由一个 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>
难点在于如何实现侧边栏的数字排列? 先上结论:
从 0 - 100 其实是在一个盒子里, 这个盒子的高度应该是由 line-height 来确定的
由于 line-height 具有垂直居中的特点, 只要让 line boxes 排在左边就好了
利用 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 了, 因为要手动计算距离和旋转的角度啊
总结关键点:
动态计算出两点之间的距离
计算出需要偏转的角度
利用 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)`
- })
至此结束.
结语
以上实现方式真的只是拾人牙慧, 能够从张鑫旭前辈的代码中学习到这么多东西真的感到敬畏. 以上完整代码均在中, 欢迎指正学习.
来源: https://juejin.im/post/5bff5fc4518825275318935c