CSS实现百分比水柱图

背景

在echarts没发现有可以直接使用的展示百分比的柱形图,只好自己封装一个组件使用

实现思路

一、图形拆解

要实现的组件是一个 可配置的圆柱形液柱图组件,常用于展示比例进度,比如任务完成度、指标达成率等。把图拆成最小单元然后拼接起来,如下表:

功能点 描述
1. 显示进度柱 高度随 num / target 变化的水柱视觉效果
2. 圆柱外形 顶部、底部使用椭圆(border-radius)构造立体感
3. 自定义样式 宽高、颜色可配置
4. 百分比显示 可选是否显示进度数值
5. 动画过渡 数值变化时水柱高度平滑变化

二、结构设计

cpp 复制代码
<div class="lui-column-bg">           <!-- 圆柱容器 -->
  <div class="lui-inner">            <!-- 动态高度水柱 -->
    <div class="lui-inner-text">    <!-- 可选百分比文字 -->
      {{ validPercentage }}%
    </div>
  </div>
</div>
  • .lui-column-bg 是整个圆柱容器,带有顶部/底部的圆帽(伪元素 ::before/::after
  • .lui-inner 是内部水柱块,其 height 是根据 num/target 实时计算的
  • .lui-inner-text 是可选显示的百分比数值

三、 计算逻辑

cpp 复制代码
validPercentage() {
  if (this.target <= 0) return 0;
  const percent = (this.num / this.target) * 100;
  return Math.min(Math.round(percent), 100);
}
  • 避免除以 0 的错误
  • 保证水柱最大不超过 100%
  • Math.round 取整更简洁美观(页面不出现小数点)

四、关键样式

1. .lui-column-bg

cpp 复制代码
.lui-column-bg {
  background-color: #2a4d5e; // 水柱外壳背景色
  position: relative;
  border-radius: 50px;
  overflow: hidden;
}
  • overflow: hidden 确保内部液面圆帽不溢出容器边界
  • 使用 ::before::after 构建顶部与底部的圆帽

2、 .lui-inner

cpp 复制代码
.lui-inner {
  position: absolute;
  bottom: 0;
  transition: height 0.5s ease-in-out;
}
  • bottom: 0 让水面从底部"涨"起来
  • transition 让数值变化动画流畅过渡

3、.lui-inner::before / ::after

  • ::before 是水柱顶部的圆形高光

  • ::after 是底部的白色圆边,用来模拟"液面"

4、.lui-inner-text

cpp 复制代码
.lui-inner-text {
  color: white;
  font-size: 14px;
  font-weight: bold;
  position: relative;
  z-index: 2;
}

显示中间百分比文字,保持在水柱内层上方,不被遮挡

五、可配置参数说明

参数 类型 说明
num Number 当前数值
target Number 目标数值
width Number 圆柱宽度(单位:px)
height Number 圆柱高度
innerColor String 水柱颜色
topColor String 圆柱顶部颜色(备用)
bottomColor String 圆柱底部颜色(备用)
showPercent Boolean 是否显示百分比文字

完整代码

cpp 复制代码
<template>
  <div
    class="lui-column-bg"
    :style="{ width: width + 'px', height: height + 'px' }"
  >
    <div
      class="lui-inner"
      :style="{
        height: validPercentage + '%',
        backgroundImage: `linear-gradient(to top, ${innerColor}, ${innerColor})`
      }"
    >
      <div class="lui-inner-text" v-if="showPercent">
        {{ validPercentage }}%
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'LiquidColumn',
  props: {
    num: { type: Number, default: 0 },
    target: { type: Number, default: 0 },
    width: { type: Number, default: 100 },
    height: { type: Number, default: 190 },
    innerColor: { type: String, default: '#28b0bd' },
    topColor: { type: String, default: '#54d8de' },
    bottomColor: { type: String, default: '#54d8de' },
    showPercent: { type: Boolean, default: true },
  },
  computed: {
    validPercentage() {
      if (this.target <= 0) return 0;
      const percent = ((this.num / this.target) * 100).toFixed(2);
      return Math.min(Math.round(percent), 100); // 取整,防止超过 100%
    },
  },
};
</script>

<style lang="scss" scoped>
.lui-column-bg {
  position: relative;
  width: 100px;
  height: 190px;
  margin: 30px auto;
  background-color: transparent;
  background-color: #2a4d5e;
}
.lui-column-bg:before {
  position: absolute;
  content: "";
  display: block;
  height: 20px;
  width: 100%;
  border-radius: 50%;
  top: -10.5px;
  z-index: 1;
  background-color: rgb(101 221 197);
}

.lui-column-bg:after {
  position: absolute;
  content: "";
  display: block;
  height: 15px;
  width: 100%;
  border-radius: 50%;
  bottom: -10px;
  background-color: #54d8de;
}

.lui-inner {
  position: absolute;
  bottom: 0;
  width: 100%;
  height: 50%;
  background-image: linear-gradient(to top, #28b0bd, #28b0bd);
  text-align: center;
}
.lui-inner::before {
  position: absolute;
  content: "";
  display: block;
  height: 20px;
  width: 100%;
  background-color: #54d8de;
  border-radius: 50%;
  top: -10.5px;
  z-index: 1;
}
.lui-inner:after {
  position: absolute;
  content: "";
  display: block;
  height: 15px;
  width: 100%;
  border-radius: 50%;
  background-color: white;
  bottom: -10px;
}
.lui-inner-text {
  color: white;
  position: absolute;
  top: 10px;
  width: 100%;
  font-size: 18px;
  font-weight: bold;
}
</style>

使用实例

1、引入组件

cpp 复制代码
import LiquidColumn from "../components/LiquidColumn";

2、注册组件

cpp 复制代码
<script>
export default {
  components: { LiquidColumn },
  }
</script>

3、调用组件

cpp 复制代码
<!-- 具体参数自行添加 -->
<liquid-column :num="50" :target="100" :showPercent="false"/>

实现效果

总结

组件相对简单,还可以从水面波动动画、颜色渐变、点击事件等方面去优化。

相关推荐
清幽竹客19 分钟前
vue-37(模拟依赖项进行隔离测试)
前端·vue.js
vvilkim19 分钟前
Nuxt.js 页面与布局系统深度解析:构建高效 Vue 应用的关键
前端·javascript·vue.js
滿23 分钟前
Vue3 父子组件表单滚动到校验错误的位置实现方法
前端·javascript·vue.js
夏梦春蝉1 小时前
ES6从入门到精通:模块化
前端·ecmascript·es6
拓端研究室2 小时前
视频讲解:门槛效应模型Threshold Effect分析数字金融指数与消费结构数据
前端·算法
工一木子3 小时前
URL时间戳参数深度解析:缓存破坏与前端优化的前世今生
前端·缓存
半点寒12W5 小时前
微信小程序实现路由拦截的方法
前端
某公司摸鱼前端6 小时前
uniapp socket 封装 (可拿去直接用)
前端·javascript·websocket·uni-app
要加油哦~6 小时前
vue | 插件 | 移动文件的插件 —— move-file-cli 插件 的安装与使用
前端·javascript·vue.js
小林学习编程6 小时前
Springboot + vue + uni-app小程序web端全套家具商场
前端·vue.js·spring boot