UniApp动效进度组件:打造流畅的用户体验

在移动应用开发中,进度展示是一个常见的交互需求。无论是文件上传、数据加载,还是步骤引导,都需要通过进度条来提供及时的反馈。本文将深入探讨如何在UniApp中实现一个动效丰富的进度组件,并融入鸿蒙设计理念,打造出专业的用户体验。

设计思路

在开发进度组件之前、我们需要考虑以下关键点:

  1. 功能需求

    • 基础进度展示
    • 动画过渡效果
    • 自定义样式
    • 多种展示模式
  2. 交互体验

    • 平滑的动画
    • 状态反馈
    • 手势操作
    • 实时更新
  3. 性能优化

    • 动画性能
    • 渲染效率
    • 内存管理

组件实现

首先创建components/AnimProgress.vue组件:

<template>
  <view 
    class="anim-progress"
    :class="[
      `progress-${type}`,
      {
        'progress-active': active,
        'progress-vertical': vertical
      }
    ]"
    :style="containerStyle"
  >
    <!-- 进度轨道 -->
    <view 
      class="progress-track"
      :style="trackStyle"
    >
      <!-- 进度条 -->
      <view 
        class="progress-bar"
        :style="barStyle"
      >
        <!-- 条纹效果 -->
        <view 
          v-if="stripe"
          class="progress-stripe"
        />
        
        <!-- 进度文本 -->
        <text 
          v-if="showText && !vertical"
          class="progress-text"
          :style="textStyle"
        >
          {{ progressText }}
        </text>
      </view>
    </view>
    
    <!-- 垂直模式文本 -->
    <text 
      v-if="showText && vertical"
      class="progress-text vertical"
      :style="textStyle"
    >
      {{ progressText }}
    </text>
  </view>
</template>

<script>
const DEFAULT_DURATION = 200

export default {
  name: 'AnimProgress',
  
  props: {
    // 进度值(0-100)
    value: {
      type: Number,
      default: 0,
      validator: val => val >= 0 && val <= 100
    },
    // 进度条类型
    type: {
      type: String,
      default: 'primary',
      validator: val => [
        'primary',
        'success',
        'warning',
        'error'
      ].includes(val)
    },
    // 是否垂直模式
    vertical: {
      type: Boolean,
      default: false
    },
    // 是否显示条纹
    stripe: {
      type: Boolean,
      default: false
    },
    // 是否激活动画
    active: {
      type: Boolean,
      default: false
    },
    // 是否显示进度文本
    showText: {
      type: Boolean,
      default: true
    },
    // 进度文本格式
    format: {
      type: Function,
      default: value => `${value}%`
    },
    // 进度条高度
    strokeWidth: {
      type: [Number, String],
      default: 12
    },
    // 进度条颜色
    color: {
      type: String,
      default: ''
    },
    // 轨道颜色
    trackColor: {
      type: String,
      default: ''
    },
    // 文本颜色
    textColor: {
      type: String,
      default: ''
    },
    // 动画持续时间
    duration: {
      type: Number,
      default: DEFAULT_DURATION
    }
  },
  
  data() {
    return {
      currentValue: 0
    }
  },
  
  computed: {
    // 容器样式
    containerStyle() {
      const style = {}
      
      if (this.vertical) {
        style.height = typeof this.strokeWidth === 'number'
          ? `${this.strokeWidth}rpx`
          : this.strokeWidth
        style.width = '12rpx'
      } else {
        style.height = '12rpx'
        style.width = typeof this.strokeWidth === 'number'
          ? `${this.strokeWidth}rpx`
          : this.strokeWidth
      }
      
      return style
    },
    
    // 轨道样式
    trackStyle() {
      return {
        backgroundColor: this.trackColor || ''
      }
    },
    
    // 进度条样式
    barStyle() {
      const style = {
        backgroundColor: this.color || '',
        transition: `all ${this.duration}ms cubic-bezier(0.4, 0, 0.2, 1)`
      }
      
      if (this.vertical) {
        style.height = `${this.currentValue}%`
        style.width = '100%'
      } else {
        style.width = `${this.currentValue}%`
        style.height = '100%'
      }
      
      return style
    },
    
    // 文本样式
    textStyle() {
      return {
        color: this.textColor || ''
      }
    },
    
    // 进度文本
    progressText() {
      return this.format(Math.round(this.currentValue))
    }
  },
  
  watch: {
    value: {
      handler(val) {
        this.updateProgress(val)
      },
      immediate: true
    }
  },
  
  methods: {
    // 更新进度
    updateProgress(value) {
      if (this.currentValue === value) return
      
      // 触发过渡开始事件
      this.$emit('transitionstart')
      
      this.currentValue = value
      
      // 设置过渡结束监听
      clearTimeout(this._timer)
      this._timer = setTimeout(() => {
        this.$emit('transitionend')
      }, this.duration)
    }
  },
  
  beforeDestroy() {
    clearTimeout(this._timer)
  }
}
</script>

<style lang="scss">
.anim-progress {
  position: relative;
  border-radius: 100rpx;
  background-color: #f5f5f5;
  transition: all 0.3s;
  
  // 垂直模式
  &.progress-vertical {
    width: 12rpx;
    
    .progress-track {
      height: 100%;
      
      .progress-bar {
        position: absolute;
        bottom: 0;
        left: 0;
        width: 100%;
        min-height: 0;
        
        .progress-stripe {
          background-image: linear-gradient(
            45deg,
            rgba(255, 255, 255, 0.15) 25%,
            transparent 25%,
            transparent 50%,
            rgba(255, 255, 255, 0.15) 50%,
            rgba(255, 255, 255, 0.15) 75%,
            transparent 75%,
            transparent
          );
          background-size: 20rpx 20rpx;
        }
      }
    }
    
    .progress-text {
      position: absolute;
      left: 24rpx;
      top: 50%;
      transform: translateY(-50%);
      white-space: nowrap;
    }
  }
  
  // 水平模式
  &:not(.progress-vertical) {
    height: 12rpx;
    
    .progress-track {
      width: 100%;
      
      .progress-bar {
        height: 100%;
        min-width: 0;
        
        .progress-stripe {
          background-image: linear-gradient(
            45deg,
            rgba(255, 255, 255, 0.15) 25%,
            transparent 25%,
            transparent 50%,
            rgba(255, 255, 255, 0.15) 50%,
            rgba(255, 255, 255, 0.15) 75%,
            transparent 75%,
            transparent
          );
          background-size: 20rpx 20rpx;
        }
        
        .progress-text {
          position: absolute;
          right: 8rpx;
          top: 50%;
          transform: translateY(-50%);
          color: #fff;
          font-size: 24rpx;
        }
      }
    }
  }
  
  // 进度轨道
  .progress-track {
    position: relative;
    width: 100%;
    height: 100%;
    border-radius: inherit;
    overflow: hidden;
    
    // 进度条
    .progress-bar {
      position: relative;
      border-radius: inherit;
      transition: inherit;
      
      // 条纹效果
      .progress-stripe {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
      }
    }
  }
  
  // 主题变体
  &.progress-primary .progress-bar {
    background-color: #2196f3;
  }
  
  &.progress-success .progress-bar {
    background-color: #4caf50;
  }
  
  &.progress-warning .progress-bar {
    background-color: #ff9800;
  }
  
  &.progress-error .progress-bar {
    background-color: #f44336;
  }
  
  // 激活动画
  &.progress-active {
    .progress-stripe {
      animation: progress-stripe 1s linear infinite;
    }
  }
}

// 条纹动画
@keyframes progress-stripe {
  from {
    background-position: 0 0;
  }
  to {
    background-position: 20rpx 0;
  }
}
</style>

使用示例

在页面中使用进度组件:

<template>
  <view class="demo-page">
    <!-- 基础用法 -->
    <view class="demo-section">
      <view class="section-title">基础用法</view>
      <anim-progress :value="50" />
    </view>
    
    <!-- 主题类型 -->
    <view class="demo-section">
      <view class="section-title">主题类型</view>
      <anim-progress 
        type="primary" 
        :value="progress.primary"
      />
      <anim-progress 
        type="success" 
        :value="progress.success"
      />
      <anim-progress 
        type="warning" 
        :value="progress.warning"
      />
      <anim-progress 
        type="error" 
        :value="progress.error"
      />
    </view>
    
    <!-- 条纹动画 -->
    <view class="demo-section">
      <view class="section-title">条纹动画</view>
      <anim-progress 
        :value="70"
        stripe
        active
      />
    </view>
    
    <!-- 垂直方向 -->
    <view class="demo-section">
      <view class="section-title">垂直方向</view>
      <view class="vertical-demo">
        <anim-progress 
          v-for="type in types"
          :key="type"
          :type="type"
          :value="progress[type]"
          vertical
          stripe
          active
        />
      </view>
    </view>
    
    <!-- 自定义样式 -->
    <view class="demo-section">
      <view class="section-title">自定义样式</view>
      <anim-progress
        :value="85"
        stroke-width="40"
        color="#722ed1"
        track-color="#f0f0f0"
        text-color="#722ed1"
        :format="val => `完成度 ${val}%`"
      />
    </view>
  </view>
</template>

<script>
import AnimProgress from '@/components/AnimProgress.vue'

export default {
  components: {
    AnimProgress
  },
  
  data() {
    return {
      types: ['primary', 'success', 'warning', 'error'],
      progress: {
        primary: 90,
        success: 75,
        warning: 60,
        error: 45
      }
    }
  }
}
</script>

<style lang="scss">
.demo-page {
  padding: 30rpx;
  
  .demo-section {
    margin-bottom: 40rpx;
    
    .section-title {
      font-size: 28rpx;
      color: #666;
      margin-bottom: 20rpx;
    }
    
    .anim-progress {
      margin-bottom: 20rpx;
      
      &:last-child {
        margin-bottom: 0;
      }
    }
  }
  
  .vertical-demo {
    height: 400rpx;
    display: flex;
    justify-content: center;
    gap: 60rpx;
  }
}
</style>

功能说明

1. 基础功能

组件支持以下基础功能:

<!-- 基础进度条 -->
<anim-progress :value="50" />

<!-- 不同主题 -->
<anim-progress type="success" :value="75" />

<!-- 条纹动画 -->
<anim-progress 
  :value="70"
  stripe
  active
/>

<!-- 垂直方向 -->
<anim-progress 
  :value="80"
  vertical
/>

2. 自定义样式

支持多种样式定制:

<anim-progress
  :value="85"
  stroke-width="40"
  color="#722ed1"
  track-color="#f0f0f0"
  text-color="#722ed1"
  :format="val => `完成度 ${val}%`"
/>

3. 动画效果

实现平滑的过渡动画:

// 进度条样式
barStyle() {
  return {
    transition: `all ${this.duration}ms cubic-bezier(0.4, 0, 0.2, 1)`,
    width: `${this.currentValue}%`
  }
}

// 更新进度
updateProgress(value) {
  if (this.currentValue === value) return
  
  this.$emit('transitionstart')
  this.currentValue = value
  
  clearTimeout(this._timer)
  this._timer = setTimeout(() => {
    this.$emit('transitionend')
  }, this.duration)
}

性能优化

1. 动画优化

使用CSS3动画提升性能:

// 条纹动画
@keyframes progress-stripe {
  from {
    background-position: 0 0;
  }
  to {
    background-position: 20rpx 0;
  }
}

// 使用transform代替位置动画
.progress-text {
  transform: translateY(-50%);
}

2. 渲染优化

合理使用计算属性:

computed: {
  progressText() {
    return this.format(Math.round(this.currentValue))
  }
}

最佳实践

  1. 样式定制

    • 提供丰富的样式属性
    • 支持主题定制
    • 保持样式一致性
  2. 动画效果

    • 使用CSS3动画
    • 平滑的过渡效果
    • 适当的动画时长
  3. 代码组织

    • 组件化封装
    • 样式模块化
    • 逻辑解耦

总结

通过以上实现,我们完成了一个功能完备的进度组件。该组件不仅实现了基础的进度展示功能,还融入了鸿蒙OS的设计理念,提供了丰富的动画效果和流畅的交互体验。在实际开发中,我们可以根据具体需求对组件进行定制和扩展,例如:

  • 添加更多动画效果
  • 扩展交互方式
  • 优化性能表现
  • 增强可定制性

希望这篇文章能够帮助大家更好地理解和实现UniApp中的进度组件,同时也能够在实际开发中应用这些技巧,打造出更好的用户界面。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐