uni-app实现进度条
前言
大家好,这里是藤原豆腐店。这次项目的需求需要用到圆形进度条,在插件市场找到个进度条插件使用起来很方便,我趁着空闲时间研究了一下他的源码实现,学习别人是怎么封装插件的,下面是我对该进度条实现原理的梳理。
ext.dcloud.net.cn/plugin?id=1…
类型控制
通过props传入mode字段控制: ring(圆环) line(长条)
props: {
// 模式: ring(圆环) line(长条)
mode: {
type: String,
default: 'line'
},
}
长条进度条的实现
<view v-if="mode ===line">
<view class="line-bar" :style="'width:'+lineProgressBarWidth+'rpx;height:'+lineProgressBarHeight+'rpx;background-color:'+defaultColor+';border-radius:'+lineProgressBarRadius+'rpx'">
<view class="line-bar-active" :style="'background-color:'+activeColor+';width:'+activeLineBarWidth+'rpx;border-radius:'+lineProgressBarRadius+'rpx'"></view>
</view>
</view>
样式实现
类样式
.line-bar{
position: relative;
.line-bar-active{
position: absolute;
height: 100%;
left: 0;
top: 0;
}
}
行内样式通过props接受值来设置进度条的宽高,圆角和背景属性
props: {
// 长条进度条的宽度 单位rpx
lineProgressBarWidth: {
type: Number,
default: 750
},
// 长条进度条的高度 单位rpx
lineProgressBarHeight: {
type: Number,
default: 20
},
// 长条进度条的圆角值
lineProgressBarRadius: {
type: Number,
default: 12
},
activeColor: {
type: String,
default: '#E00300'
},
defaultColor: {
type: String,
default: '#CDCDCD'
},
}
功能实现
在data中声明activeLineBarWidth,用于设置已激活进度条的长度
data() {
return {
//进度条激活长度
activeLineBarWidth: 0,
}
},
进度条开始计时
start(progress){
this.calcActiveLineWidth(progress);
}
计算进度条激活长度--进度比重*总宽度,同时对越界的情况进行处理
calcActiveLineWidth(progress){
// 处理越界
progress = progress > 100 ? 100 : progress;
progress = progress < 0 ? 0 : progress;
// 计算已被激活的长条(线条)进度条的宽度
// 背景总宽度
let bgWidth = this.$props.lineProgressBarWidth;
// 已激活的长条进度条宽度
let activeLineBarWidth = bgWidth * (progress / 100.0);
// 避免越界
activeLineBarWidth = activeLineBarWidth < 0 ? 0 : activeLineBarWidth;
activeLineBarWidth = activeLineBarWidth > bgWidth ? bgWidth : activeLineBarWidth;
this.activeLineBarWidth = activeLineBarWidth;
console.error("宽度:",this.activeLineBarWidth);
},
父组件调用
this.$refs.lineProgressBar.start(10)
uni-app的内置进度条
此处还可以使用uni-app自带progress组件,也可以实现长条进度条的效果,具体使用可以看官方文档
uniapp.dcloud.net.cn/component/p…
但这个内置组件的圆角属性在抖音小程序不支持,如果想要圆角属性可以适配多种小程序,可以尝试本文中的插件。
圆形进度条的实现
<view v-else-if="mode === 'ring'" class="circle" :style="[circle]">
<view class="left" :style="[size]">
<view class="left-circle"
:style="[{'animation':stycircle1},{ 'animation-delay': animationDelay },{'animation-play-state':pause_text},size,leftCircle]">
</view>
</view>
<view class="right" :style="[size]">
<view class="right-circle"
:style="[{'animation':stycircle2},{'animation-play-state':pause_text},size,rightcircle]"></view>
</view>
<view class="inner" :style="[inner]">
<slot></slot>
</view>
</view>
样式实现
类元素设置位置和形状
- 通过.circle设置为圆形
- .left.right定位两个半圆的位置
.circle {
border-radius: 50%;
position: relative;
}
.left,.right {
position: absolute;
overflow: hidden;
}
.right {
right: 0;
}
行内属性设置尺寸和背景
- circle来定制外圆的宽高和背景
- size来定制内圆的尺寸(注意这里的宽度是取一半)
- leftCircle,rightcircle分别设置左边圆形和右半圆形以及被激活的背景
props: {
// 圆环的宽高大小 单位rpx
ringSize: {
type: Number,
default: 200
},
defaultColor: {
type: String,
default: '#CDCDCD'
},
},
computed: {
circle() {
const {
ringSize,
defaultColor
} = this
return {
width: `${ringSize}rpx`,
height: `${ringSize}rpx`,
background: defaultColor
}
},
size() {
const {
ringSize
} = this
return {
width: `${ringSize /2 +1}rpx`,
height: `${ringSize + 1}rpx`
}
},
// 设置左半圆
leftCircle() {
const {
ringSize,
activeColor
} = this
return {
borderTopLeftRadius: `${ringSize}rpx`,
borderBottomLeftRadius: `${ringSize}rpx`,
background: activeColor
}
},
rightcircle() {
const {
ringSize,
activeColor
} = this
return {
borderTopRightRadius: `${ringSize}rpx`,
borderBottomRightRadius: `${ringSize}rpx`,
background: activeColor
}
},
}
通过transform分别设置左右半圆的初始位置
.left-circle {
transition: all .5s;
transform-origin: right center;
transform: rotate(180deg);
}
.right-circle {
transition: all .5s;
transform-origin: left center;
transform: rotate(-180deg);
}
通过插槽定制圆内的内容
<view class="inner" :style="[inner]">
<slot></slot>
</view>
通过绝对定位+transform设置插槽容器相对于圆环水平垂直居中,再通过flex布局和overflow设置插槽里面的内容水平垂直居中和溢出隐藏
.inner {
background: #fff;
position: absolute;
z-index: 999;
border-radius: 100%;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
通过行内属性inner设置插槽的宽高
props:{
// 圆环的粗细值
border: {
type: Number,
default: 30
},
}
computed:{
inner() {
const {
ringSize,
border
} = this
return {
width: `${ringSize - border}rpx`,
height: `${ringSize - border}rpx`,
}
}
}
通过插槽可以定制圆环内的内容
比如显示剩余时间
<luanqing-progressbar
v-if="showMode === 'ring'"
ref="ringProgressBar"
active-color="#3C92FD"
default-color="#F2F6FF"
mode="ring"
:border="20"
:ringSize="180"
:deley="20000"
@pause="pause">
<view class="ring-content">
<view class="ring-text">剩余时间</view>
<view class="ring-time">{{ remainingTime }}</view>
</view>
</luanqing-progressbar>
动画实现
<view class="left-circle"
:style="[{'animation':stycircle1},{ 'animation-delay': animationDelay },{'animation-play-state':pause_text},size,leftCircle]">
</view>
<view class="right-circle"
:style="[{'animation':stycircle2},{'animation-play-state':pause_text},size,rightcircle]">
</view>
data中设置stycircle用于指定动画属性,animationDelay用于指定左半圆的延迟时间
props:{
// 圆环在多少毫秒闭环
deley: {
type: Number,
default: 10
},
}
data(){
return {
stycircle1: {},
stycircle2: {},
animationDelay: 0,
timeAll: 0, //时间
}
}
通过js控制动画的开始和停止
开始动画时设置动画属性,并将deley传给timeAll,同时开启计时器
start(){
if (this.deley < 100) {
uni.showToast({
title: '进度时间须>=100毫秒',
icon: 'none',
});
} else {
this.stop();
this.$nextTick(()=>{
// 将时间除以2,先走右半圆再走左半圆
let time = Number((this.deley / 2).toFixed(2));
this.stycircle2 = `progross2 ${time}ms linear forwards`;
this.animationDelay = `${time}ms`;
this.stycircle1 = `progross1 ${time}ms linear forwards`;
this.timeAll = this.deley;//变量存储动画时间
this.startInterval();
});
}
}
// 停止并重置
stop() {
// 停止动画
this.stycircle2 = `progross 0ms linear forwards`;
this.stycircle1 = `progross 0ms linear forwards`;
this.animationDelay = 0;
this.finish = false;
// 清空定时器
if(timerCount){
clearInterval(timerCount);
}
}
// 开始计时器
startInterval() {
// 如果已经存在就先清除
if(timerCount){
clearInterval(timerCount);
}
//计时器每100ms执行一次
timerCount = setInterval(() => {
this.timeAll -= 100;
// 时间小于1时,结束计时器
if (this.timeAll < 1) {
this.finish = true;
clearInterval(timerCount);
// 父组件可以通过自定义事件知道计时器已经结束
this.$emit('endTime');
}
}, 100)
},
设置左右半圆的动画旋转角度
@keyframes progross1 {
to {
transform: rotate(360deg);
}
}
@keyframes progross2 {
to {
transform: rotate(0deg);
}
}
同时它还支持暂停和继续动画的效果
// 暂停业务
pause() {
// 如果已经结束,直接返回
if (this.finish) {
return
}
// 清空计时器
if(timerCount){
clearInterval(timerCount);
}
// 设置animation-play-state的状态
this.pause_text = 'paused'
// 父组件可以获取暂停时的时间
this.$emit('pause', this.timeAll)
},
// 继续开始
resume() {
// 如果不是暂停状态,则直接返回
if (this.pause_text == '' || this.pause_text == 'running') {
return
}
// 设置animation-play-state的状态
this.pause_text = 'running';
// 重启定时器
this.startInterval();
},
组件销毁
destroyed() {
if(timerCount){
clearInterval(timerCount)
}
},
插件的缺陷
在实际测试中发现,该插件在ios系统版本为15的手机下会出现倒计时的动画无法生效,请教公司的前端大佬,大佬说可能样式被覆盖了,就给这个插件做了优化:给左右半圆都加上了3d透视点,同时加上了延迟时间。这样优化解决了无法生效的问题,但在低性能的手机的抖音小程序上还是会出现动画闪烁的问题,具体还不清楚怎么解决
.left,.right {
position: absolute;
overflow: hidden;
transform: perspective(1000);
}
.left-circle {
transform-origin: right center;
transform: rotate(180deg);
transition-delay: 0.01s;
}
.right-circle {
transition: all .5s;
transform-origin: left center;
transform: rotate(-180deg);
transition-delay: 0.01s;
}
结尾
看到这里的朋友如果觉得文章写的还不错的话,麻烦帮我点个赞,也可以点点收藏关注,万分感谢!!
转载自:https://juejin.cn/post/7253736317934190649