likes
comments
collection
share

uni-app实现进度条

作者站长头像
站长
· 阅读数 15

前言

大家好,这里是藤原豆腐店。这次项目的需求需要用到圆形进度条,在插件市场找到个进度条插件使用起来很方便,我趁着空闲时间研究了一下他的源码实现,学习别人是怎么封装插件的,下面是我对该进度条实现原理的梳理。

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>

样式实现

uni-app实现进度条

类元素设置位置和形状

  • 通过.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`,
    }
  }
}

通过插槽可以定制圆环内的内容

比如显示剩余时间uni-app实现进度条

<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;
}

结尾

看到这里的朋友如果觉得文章写的还不错的话,麻烦帮我点个赞,也可以点点收藏关注,万分感谢!!