效果图
//引用页面
<div style="height: 60px;background-color: #fff;border-radius: 5px;width: 40px;">
<WavePercentage
:percentage="progress"
primary-color="#ffcb7c"
secondary-color="#ffcb7c"
/>
</div>
import WavePercentage from './WavePercentage.vue'
const progress = ref(60)
//WavePercentage页面
<template>
<div ref="containerRef" class="wave-percentage-container">
<canvas ref="canvasRef" class="wave-canvas"></canvas>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, defineProps, withDefaults } from 'vue'
class Wave {
constructor(height, power = 60) {
this.__force = 0
this.__wavePower = power
this.__count = 0
this.__y = height
this.__h = height
this.__pos = 0
}
get height() {
return this.__h
}
set height(value) {
this.__h = Math.max(0, value)
}
get percent() {
return (1 - this.__y / this.__h) * 100
}
set percent(value) {
this.__y = this.__h * (1 - value / 100)
}
update() {
// 添加一个持续的波动效果,但不改变实际百分比
this.__count += 0.1
this.__pos += 0.04
this.__force = Math.sin(this.__count)
}
draw(ctx, w, h, makeGrd, percentage) {
// 固定波浪高度,但添加动态波动效果
const actualY = this.__h * (1 - percentage / 100)
ctx.fillStyle = makeGrd(h)
ctx.beginPath()
ctx.moveTo(0, actualY)
const p = Math.sin(this.__pos)
ctx.quadraticCurveTo(
w * (p + 0.25),
actualY + (this.__wavePower * this.__force * 0.2),
w * (p + 0.5),
actualY
)
ctx.quadraticCurveTo(
w * (p + 0.75),
actualY - (this.__wavePower * this.__force * 0.2),
w,
actualY
)
ctx.lineTo(w, h)
ctx.lineTo(0, h)
ctx.lineTo(0, actualY)
ctx.closePath()
ctx.fill()
}
}
// Props definition
const props = defineProps({
percentage: {
type: Number,
default: 50,
validator: (value) => value >= 0 && value <= 100
},
wavePower: {
type: Number,
default: 100
},
primaryColor: {
type: String,
default: '#419dff'
},
secondaryColor: {
type: String,
default: '#66bfff'
}
})
// Refs for canvas and container
const containerRef = ref(null)
const canvasRef = ref(null)
// Animation variables
let animationFrameId = null
let wave = null
let ctx = null
// Create gradient function
const makeGrd = (ctx, h, primaryColor, secondaryColor) => {
const g = ctx.createLinearGradient(0, 0, 0, h)
g.addColorStop(0, primaryColor)
g.addColorStop(1, secondaryColor)
return g
}
// Draw function
const draw = () => {
if (!containerRef.value || !canvasRef.value) return
const canvas = canvasRef.value
const container = containerRef.value
// Update dimensions
const w = canvas.width = container.clientWidth
const h = canvas.height = container.clientHeight
// Recreate wave if not exists or dimensions changed
if (!wave || wave.height !== h) {
wave = new Wave(h, props.wavePower)
}
// Update wave animation
wave.update()
ctx = canvas.getContext('2d')
// Clear canvas
ctx.globalCompositeOperation = 'source-over'
ctx.fillStyle = "rgba(255, 255, 255, 1)"
ctx.fillRect(0, 0, w, h)
ctx.globalCompositeOperation = 'xor'
ctx.fillStyle = "rgba(0, 0, 0, 1)"
ctx.fillRect(0, 0, w, h)
// Draw wave with current percentage
wave.draw(ctx, w, h, (h) => makeGrd(ctx, h, props.primaryColor, props.secondaryColor), props.percentage)
// Draw percentage text
ctx.textAlign = "center"
ctx.textBaseline = 'middle'
ctx.font = 'bold 1rem serif'
ctx.fillStyle = "rgba(0, 0, 0, 1)"
ctx.fillText(`${props.percentage | 0}%`, w / 2, h / 2)
// Continue animation
animationFrameId = requestAnimationFrame(draw)
}
// Lifecycle hooks
onMounted(() => {
animationFrameId = requestAnimationFrame(draw)
})
onUnmounted(() => {
if (animationFrameId) {
cancelAnimationFrame(animationFrameId)
}
})
</script>
<style scoped>
.wave-percentage-container {
position: relative;
width: 100%;
height: 100%;
}
.wave-canvas {
width: 100%;
height: 100%;
}
</style>
直接贴代码可以直接使用,支持动态设置值,修改波纹颜色