wx-mini-weview
Version:
weixin UI
276 lines (249 loc) • 7.73 kB
JavaScript
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();
}
}
});