import { BLUE, WHITE } from '../common/color'; import { VantComponent } from '../common/component'; import { getSystemInfoSync } from '../common/utils'; import { isObj } from '../common/validator'; import { canIUseCanvas2d } from '../common/version'; import { adaptor } from './canvas'; function format(rate) { return Math.min(Math.max(rate, 0), 100); } const PERIMETER = 2 * Math.PI; const BEGIN_ANGLE = -Math.PI / 2; const STEP = 1; VantComponent({ props: { text: String, lineCap: { type: String, value: 'round', }, value: { type: Number, value: 0, observer: 'reRender', }, speed: { type: Number, value: 50, }, size: { type: Number, value: 100, observer() { this.drawCircle(this.currentValue); }, }, fill: String, layerColor: { type: String, value: WHITE, }, color: { type: null, value: BLUE, observer() { this.setHoverColor().then(() => { this.drawCircle(this.currentValue); }); }, }, type: { type: String, value: '', }, strokeWidth: { type: Number, value: 4, }, clockwise: { type: Boolean, value: true, }, }, data: { hoverColor: BLUE, }, methods: { getContext() { const { type, size } = this.data; if (type === '' || !canIUseCanvas2d()) { const ctx = wx.createCanvasContext('van-circle', this); return Promise.resolve(ctx); } const dpr = getSystemInfoSync().pixelRatio; return new Promise((resolve) => { wx.createSelectorQuery() .in(this) .select('#van-circle') .node() .exec((res) => { const canvas = res[0].node; const ctx = canvas.getContext(type); if (!this.inited) { this.inited = true; canvas.width = size * dpr; canvas.height = size * dpr; ctx.scale(dpr, dpr); } resolve(adaptor(ctx)); }); }); }, setHoverColor() { const { color, size } = this.data; if (isObj(color)) { return this.getContext().then((context) => { if (!context) return; const LinearColor = context.createLinearGradient(size, 0, 0, 0); Object.keys(color) .sort((a, b) => parseFloat(a) - parseFloat(b)) .map((key) => LinearColor.addColorStop(parseFloat(key) / 100, color[key])); this.hoverColor = LinearColor; }); } this.hoverColor = color; return Promise.resolve(); }, presetCanvas(context, strokeStyle, beginAngle, endAngle, fill) { const { strokeWidth, lineCap, clockwise, size } = this.data; const position = size / 2; const radius = position - strokeWidth / 2; context.setStrokeStyle(strokeStyle); context.setLineWidth(strokeWidth); context.setLineCap(lineCap); context.beginPath(); context.arc(position, position, radius, beginAngle, endAngle, !clockwise); context.stroke(); if (fill) { context.setFillStyle(fill); context.fill(); } }, renderLayerCircle(context) { const { layerColor, fill } = this.data; this.presetCanvas(context, layerColor, 0, PERIMETER, fill); }, renderHoverCircle(context, formatValue) { const { clockwise } = this.data; // 结束角度 const progress = PERIMETER * (formatValue / 100); const endAngle = clockwise ? BEGIN_ANGLE + progress : 3 * Math.PI - (BEGIN_ANGLE + progress); this.presetCanvas(context, this.hoverColor, BEGIN_ANGLE, endAngle); }, drawCircle(currentValue) { const { size } = this.data; this.getContext().then((context) => { if (!context) return; context.clearRect(0, 0, size, size); this.renderLayerCircle(context); const formatValue = format(currentValue); if (formatValue !== 0) { this.renderHoverCircle(context, formatValue); } context.draw(); }); }, reRender() { // tofector 动画暂时没有想到好的解决方案 const { value, speed } = this.data; if (speed <= 0 || speed > 1000) { this.drawCircle(value); return; } this.clearMockInterval(); this.currentValue = this.currentValue || 0; const run = () => { this.interval = setTimeout(() => { if (this.currentValue !== value) { if (Math.abs(this.currentValue - value) < STEP) { this.currentValue = value; } else if (this.currentValue < value) { this.currentValue += STEP; } else { this.currentValue -= STEP; } this.drawCircle(this.currentValue); run(); } else { this.clearMockInterval(); } }, 1000 / speed); }; run(); }, clearMockInterval() { if (this.interval) { clearTimeout(this.interval); this.interval = null; } }, }, mounted() { this.currentValue = this.data.value; this.setHoverColor().then(() => { this.drawCircle(this.currentValue); }); }, destroyed() { this.clearMockInterval(); }, });