Vue3 + ECharts 水波纹组件开发完整指南
前言
在现代 Web 应用中,数据可视化越来越重要。水波纹图表作为一种直观的百分比展示方式,广泛应用于仪表盘、监控系统等场景。本文将详细介绍如何在 Vue3 项目中使用 ECharts 和 echarts-liquidfill 插件创建一个功能完整、动画流畅的水波纹组件。
效果预览
最终实现的水波纹组件具有以下特点:
- 🌊 流畅的水波动画效果
- 🎨 可自定义颜色和尺寸
- 📊 支持百分比数据展示
- ⚡ 响应式设计,支持窗口缩放
- 🔧 丰富的配置选项
技术栈
- Vue 3 - 前端框架
- TypeScript - 类型支持
- ECharts 5.6.0 - 图表库
- echarts-liquidfill 3.1.0 - 水波纹插件
安装依赖
首先安装必要的依赖包:
npm install echarts echarts-liquidfill
核心组件实现
1. 组件结构设计
创建 EChartsLiquidFill.vue 组件:
<template>
<div class="echarts-liquid-wrapper" :style="{ width, height }">
<div ref="chartRef" :style="{ width: '100%', height: '100%' }"></div>
</div>
</template>
2. TypeScript 接口定义
interface Props {
/** 0-1 或 0-100 的百分比 */
percent: number;
/** 文本标签 */
label?: string;
/** 主题颜色 */
color?: string;
/** 组件尺寸 */
width?: string;
height?: string;
/** 是否开启动画 */
animation?: boolean;
/** 振幅大小 */
amplitude?: number;
/** 水波长度 */
waveLength?: number;
}
3. 组件属性默认值
const props = withDefaults(defineProps<Props>(), {
color: '#7cc2ff',
width: '120px',
height: '120px',
animation: true,
amplitude: 8, // 较小的振幅,避免覆盖整个圆形
waveLength: 300 // 适中的波长
});
关键配置详解
1. 多层水波效果
为了创建更真实的水波效果,我们使用多层水波:
data: [
{
value: normPercent(),
direction: 'right', // 第一层向右流动
itemStyle: {
color: props.color,
opacity: 0.8
}
},
{
value: normPercent() * 0.95, // 略低的水位
direction: 'left', // 第二层向左流动
itemStyle: {
color: props.color,
opacity: 0.6
}
}
]
2. 动画参数优化
// 关键动画参数
period: 2000, // 水波周期 - 控制波动速度
phase: 0, // 水波相位
animationEasing: 'linear', // 线性动画
animationDuration: 5000, // 初始动画时长
animationDurationUpdate: 2500 // 更新动画时长
3. 外观样式配置
outline: {
borderDistance: 4,
itemStyle: {
borderWidth: 3,
borderColor: props.color,
shadowBlur: 15,
shadowColor: props.color,
opacity: 0.8
}
},
backgroundStyle: {
color: 'rgba(255, 255, 255, 0.05)'
}
完整组件代码
<template>
<div class="echarts-liquid-wrapper" :style="{ width, height }">
<div ref="chartRef" :style="{ width: '100%', height: '100%' }"></div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue';
import * as echarts from 'echarts';
import 'echarts-liquidfill';
// ... 接口定义和属性配置 ...
const initChart = () => {
if (!chartRef.value) return;
if (chartInstance) {
chartInstance.dispose();
}
chartInstance = echarts.init(chartRef.value);
const option = {
series: [{
type: 'liquidFill',
data: [
{
value: normPercent(),
direction: 'right',
itemStyle: { color: props.color, opacity: 0.8 }
},
{
value: normPercent() * 0.95,
direction: 'left',
itemStyle: { color: props.color, opacity: 0.6 }
}
],
radius: '85%',
center: ['50%', '50%'],
shape: 'circle',
waveAnimation: props.animation,
amplitude: props.amplitude,
waveLength: props.waveLength,
period: 2000,
animationEasing: 'linear',
animationDuration: 5000,
// ... 其他配置
}]
};
chartInstance.setOption(option);
};
// 生命周期管理
onMounted(() => {
nextTick(() => {
initChart();
window.addEventListener('resize', handleResize);
});
});
onUnmounted(() => {
if (chartInstance) {
chartInstance.dispose();
}
window.removeEventListener('resize', handleResize);
});
</script>
使用示例
基础用法
<template>
<EChartsLiquidFill
:percent="72"
label="72%"
color="#7cc2ff"
width="150px"
height="150px"
/>
</template>
动态数据绑定
<template>
<div class="dashboard">
<EChartsLiquidFill
:percent="waterLevel"
:label="`${waterLevel}%`"
color="#1890ff"
width="200px"
height="200px"
/>
<el-slider v-model="waterLevel" :max="100" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const waterLevel = ref(65)
</script>
多个水球展示
<template>
<div class="water-health-panel">
<div class="panel-title">水质健康评估</div>
<div class="items">
<div class="item" v-for="item in healthItems" :key="item.label">
<EChartsLiquidFill
:percent="item.percentage"
:label="`${item.percentage}%`"
color="#7cc2ff"
width="110px"
height="110px"
/>
<div class="value">{{ item.value }}{{ item.unit }}</div>
<div class="label">{{ item.label }}</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const healthItems = ref([
{ label: 'CODmn', value: 28.45, unit: 'mg/L', percentage: 72 },
{ label: '氨氮', value: 15.23, unit: 'mg/L', percentage: 85 },
{ label: '总磷', value: 41.67, unit: 'mg/L', percentage: 48 },
{ label: '总氮', value: 35.92, unit: 'mg/L', percentage: 63 }
])
</script>
<style scoped>
.water-health-panel {
padding: 20px;
background: white;
border-radius: 8px;
}
.items {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20px;
}
.item {
display: flex;
flex-direction: column;
align-items: center;
padding: 15px;
border-radius: 8px;
background: #f8fafc;
}
.value {
margin-top: 8px;
font-size: 16px;
font-weight: bold;
color: #1f2937;
}
.label {
margin-top: 4px;
font-size: 14px;
color: #6b7280;
}
</style>
实时数据更新
<template>
<div class="real-time-monitor">
<h3>实时监控数据</h3>
<div class="monitor-grid">
<div v-for="item in monitorData" :key="item.id" class="monitor-item">
<EChartsLiquidFill
:percent="item.value"
:label="`${item.value}%`"
:color="getStatusColor(item.value)"
width="120px"
height="120px"
/>
<h4>{{ item.name }}</h4>
<p :class="getStatusClass(item.value)">
{{ getStatusText(item.value) }}
</p>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
const monitorData = ref([
{ id: 1, name: '水位监测', value: 75 },
{ id: 2, name: '水质指标', value: 82 },
{ id: 3, name: '设备状态', value: 95 }
])
// 根据数值获取状态颜色
const getStatusColor = (value: number) => {
if (value >= 80) return '#52c41a' // 绿色 - 良好
if (value >= 60) return '#faad14' // 黄色 - 一般
return '#ff4d4f' // 红色 - 异常
}
// 根据数值获取状态样式类
const getStatusClass = (value: number) => {
if (value >= 80) return 'status-good'
if (value >= 60) return 'status-warning'
return 'status-danger'
}
// 根据数值获取状态文本
const getStatusText = (value: number) => {
if (value >= 80) return '状态良好'
if (value >= 60) return '需要关注'
return '异常警告'
}
// 模拟实时数据更新
let timer: NodeJS.Timeout
onMounted(() => {
timer = setInterval(() => {
monitorData.value.forEach(item => {
// 随机变化 ±5
const change = (Math.random() - 0.5) * 10
item.value = Math.max(0, Math.min(100, item.value + change))
})
}, 3000)
})
onUnmounted(() => {
if (timer) clearInterval(timer)
})
</script>
<style scoped>
.monitor-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 24px;
margin-top: 20px;
}
.monitor-item {
text-align: center;
padding: 20px;
border-radius: 12px;
background: white;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.status-good { color: #52c41a; }
.status-warning { color: #faad14; }
.status-danger { color: #ff4d4f; }
</style>
性能优化要点
1. 实例管理
- 组件销毁时正确释放 ECharts 实例
- 避免内存泄漏
2. 响应式处理
- 监听窗口大小变化
- 动态调整图表尺寸
3. 动画优化
- 合理设置动画参数
- 避免过度动画影响性能
常见问题解决
1. 水波振幅过大
问题:水波纹振幅太大,覆盖整个圆形
解决:调整 amplitude 参数,建议值为 5-12
amplitude: 8 // 推荐值
2. 动画速度过快
问题:水波动画速度太快,影响视觉效果
解决:增加 period 值,减慢动画速度
period: 2000 // 2秒一个周期
3. 多层水波高度差异
问题:多层水波高度差异过大
解决:使用接近的数值,如 value * 0.95
扩展功能
1. 自定义形状
shape: 'circle' | 'rect' | 'roundRect' | 'triangle' | 'diamond'
2. 渐变色支持
color: {
type: 'linear',
x: 0, y: 0, x2: 0, y2: 1,
colorStops: [
{ offset: 0, color: '#7cc2ff' },
{ offset: 1, color: '#4a90e2' }
]
}
3. 动态数据更新
watch(() => props.percent, () => {
updateChart();
});
总结
通过本文的详细介绍,我们成功实现了一个功能完整的 Vue3 + ECharts 水波纹组件。该组件具有良好的可配置性、优秀的动画效果和稳定的性能表现。
关键要点回顾:
- 合理配置动画参数,平衡视觉效果和性能
- 正确管理 ECharts 实例生命周期
- 使用多层水波创建更真实的效果
- 提供丰富的自定义选项满足不同需求
希望这个组件能够帮助大家在项目中快速实现美观的数据可视化效果!
高级配置选项
完整配置参数表
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
percent | number | - | 百分比值(0-100或0-1) |
label | string | 自动计算 | 显示文本 |
color | string | ‘#7cc2ff’ | 主题颜色 |
width/height | string | ‘120px’ | 组件尺寸 |
animation | boolean | true | 是否开启动画 |
amplitude | number | 8 | 波浪振幅 |
waveLength | number | 300 | 波浪长度 |
period | number | 2000 | 动画周期(ms) |
shape | string | ‘circle’ | 容器形状 |
最佳实践建议
- 振幅设置:建议在 5-12 之间,避免过大覆盖圆形
- 动画周期:推荐 1500-3000ms,太快会影响观感
- 颜色搭配:使用品牌色或语义化颜色
- 尺寸适配:根据容器大小合理设置组件尺寸
4359

被折叠的 条评论
为什么被折叠?



