mirror of
https://gitee.com/vant-contrib/vant.git
synced 2025-04-05 19:41:42 +08:00
187 lines
4.2 KiB
JavaScript
187 lines
4.2 KiB
JavaScript
import { createNamespace, isObject, addUnit } from '../utils';
|
|
import { raf, cancelRaf } from '../utils/dom/raf';
|
|
import { BLUE, WHITE } from '../utils/constant';
|
|
|
|
const [createComponent, bem] = createNamespace('circle');
|
|
|
|
const PERIMETER = 3140;
|
|
|
|
let uid = 0;
|
|
|
|
function format(rate) {
|
|
return Math.min(Math.max(rate, 0), 100);
|
|
}
|
|
|
|
function getPath(clockwise, viewBoxSize) {
|
|
const sweepFlag = clockwise ? 1 : 0;
|
|
return `M ${viewBoxSize / 2} ${
|
|
viewBoxSize / 2
|
|
} m 0, -500 a 500, 500 0 1, ${sweepFlag} 0, 1000 a 500, 500 0 1, ${sweepFlag} 0, -1000`;
|
|
}
|
|
|
|
export default createComponent({
|
|
props: {
|
|
text: String,
|
|
strokeLinecap: String,
|
|
currentRate: {
|
|
type: Number,
|
|
default: 0,
|
|
},
|
|
speed: {
|
|
type: [Number, String],
|
|
default: 0,
|
|
},
|
|
size: {
|
|
type: [Number, String],
|
|
default: 100,
|
|
},
|
|
fill: {
|
|
type: String,
|
|
default: 'none',
|
|
},
|
|
rate: {
|
|
type: [Number, String],
|
|
default: 100,
|
|
},
|
|
layerColor: {
|
|
type: String,
|
|
default: WHITE,
|
|
},
|
|
color: {
|
|
type: [String, Object],
|
|
default: BLUE,
|
|
},
|
|
strokeWidth: {
|
|
type: [Number, String],
|
|
default: 40,
|
|
},
|
|
clockwise: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
},
|
|
|
|
emits: ['update:currentRate'],
|
|
|
|
beforeCreate() {
|
|
this.uid = `van-circle-gradient-${uid++}`;
|
|
},
|
|
|
|
computed: {
|
|
style() {
|
|
const size = addUnit(this.size);
|
|
return {
|
|
width: size,
|
|
height: size,
|
|
};
|
|
},
|
|
|
|
path() {
|
|
return getPath(this.clockwise, this.viewBoxSize);
|
|
},
|
|
|
|
viewBoxSize() {
|
|
return +this.strokeWidth + 1000;
|
|
},
|
|
|
|
layerStyle() {
|
|
const offset = (PERIMETER * this.currentRate) / 100;
|
|
|
|
return {
|
|
stroke: `${this.color}`,
|
|
strokeWidth: `${+this.strokeWidth + 1}px`,
|
|
strokeLinecap: this.strokeLinecap,
|
|
strokeDasharray: `${offset}px ${PERIMETER}px`,
|
|
};
|
|
},
|
|
|
|
hoverStyle() {
|
|
return {
|
|
fill: `${this.fill}`,
|
|
stroke: `${this.layerColor}`,
|
|
strokeWidth: `${this.strokeWidth}px`,
|
|
};
|
|
},
|
|
|
|
gradient() {
|
|
return isObject(this.color);
|
|
},
|
|
|
|
LinearGradient() {
|
|
if (!this.gradient) {
|
|
return;
|
|
}
|
|
|
|
const Stops = Object.keys(this.color)
|
|
.sort((a, b) => parseFloat(a) - parseFloat(b))
|
|
.map((key, index) => (
|
|
<stop key={index} offset={key} stop-color={this.color[key]} />
|
|
));
|
|
|
|
return (
|
|
<defs>
|
|
<linearGradient id={this.uid} x1="100%" y1="0%" x2="0%" y2="0%">
|
|
{Stops}
|
|
</linearGradient>
|
|
</defs>
|
|
);
|
|
},
|
|
},
|
|
|
|
watch: {
|
|
rate: {
|
|
handler(rate) {
|
|
this.startTime = Date.now();
|
|
this.startRate = this.currentRate;
|
|
this.endRate = format(rate);
|
|
this.increase = this.endRate > this.startRate;
|
|
this.duration = Math.abs(
|
|
((this.startRate - this.endRate) * 1000) / this.speed
|
|
);
|
|
|
|
if (this.speed) {
|
|
cancelRaf(this.rafId);
|
|
this.rafId = raf(this.animate);
|
|
} else {
|
|
this.$emit('update:currentRate', this.endRate);
|
|
}
|
|
},
|
|
immediate: true,
|
|
},
|
|
},
|
|
|
|
methods: {
|
|
animate() {
|
|
const now = Date.now();
|
|
const progress = Math.min((now - this.startTime) / this.duration, 1);
|
|
const rate = progress * (this.endRate - this.startRate) + this.startRate;
|
|
|
|
this.$emit('update:currentRate', format(parseFloat(rate.toFixed(1))));
|
|
|
|
if (this.increase ? rate < this.endRate : rate > this.endRate) {
|
|
this.rafId = raf(this.animate);
|
|
}
|
|
},
|
|
},
|
|
|
|
render() {
|
|
return (
|
|
<div class={bem()} style={this.style}>
|
|
<svg viewBox={`0 0 ${this.viewBoxSize} ${this.viewBoxSize}`}>
|
|
{this.LinearGradient}
|
|
<path class={bem('hover')} style={this.hoverStyle} d={this.path} />
|
|
<path
|
|
d={this.path}
|
|
class={bem('layer')}
|
|
style={this.layerStyle}
|
|
stroke={this.gradient ? `url(#${this.uid})` : this.color}
|
|
/>
|
|
</svg>
|
|
{this.$slots.default
|
|
? this.$slots.default()
|
|
: this.text && <div class={bem('text')}>{this.text}</div>}
|
|
</div>
|
|
);
|
|
},
|
|
});
|