UNPKG

wx-mini-weview

Version:

weixin UI

276 lines (249 loc) 7.73 kB
Component({ options: { styleIsolation: 'apply-shared', multipleSlots: true }, properties: { // Progress value (0-100) progress: { type: Number, value: 20 }, // 主题样式类 theme: { type: String, value: 'wv-theme-primary wv-content-light' }, // Custom background for the bar backgroundBar: { type: String, value: '' }, // Custom background for the progress backgroundInner: { type: String, value: '' }, // Background color for the hollow center of the circle backgroundHollow: { type: String, value: '' }, // Size of the circle size: { type: String, value: '100%' }, // Width of the component container width: { type: String, value: '100%' }, // Height of the component container height: { type: String, value: '' }, // Thickness/width of the progress circle thickness: { type: String, value: '20rpx' }, // Whether to animate progress changes animate: { type: Boolean, value: true }, // Animation duration (milliseconds) duration: { type: String, value: '600ms' }, // Text style textProgressStyle: { type: String, value: 'color:initial;' }, // Text position: none, center, up, down, left, right textProgressPosition: { type: String, value: 'center' }, // Whether to show striped pattern on the inner progress striped: { type: Boolean, value: false }, // Start angle in degrees (0 = top, 90 = right, etc.) startAngle: { type: Number, value: 0 }, // Inner border radius - applies to the progress arc ends radius: { type: Boolean, value: true } }, data: { progressAngle: 0, progressText: '0%', containerStyle: '--wv-progress-circle-size-actual: 0;', // Default value animationDuration: '0ms', // No initial animation animationInProgress: false, angleRotation: 0 // Separate property for angle rotation after measurement }, lifetimes: { attached: function() { // Initial setup without rotation this.setData({ progressAngle: 0, progressText: '0%', angleRotation: 0 }); }, ready: function() { // Measure actual size first without rotation this.updateActualSize(); // Allow initial rendering to complete, then animate to the target value setTimeout(() => { // Apply the rotation angle after size calculation this.setData({ angleRotation: this.properties.startAngle }); if (this.properties.progress > 0 && this.properties.animate) { this.animateProgressAngle(this.properties.progress); } else { this.updateProgressAngle(); } }, 50); }, detached: function() { // Clean up any active animation timers if (this.animationTimer) { clearInterval(this.animationTimer); this.animationTimer = null; } } }, observers: { 'progress': function(newProgress) { if (this.properties.animate) { this.animateProgressAngle(newProgress); } else { this.updateProgressAngle(newProgress); } }, 'animate': function(newValue) { this.setData({ animationDuration: newValue ? this.properties.duration : '0ms' }); }, 'duration': function(newValue) { if (this.properties.animate) { this.setData({ animationDuration: newValue }); } }, 'size': function(newSize) { // When size changes, we need to re-measure after render setTimeout(() => { this.updateActualSize(); }, 50); // Small delay to ensure DOM has been updated }, 'startAngle': function(newAngle) { // Only update rotation after component is fully initialized if (this.data.containerStyle !== '--wv-progress-circle-size-actual: 0;') { this.setData({ angleRotation: newAngle }); } } }, methods: { updateProgressAngle: function(progressValue = null) { // Use provided value or get from properties const progress = progressValue !== null ? progressValue : this.properties.progress; // Ensure progress is within 0-100 range const normalizedProgress = Math.max(0, Math.min(100, progress)); // Convert progress percentage to degrees (0-360) const angle = (normalizedProgress / 100) * 360; // Update the progress text const progressText = `${Math.round(normalizedProgress)}%`; // Update both angle and text this.setData({ progressAngle: angle, progressText: progressText }); }, animateProgressAngle: function(newProgress) { // If an animation is already in progress, cancel it if (this.animationTimer) { clearInterval(this.animationTimer); this.animationTimer = null; } // Get current and target angles const normalizedProgress = Math.max(0, Math.min(100, newProgress)); const currentAngle = this.data.progressAngle; const targetAngle = (normalizedProgress / 100) * 360; if (currentAngle === targetAngle) return; // Calculate animation duration in ms (remove 'ms' suffix if present) const durationStr = this.properties.duration; const duration = parseInt(durationStr.replace('ms', '')); // Update the progress text immediately to match the target value const progressText = `${Math.round(normalizedProgress)}%`; this.setData({ progressText: progressText }); // Calculate animation steps const startTime = Date.now(); const startAngle = currentAngle; const angleChange = targetAngle - startAngle; // Use animation frames to smoothly update the angle const animateFrame = () => { const elapsed = Date.now() - startTime; const progress = Math.min(elapsed / duration, 1); // Calculate current angle using easing function const currentAngle = startAngle + angleChange * this.easeInOutQuad(progress); this.setData({ progressAngle: currentAngle }); if (progress < 1) { this.animationTimer = setTimeout(animateFrame, 16); // ~60fps } else { this.setData({ progressAngle: targetAngle, progressText: `${Math.round(normalizedProgress)}%` }); this.animationTimer = null; } }; // Start animation animateFrame(); }, // Easing function for smoother animations easeInOutQuad: function(t) { return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2; }, updateActualSize: function() { const query = this.createSelectorQuery(); query.select('.wv-progress-circle-container').boundingClientRect(); query.exec((res) => { if (res && res[0]) { const actualWidth = res[0].width; // Update the CSS variable with the measured width this.setData({ containerStyle: `--wv-progress-circle-size-actual: ${actualWidth}px;` }); } }); } }, pageLifetimes: { resize: function() { // Update actual size when page resizes (for responsive layouts) this.updateActualSize(); }, show: function() { // Update when page is shown (in case dimensions changed while hidden) this.updateActualSize(); } } });