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"/>

实现效果

总结

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

相关推荐
zero15971 小时前
TypeScript 快速实战系列:核心进阶|接口(Interface) + 类型(Type):大模型开发神器
前端·typescript·大模型编程语言
万邦科技Lafite1 小时前
通过淘宝关键词API接口批量获取商品信息指南
java·前端·javascript
天天进步20151 小时前
[前端篇] 桌面端与 AI 的碰撞:Toonflow 基于 Electron 的高效交互实现
前端·人工智能·electron
J超会运1 小时前
OpenEuler24.03 LVS+Keepalived实战指南
linux·服务器·前端
sycmancia1 小时前
Qt——布局管理区(二)
开发语言·前端·qt
勇哥是也1 小时前
前端也能玩 AI:阿里云百炼流式对话开发
前端·人工智能·阿里云
夜影风1 小时前
Prompt Engineering(提示词工程) vs. Agent Skills(智能体技能):从“口头吩咐”到“标准化操作手册”的进化
前端·人工智能·prompt
落魄江湖行1 小时前
基础篇九 Nuxt4 插件系统:扩展 Nuxt 能力
前端·vue.js·typescript·nuxt4
程序员小寒1 小时前
JavaScript设计模式(十):模板方法模式实现与应用
前端·javascript·设计模式·模板方法模式
Bigger1 小时前
第六章:我是如何剖析 Claude Code 的终端界面渲染原理的
前端·react.js·claude