<template>
|
<view class="base-style"
|
:style="'position: relative;width: ' + diameter + 'px;height: ' + diameter + 'px;display: flex;flex-direction: row;background-color: ' + bgColor + ';'">
|
<!-- 左半圆和右半圆都要经历下面的5步:
|
[第1步]第1层限定区域;
|
[第2步]第2层决定显示一个整圆的左半边还是右半边;
|
[第3步]第3层先使用激活颜色绘制一个圆环, 再添加一个同级且宽度为区域一半的盒子A;
|
[第4步]在盒子A中再使用圆环底色绘制一个圆环, 此时整个圆环是 '左一半是激活颜色、右一半是圆环底色', 但这个圆环同时只能被看到一半;
|
[第5步]旋转第2层。 -->
|
|
<!-- 左半圆 -->
|
<view class="base-style" :style="firstLayerViewStyle">
|
<view :style="secondLayerViewStyle + secondLayerForLeft">
|
<!-- 使用激活颜色绘制一个圆环。 -->
|
<view :style="thirdLayerStyle">
|
</view>
|
<!-- 再使用背景色遮盖同级圆环的一半。 -->
|
<view class="base-style" :style="thirdLayerStyleForBg">
|
<view :style="fourthLayerStyleForBg" />
|
</view>
|
<view v-if="0 < ePercent && ePercent < 0.5" :style="endPointStyle + endPointStyleForLeft" />
|
</view>
|
</view>
|
|
<!-- 右半圆 -->
|
<view class="base-style" :style="firstLayerViewStyle">
|
<!-- 适配:为了避免右侧遮盖显示不全 此处向左多移动了1px -->
|
<view :style="secondLayerViewStyle + 'left: ' + (- diameter / 2 - 1) + 'px;' + secondLayerForRight">
|
<!-- 使用激活颜色绘制一个圆环。 -->
|
<view :style="thirdLayerStyle">
|
</view>
|
<!-- 再使用背景色遮盖同级圆环的一半。 -->
|
<view class="base-style" :style="thirdLayerStyleForBg">
|
<view :style="fourthLayerStyleForBg" />
|
</view>
|
<view v-if="ePercent > 0.5" :style="endPointStyle + endPointStyleForRight" />
|
</view>
|
</view>
|
|
<view v-if="0.5 == ePercent" :style="endPointStyle + 'background-color: ' + this.hoopBgColor + ';'" />
|
<!-- #ifdef APP-PLUS -->
|
<!-- 处理现象: 安卓App的顶部和底部会有一个小白点。 -->
|
<!-- <view v-if="ePercent > 0.5" :style="'position: absolute;top: 0;' + repairPointStyle" /> -->
|
<!-- <view v-if="1.0 == ePercent" :style="'position: absolute;bottom: 0;' + repairPointStyle" /> -->
|
<!-- #endif -->
|
</view>
|
</template>
|
|
<script>
|
export default {
|
name: 'progressCircle',
|
props: {
|
// 背景色(不宜设置为透明 否则 需要 在 左thirdLayer 的外面 再嵌套一个盒子)。
|
bgColor: {
|
type: String,
|
default: '#FFFFFF'
|
},
|
// 圆环的外直径(单位px)。
|
diameter: {
|
type: Number,
|
default: 250
|
},
|
// 圆环线条的厚度(单位px)。
|
hoopThickness: {
|
type: Number,
|
default: 8
|
},
|
// 圆环底色(灰色的圆环)。
|
hoopBgColor: {
|
type: String,
|
default: '#D1D1D1'
|
},
|
// 圆环激活部分的颜色。
|
hoopColor: {
|
type: String,
|
default: '#FF4C20'
|
},
|
// 圆环进度百分比值(其值范围在0到1之间)。
|
percent: {
|
type: [Number, String],
|
default: 0,
|
validator: val => {
|
return val >= 0 && val <= 1;
|
},
|
},
|
animate: {
|
type: Boolean,
|
default: false,
|
},
|
// 动画持续时间(单位:毫秒)
|
animationDuration: {
|
type: Number,
|
default: 2000
|
},
|
// 动画步进值
|
animationStep: {
|
type: Number,
|
default: 0.1
|
}
|
},
|
data() {
|
return {
|
targetPercent: 0,
|
ePercent: 0,
|
showTimer: undefined,
|
};
|
},
|
watch: {
|
percent: {
|
handler: function() {
|
console.log('progressCircle_watch_percent', this.percent);
|
this.loadData();
|
},
|
},
|
},
|
computed: {
|
firstLayerViewStyle() {
|
return 'position: relative;width: ' + (this.diameter / 2) +
|
'px;height: ' + this.diameter + 'px;';
|
},
|
secondLayerViewStyle() {
|
return 'box-sizing: border-box;position: absolute;top: 0;width: ' + this.diameter +
|
'px;height: ' + this.diameter + 'px;';
|
},
|
thirdLayerStyle() {
|
return 'box-sizing: border-box;width: ' + this.diameter + 'px;height: ' + this.diameter +
|
'px;border-radius: ' + (this.diameter / 2) +
|
'px;border-width: ' + this.hoopThickness +
|
'px;border-style: solid;border-color: ' + this.hoopColor + ';';
|
},
|
thirdLayerStyleForBg() {
|
return 'box-sizing: border-box;position: absolute;top: 0;left: ' + (this.diameter / 2) + 'px;width: ' +
|
this.diameter + 'px;height: ' + this.diameter + 'px;background-color: ' + this.bgColor + ';';
|
},
|
fourthLayerStyleForBg() {
|
return 'box-sizing: border-box;margin-left: ' + (-this.diameter / 2) + 'px;width: ' + this.diameter +
|
'px;height: ' +
|
this.diameter + 'px;border-radius: ' + (this.diameter / 2) + 'px;border-width: ' +
|
this.hoopThickness + 'px;border-style: solid;border-color: ' + this.hoopBgColor + ';';
|
},
|
secondLayerForLeft() {
|
let angle = 0;
|
if (this.ePercent < 0.5) {
|
angle += (180 * (this.ePercent - 0.5) / 0.5);
|
}
|
// #ifdef APP-PLUS
|
return 'left: 0;transform: rotate(' + angle + 'deg);';
|
// #endif
|
// #ifndef APP-PLUS
|
return 'left: 0;transform: rotate(' + angle + 'deg);-webkit-transform: rotate(' + angle + 'deg);';
|
// #endif
|
},
|
secondLayerForRight() {
|
let angle = 0;
|
if (this.ePercent > 0.5) {
|
angle += (180 * (this.ePercent - 0.5) / 0.5);
|
}
|
// #ifdef APP-PLUS
|
return 'right: 0;transform: rotate(' + angle + 'deg);';
|
// #endif
|
// #ifndef APP-PLUS
|
return 'right: 0;transform: rotate(' + angle + 'deg);-webkit-transform: rotate(' + angle + 'deg);';
|
// #endif
|
},
|
// repairPointStyle() {
|
// return 'left: ' + (this.diameter - this.hoopThickness) / 2 + 'px;width: ' +
|
// this.hoopThickness + 'px;height: ' + this.hoopThickness + 'px;border-radius: ' +
|
// this.hoopThickness / 2 + 'px;background-color: ' + this.hoopColor + ';';
|
// },
|
endPointStyle() {
|
// 结束点圆心圈直径。
|
const _circleCenterRadius = 2;
|
return 'box-sizing: border-box;position: absolute;top: 0;left: ' + (this.diameter - this.hoopThickness) / 2 +
|
'px;width: ' +
|
this.hoopThickness + 'px;height: ' + this.hoopThickness + 'px;border-radius: ' + (this.hoopThickness / 2) +
|
'px;border-width: ' + (this.hoopThickness / 2 - _circleCenterRadius) +
|
'px;border-style: solid;border-color: ' +
|
this.hoopColor + ';';
|
},
|
endPointStyleForLeft() {
|
return 'background-color: ' + ((this.ePercent > 0.5) ? this.hoopColor : this.hoopBgColor) + ';';
|
},
|
endPointStyleForRight() {
|
return 'background-color: ' + ((1 == this.ePercent) ? this.hoopColor : this.hoopBgColor) + ';';
|
},
|
},
|
mounted() {
|
console.log('progressCircle_mounted');
|
this.loadData();
|
},
|
methods: {
|
loadData() {
|
this.targetPercent = parseFloat(this.percent);
|
console.log('progressCircle_loadData');
|
if (!this.animate) {
|
this.ePercent = this.targetPercent;
|
} else {
|
let _this = this;
|
this.ePercent = 0;
|
this.showTimer && clearInterval(this.showTimer);
|
|
// 计算动画间隔时间
|
const stepInterval = Math.max(50, this.animationDuration * this.animationStep);
|
|
this.showTimer = setInterval(() => {
|
let tempPercent = _this.ePercent + _this.animationStep;
|
if (tempPercent < _this.targetPercent) {
|
_this.ePercent = tempPercent;
|
return;
|
};
|
_this.ePercent = _this.targetPercent;
|
clearInterval(_this.showTimer);
|
}, stepInterval);
|
}
|
}
|
}
|
}
|
</script>
|
|
<style scoped>
|
.base-style {
|
box-sizing: border-box;
|
/* 溢出隐藏 */
|
overflow: hidden;
|
}
|
</style>
|