import { watch, computed, PropType } from 'vue'; import { raf, cancelRaf } from '@vant/use'; import { isObject, getSizeStyle, createNamespace } from '../utils'; const [createComponent, bem] = createNamespace('circle'); let uid = 0; function format(rate: string | number) { return Math.min(Math.max(+rate, 0), 100); } function getPath(clockwise: boolean, viewBoxSize: number) { 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, size: [Number, String], color: [String, Object] as PropType>, layerColor: String, strokeLinecap: String as PropType, currentRate: { type: Number, default: 0, }, speed: { type: [Number, String], default: 0, }, fill: { type: String, default: 'none', }, rate: { type: [Number, String], default: 100, }, strokeWidth: { type: [Number, String], default: 40, }, clockwise: { type: Boolean, default: true, }, }, emits: ['update:currentRate'], setup(props, { emit, slots }) { const id = `van-circle-${uid++}`; const viewBoxSize = computed(() => +props.strokeWidth + 1000); const path = computed(() => getPath(props.clockwise, viewBoxSize.value)); watch( () => props.rate, (rate) => { let rafId; const startTime = Date.now(); const startRate = props.currentRate; const endRate = format(rate); const duration = Math.abs( ((startRate - endRate) * 1000) / +props.speed ); const animate = () => { const now = Date.now(); const progress = Math.min((now - startTime) / duration, 1); const rate = progress * (endRate - startRate) + startRate; emit('update:currentRate', format(parseFloat(rate.toFixed(1)))); if (endRate > startRate ? rate < endRate : rate > endRate) { rafId = raf(animate); } }; if (props.speed) { if (rafId) { cancelRaf(rafId); } rafId = raf(animate); } else { emit('update:currentRate', endRate); } }, { immediate: true } ); const renderHover = () => { const PERIMETER = 3140; const { color, strokeWidth, currentRate, strokeLinecap } = props; const offset = (PERIMETER * currentRate) / 100; const style = { stroke: `${color}`, strokeWidth: `${+strokeWidth + 1}px`, strokeLinecap, strokeDasharray: `${offset}px ${PERIMETER}px`, }; return ( ); }; const renderLayer = () => { const style = { fill: props.fill, stroke: props.layerColor, strokeWidth: `${props.strokeWidth}px`, }; return ; }; const renderGradient = () => { const { color } = props; if (!isObject(color)) { return; } const Stops = Object.keys(color) .sort((a, b) => parseFloat(a) - parseFloat(b)) .map((key, index) => ( )); return ( {Stops} ); }; const renderText = () => { if (slots.default) { return slots.default(); } if (props.text) { return
{props.text}
; } }; return () => (
{renderGradient()} {renderLayer()} {renderHover()} {renderText()}
); }, });