绿满眶商城微信小程序-uniapp
xiangpei
2025-06-24 f64549c06c2e4fd3f47552ae0fcb3ae5ae79f796
首页视频性能优化
3个文件已修改
20个文件已添加
2415 ■■■■■ 已修改文件
pages/tabbar/index/home.vue 158 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/video/home-page.vue 52 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/video/video-play.vue 199 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
uni_modules/zero-loading/changelog.md 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
uni_modules/zero-loading/components/zero-loading/static/loading-annulus.vue 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
uni_modules/zero-loading/components/zero-loading/static/loading-atom.vue 108 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
uni_modules/zero-loading/components/zero-loading/static/loading-bounce.vue 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
uni_modules/zero-loading/components/zero-loading/static/loading-circle.vue 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
uni_modules/zero-loading/components/zero-loading/static/loading-equal.vue 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
uni_modules/zero-loading/components/zero-loading/static/loading-eyes.vue 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
uni_modules/zero-loading/components/zero-loading/static/loading-gear.vue 118 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
uni_modules/zero-loading/components/zero-loading/static/loading-locating.vue 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
uni_modules/zero-loading/components/zero-loading/static/loading-love.vue 201 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
uni_modules/zero-loading/components/zero-loading/static/loading-photo.vue 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
uni_modules/zero-loading/components/zero-loading/static/loading-pulse.vue 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
uni_modules/zero-loading/components/zero-loading/static/loading-radar.vue 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
uni_modules/zero-loading/components/zero-loading/static/loading-sun.vue 140 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
uni_modules/zero-loading/components/zero-loading/static/loading-surround.vue 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
uni_modules/zero-loading/components/zero-loading/static/loading-sword.vue 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
uni_modules/zero-loading/components/zero-loading/static/loading-wobble.vue 127 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
uni_modules/zero-loading/components/zero-loading/zero-loading.vue 186 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
uni_modules/zero-loading/package.json 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
uni_modules/zero-loading/readme.md 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/tabbar/index/home.vue
@@ -1,11 +1,14 @@
<template>
  <view class="video-container">
    <!-- 视频加载 -->
    <zero-loading v-show="videoLoading" type="circle" color="#0ebd57" text=""></zero-loading>
    <!-- 视频列表 -->
    <swiper
      class="video-swiper"
      vertical
      :current="currentIndex"
      @change="onSwiperChange"
      :duration="250"
      easing-function="linear"
    >
      <swiper-item
@@ -25,14 +28,16 @@
                <image src="/static/video/play.png" style="width: 45px;height: 45px" mode="aspectFit"></image>
              </view>
              <video
                v-if="index >= currentIndex - videoLiveOffset && index <= currentIndex + videoLiveOffset"
                :id="'video'+index"
                :ref="'video'+index"
                :src="item.videoUrl"
                :autoplay="false"
                :autoplay="index === currentIndex"
                :controls="false"
                :loop="true"
                :object-fit="item.objectFit"
                :enable-progress-gesture="false"
                :show-center-play-btn="false"
                class="video-item"
                @play="onPlay(item.id, index)"
                @pause="onPause(index)"
@@ -40,12 +45,13 @@
                @click="togglePlay(index)"
                @timeupdate="onTimeUpdate($event)"
                @loadedmetadata="onLoadedMetadata($event)"
                @waiting="videoWaiting(index)"
              ></video>
              <!-- 自定义控制条 -->
              <view
                @touchstart="handleTouchStart"
                @touchmove="handleTouchMove"
                @touchend="handleTouchEnd"
                @touchstart.stop="handleTouchStart"
                @touchmove.stop="handleTouchMove"
                @touchend.stop="handleTouchEnd"
                class="container">
                <!-- 进度条 - 整个区域可拖动 -->
                <view class="process-warp" :style="{ opacity: showProcess ? 1 : 0 }">
@@ -300,13 +306,11 @@
            startPlayTime: 0 // 这个视频从什么时候开始播放的
        },
        currentVideoIsPlaying: true, // 当前视频是否正在播放
        isFullScreen: false,
        windowHeight: 0,
        currentIndex: 0, // 当前播放的视频索引
        videoLoading: false, // 视频缓冲中
        videoList: [],   // 视频列表数据
        videoContexts: [], // 视频上下文对象集合
        videoBufferOffset: 0.1 ,// 视频预加载参数
        videoLiveOffset: 5, // 保留当前视频前后各多少个视频上下文
        videoLiveOffset: 2, // 保留当前视频前后各多少个视频上下文
        touchXY: {  // 监听左滑右滑
            startX: 0,
            endX: 0,
@@ -662,56 +666,6 @@
            }
        })
      },
    // 初始化视频上下文
    initVideoContexts() {
      const start = Math.max(0, this.currentIndex - this.videoLiveOffset);
      const end = Math.min(this.currentIndex + this.videoLiveOffset, this.videoList.length - 1);
      let contextsLength = this.videoContexts.length;
      if (contextsLength === 0) {
          // 第一次初始化
          for (let i = 0; i < this.videoList.length; i++) {
            if (i < start || i > end) {
                this.videoContexts.push(null)
            } else {
                let videoContent = uni.createVideoContext(`video${i}`, this);
                videoContent.seek(this.videoBufferOffset);
                videoContent.pause();
                this.videoContexts.push(videoContent);
            }
          }
      } else {
         for (let i = 0; i < this.videoList.length; i++) {
             contextsLength = this.videoContexts.length
            if (contextsLength - 1 >= i) {
                // 如果已经是null了就不用管,因为视频加载只会在后面push,前面已经设置为null则无需处理
                if (this.videoContexts[i] == null) {
                    continue
                }
                // 超出可视化范围的视频直接释放资源,并置为null
                if (i < start || i > end) {
                    if (this.videoContexts[i]) {
                        this.videoContexts[i].stop();
                        this.videoContexts[i] = null
                    }
                }
            } else {
                if (i < start || i > end) {
                    this.videoContexts.push(null);
                } else {
                    let videoContent = uni.createVideoContext(`video${i}`, this);
                    videoContent.seek(this.videoBufferOffset);
                    videoContent.pause();
                    this.videoContexts.push(videoContent);
                }
            }
         }
      }
      // 将当前视频设置为播放
      if (this.videoContexts[this.currentIndex]) {
          this.videoContexts[this.currentIndex].play()
      }
    },
    // 加载视频数据
    async loadVideos() {
@@ -730,9 +684,6 @@
              ),
            ];
          }
          this.$nextTick(() => {
            this.initVideoContexts();
          });
          this.loading = false;
          if(res.data.data.length < this.videoQuery.pageSize) {
              this.videoNoMore = true;
@@ -745,6 +696,7 @@
    // 滑动切换视频
    onSwiperChange(e) {
        this.videoLoading = false
        // 如果视频处于暂停状态往下刷视频,那么需要再计算一次暂停时间
        if(!this.currentVideoIsPlaying) {
            if(this.startPauseTime !== 0) {
@@ -756,49 +708,23 @@
        this.savePlayRecord()
        const oldIndex = this.currentIndex;
        this.currentIndex = e.detail.current;
        const videoContext = uni.createVideoContext(`video${oldIndex}`, this);
        // 暂停上一个视频
        if (this.videoContexts[oldIndex]) {
            this.videoContexts[oldIndex].pause();
        }
        videoContext.pause();
        this.startPauseTime = 0;
        // 设置当前播放视频的总时长
        this.duration = this.videoList[this.currentIndex].videoDuration;
        this.formartDuration = this.sliderFormatTime(this.duration);
        // 播放当前视频
        if (this.videoContexts[this.currentIndex]) {
            this.videoContexts[this.currentIndex].play();
        }
        this.clearVideoContext()
    },
    // 清除超出视频可视化区域的视频上下文
    async clearVideoContext() {
        // 对超出可视化区域的视频上下文做销毁处理
        const start = Math.max(0, this.currentIndex - this.videoLiveOffset);
        const end = Math.min(this.currentIndex + this.videoLiveOffset, this.videoList.length - 1);
        for (let i = 0; i < this.videoContexts.length; i++) {
            if (i < start || i > end) {
                if (this.videoContexts[i]) {
                    this.videoContexts[i].stop();
                    this.videoContexts[i] = null
                }
            } else {
                if (this.videoContexts[i] == null) {
                    let videoContent = uni.createVideoContext(`video${i}`, this);
                    videoContent.seek(this.videoBufferOffset);
                    videoContent.pause();
                    this.videoContexts[i] = videoContent;
                }
            }
        }
        // 播放当前视频
        const videoContext1 = uni.createVideoContext(`video${this.currentIndex}`, this);
        videoContext1.play()
        // 如果剩余视频不足,触发请求获取更多视频
        if (this.videoList.length - 1 < this.currentIndex + this.videoLiveOffset) {
            this.loadVideos()
        }
    },
    },
    // 开始触摸
    handleSwiperStart(e) {
@@ -808,12 +734,15 @@
    },
    // 触摸中
    handleSwiperMove(e) {
        console.log("触摸中", e);
        this.touchXY.endX = e.touches[0].pageX
        this.touchXY.endY = e.touches[0].pageY
    },
    // 结束触摸
    handleSwiperEnd(item) {
        // 防止滑动滚动条也触发跳转
        if (this.showProcess) {
            return
        }
        const diffX = this.touchXY.endX - this.touchXY.startX
        const diffY = this.touchXY.endY - this.touchXY.startY
@@ -861,18 +790,16 @@
    },
    // 单击屏幕:暂停或继续播放
    togglePlay(index) {
        console.log("单击视频", index, this.videoContexts);
        console.log("单击视频", index);
        const videoContext = uni.createVideoContext(`video${index}`, this);
        if(this.currentVideoIsPlaying) {
            this.videoContexts[index].pause();
            videoContext.pause();
        } else {
            this.videoContexts[index].play();
            videoContext.play();
        }
    },
    // 视频播放事件
    onPlay(id, index) {
        this.getBarRect()
        this.progress = 0
        console.log(id, index, "触发播放");
        if(index === this.currentIndex) {
            this.currentVideoIsPlaying = true;
            if(! this.duration) {
@@ -881,9 +808,11 @@
                this.formartDuration = this.sliderFormatTime(this.duration);
            }
        } else {
            this.currentVideoIsPlaying = false;
            return
        }
        this.getBarRect()
        this.progress = 0
        console.log(id, index, "触发播放");
        this.playRecord.videoId = id;
        // 没初始化才赋值,因为一个视频重复播放onPlay会重复触发
        if(this.playRecord.startPlayTime === 0) {
@@ -893,7 +822,7 @@
            const duration = Date.now() - this.startPauseTime
            this.totalPauseTime += duration
        }
        this.videoLoading = false
    },
    // 视频暂停事件
@@ -901,11 +830,8 @@
        console.log(index, "触发暂停");
        if(index === this.currentIndex) {
            this.currentVideoIsPlaying = false;
        } else {
            this.currentVideoIsPlaying = true;
            return
            this.startPauseTime = Date.now()
        }
      this.startPauseTime = Date.now()
    },
    // 视频结束事件
    onEnded(index) {
@@ -914,6 +840,7 @@
    // 记录播放时长
    onTimeUpdate(e) {
        this.videoLoading = false
        this.playRecord.playAt = e.detail.currentTime;
        this.currentTime = e.detail.currentTime;
@@ -926,7 +853,8 @@
      this.startProgress = this.progress; // 记录开始时的进度
      this.startX = e.touches[0].pageX;
      console.log("记录开始时的进度", this.startProgress);
      this.videoContexts[this.currentIndex].pause()
      const videoContext = uni.createVideoContext(`video${this.currentIndex}`, this);
      videoContext.pause()
      // this.updateProgress(e);
    },
@@ -934,7 +862,6 @@
    handleTouchMove(e) {
      if (!this.isDragging || !this.barWidth) return;
      clearTimeout(this.processHidenTimer)
      this.videoContexts[this.currentIndex].pause()
      this.updateProgress(e);
    },
@@ -942,8 +869,9 @@
    handleTouchEnd() {
      this.isDragging = false;
      console.log("滑动结束", this.duration * this.progress);
      this.videoContexts[this.currentIndex].seek(this.duration * this.progress / 100)
      this.videoContexts[this.currentIndex].play()
      const videoContext = uni.createVideoContext(`video${this.currentIndex}`, this);
      videoContext.seek(this.duration * this.progress / 100)
      videoContext.play()
      this.processHidenTimer = setTimeout(() => {
          this.showProcess = false;
        }, 1000);
@@ -959,7 +887,6 @@
        // 将像素距离转换为进度增量
        const deltaProgress = (deltaX / this.barWidth) * 100;
        console.log("进度增量", deltaProgress);
        // 计算新进度 = 开始时的进度 + 滑动增量
        let newProgress = this.startProgress + deltaProgress;
@@ -967,6 +894,13 @@
        newProgress = Math.max(0, Math.min(100, newProgress));
        this.progress = newProgress;
    },
    // 视频缓冲
    videoWaiting(index) {
        if (index === this.currentIndex) {
            console.log("视频缓冲中。。。");
            this.videoLoading = true;
        }
    },
    // 获取视频总时长
    onLoadedMetadata(e) {
@@ -1015,7 +949,7 @@
    .video-item {
      width: 100%;
      height: 100%;
      object-fit: cover;
      /* object-fit: cover; */
    }
    .play-icon {
      position: absolute;
pages/video/home-page.vue
@@ -24,47 +24,47 @@
          <text class="stat-label">获赞</text>
        </view>
      </view>
      <!-- 关注按钮 -->
      <view class="follow-btn-container" v-if="!userInfo.self">
        <button
          class="follow-btn"
          :class="{followed: userInfo.hasSub}"
        <button
          class="follow-btn"
          :class="{followed: userInfo.hasSub}"
          @click="toggleFollow"
        >
          {{userInfo.hasSub ? '取消关注' : '关注'}}
        </button>
      </view>
      <view class="edit-icon" @click="editInfo" v-if="userInfo.self">
        <uni-icons type="compose" size="20" color="#666"></uni-icons>编辑主页信息
      </view>
    </view>
        <!-- 作品/喜欢切换 -->
        <view class="tab-bar">
          <view
            class="tab-item"
            :class="{active: currentTab === 'works'}"
          <view
            class="tab-item"
            :class="{active: currentTab === 'works'}"
            @click="switchTab('works')"
          >
            作品{{`(${videoTotal})`}}
          </view>
          <view
            class="tab-item"
            :class="{active: currentTab === 'likes'}"
          <view
            class="tab-item"
            :class="{active: currentTab === 'likes'}"
            @click="switchTab('likes')"
          >
            喜欢
          </view>
        </view>
        <!-- 视频列表 -->
        <scroll-view class="video-list" scroll-y :show-scrollbar="false" @scrolltolower="getPage" v-show="currentTab === 'works' && videoList.length > 0">
            <view class="video-container">
                <view
                  class="video-item"
                  v-for="(item, index) in videoList"
                  class="video-item"
                  v-for="(item, index) in videoList"
                  :key="item.id"
                >
                  <image class="video-cover" @click="playAuthorVideo(index)" :src="item.videoContentType === 'video' ? item.coverUrl : item.imgs[0]" mode="aspectFill"></image>
@@ -91,8 +91,8 @@
        <scroll-view class="video-list" scroll-y :show-scrollbar="false" @scrolltolower="getPage" v-show="currentTab === 'likes' && collectVideoList.length > 0">
            <view class="video-container">
                <view
                  class="video-item"
                  v-for="(item, index) in collectVideoList"
                  class="video-item"
                  v-for="(item, index) in collectVideoList"
                  :key="item.id"
                  @click="playCollectVideo(index)"
                >
@@ -105,10 +105,10 @@
                      </view>
                    </view>
                  </view>
                </view>
                </view>
            </view>
        </scroll-view>
        <!-- 空状态 -->
        <view class="empty-state" v-if="videoList.length === 0 && currentTab === 'works'">
          <!-- <image src="/static/images/empty.png" mode="aspectFit" class="empty-image"></image> -->
@@ -119,13 +119,13 @@
          <!-- <image src="/static/images/empty.png" mode="aspectFit" class="empty-image"></image> -->
          <text class="empty-text">还没有点赞作品哦~</text>
        </view>
        <!-- 删除视频提醒框 -->
        <uni-popup ref="delDialog" type="dialog">
            <uni-popup-dialog type="error" cancelText="取消" confirmText="删除" title="提醒" :content="`您正在删除:${opVideo.title}`" @confirm="deleteVideo"
                @close="dialogClose"></uni-popup-dialog>
        </uni-popup>
        <!-- 下架视频提醒框 -->
        <uni-popup ref="downDialog" type="dialog">
            <uni-popup-dialog type="error" cancelText="取消" confirmText="下架" title="提醒" :content="`您正在下架:${opVideo.title}`" @confirm="downVideo"
@@ -383,14 +383,14 @@
        url: `/pages/video/home-page-edit?authorId=${this.authorId}&avatar=${this.userInfo.avatar}&motto=${this.userInfo.motto || ''}&nickName=${this.userInfo.nickName}`
      });
    },
    // 跳转到粉丝/关注列表
    navigateToFollow(type) {
      uni.navigateTo({
        url: `/pages/user/follow?type=${type}`
      });
    },
    // 跳转到点赞列表
    navigateToLike() {
      uni.navigateTo({
@@ -624,15 +624,15 @@
  height: 70rpx;
  line-height: 70rpx;
  padding: 0 40rpx;
  &::after {
    border: none;
  }
  &.followed {
    background-color: #f5f5f5;
    color: #666;
  }
}
</style>
</style>
pages/video/video-play.vue
@@ -1,32 +1,43 @@
<template>
  <view class="video-container">
    <!-- 视频加载 -->
    <zero-loading v-show="videoLoading" type="circle" color="#0ebd57" text=""></zero-loading>
    <!-- 视频列表 -->
    <swiper 
      class="video-swiper" 
      vertical 
      circular
      :current="currentIndex"
      @change="onSwiperChange"
      :duration="250"
      easing-function="linear"
    >
      <swiper-item v-for="(item, index) in videoList" :key="item.id">
      <swiper-item
        v-for="(item, index) in videoList"
        :key="item.id"
        @touchstart="handleSwiperStart"
        @touchmove="handleSwiperMove"
        @touchend="handleSwiperEnd(item)"
         >
          <view style="width: 100%;height: 100%;" v-if="item.videoContentType === 'video'">
                <!-- 播放按钮(仅当视频暂停时显示) -->
                <view 
                  class="play-icon" 
                  @click="togglePlay(index)"
                  v-if="!currentVideoIsPlaying"
                  v-show="!currentVideoIsPlaying"
                >
                  <image src="/static/video/play.png" style="width: 45px;height: 45px" mode="aspectFit"></image>
                </view>
                <video
                v-if="index >= currentIndex - videoLiveOffset && index <= currentIndex + videoLiveOffset"
                  :id="'video'+index"
                  :ref="'video'+index"
                  :src="item.videoUrl"
                  :autoplay="currentIndex === index"
                  :autoplay="index === currentIndex"
                  :controls="false"
                  :loop="true"
                  :object-fit="item.objectFit"
                  :enable-progress-gesture="false"
                :show-center-play-btn="false"
                  class="video-item"
                  @play="onPlay(item.id, index)"
                  @pause="onPause(index)"
@@ -34,13 +45,13 @@
                  @click="togglePlay(index)"
                  @timeupdate="onTimeUpdate($event)"
                  @loadedmetadata="onLoadedMetadata($event)"
                  @waiting="videoWaiting(index)"
                ></video>
                <!-- 自定义控制条 -->
                <view 
                  @touchstart="handleTouchStart"
                  @touchmove="handleTouchMove"
                  @touchend="handleTouchEnd"
                  @touchstart.stop="handleTouchStart"
                  @touchmove.stop="handleTouchMove"
                  @touchend.stop="handleTouchEnd"
                  class="container">
                  <!-- 进度条 - 整个区域可拖动 -->
                  <view class="process-warp" :style="{ opacity: showProcess ? 1 : 0 }">
@@ -291,24 +302,28 @@
            startPlayTime: 0 // 这个视频从什么时候开始播放的
        },
        currentVideoIsPlaying: true, // 当前视频是否正在播放
        isFullScreen: false,
        windowHeight: 0,
        currentIndex: 0, // 当前播放的视频索引
        videoList: [
        ],   // 视频列表数据
        videoContexts: [], // 视频上下文对象集合
        videoLoading: false, // 视频缓冲中
        videoList: [],   // 视频列表数据
        videoBufferOffset: 0.1 ,// 视频预加载参数
        videoLiveOffset: 2, // 保留当前视频前后各多少个视频上下文
        touchXY: {  // 监听左滑右滑
            startX: 0,
            endX: 0,
            startY: 0,
            endY: 0
        },
        loading: false,  // 是否正在加载
        videoQuery: {
            pageNumber: 1,
            pageSize: 6,
            pageSize: 10,
            authorId: '',
            videoFrom: ''
        }
    }
  },
  onShow() {
      this.loadVideos()
      // this.loadVideos()
      // 如果视频按下暂停后切换页面再回到页面时,只算暂停时间(因为暂停时间和离开页面时间是重复的,只算一个)
      if(this.startHidenTime !== 0 && this.currentVideoIsPlaying) {
          const duration = Date.now() - this.startHidenTime
@@ -321,24 +336,28 @@
  onUnload() {
      uni.removeStorageSync("playInfo");
  },
  onReady() {
  },
  onLoad(option) {
      const playInfo = uni.getStorageSync("playInfo", playInfo);
      if(playInfo) {
          this.currentIndex = playInfo.playIndex;
          this.videoList = playInfo.videoList;
          console.log("拿到数据了",playInfo);
          this.videoQuery.pageNumber = playInfo.pageNumber;
          this.videoNoMore = playInfo.nomore;
          this.videoQuery.authorId = option.authorId;
          this.videoQuery.videoFrom = option.videoFrom;
          this.currentIndex = playInfo.playIndex;
          this.currentVideoIsPlaying = true;
          this.$nextTick(() => {
              const videoContext = uni.createVideoContext(`video${this.currentIndex}`, this);
              videoContext.play()
          })
      } else {
          this.videoQuery.videoFrom = 'recommend';
          this.loadVideos();
      }
  },
  onReady() {
    // 初始化视频上下文
    this.initVideoContexts();
  },
  onShareAppMessage(e) {
      const userInfo = storage.getUserInfo();
@@ -623,13 +642,6 @@
            }
        })
      },
    // 初始化视频上下文
    initVideoContexts() {
      this.videoContexts = this.videoList.map((_, index) => {
          let videoContent = uni.createVideoContext(`video${index}`, this);
          return videoContent;
      });
    },
    
    // 加载视频数据
    async loadVideos() {
@@ -647,9 +659,6 @@
              ),
            ];
          }
          this.$nextTick(() => {
            this.initVideoContexts();
          });
          this.loading = false;
          if(res.data.data.length < this.videoQuery.pageSize) {
              this.videoNoMore = true;
@@ -661,29 +670,75 @@
    
    // 滑动切换视频
    onSwiperChange(e) {
        // 如果视频处于暂停状态往下刷视频,那么需要再计算一次暂停时间
        if(!this.currentVideoIsPlaying) {
            if(this.startPauseTime !== 0) {
                const duration = Date.now() - this.startPauseTime
                this.totalPauseTime += duration
            }
        }
        // 保存上一个视频的播放记录
        this.savePlayRecord()
        const oldIndex = this.currentIndex;
        this.currentIndex = e.detail.current;
        // 暂停上一个视频
        if (this.videoContexts[oldIndex]) {
            this.videoContexts[oldIndex].pause();
        this.videoLoading = false
        // 如果视频处于暂停状态往下刷视频,那么需要再计算一次暂停时间
        if(!this.currentVideoIsPlaying) {
            if(this.startPauseTime !== 0) {
                const duration = Date.now() - this.startPauseTime
                this.totalPauseTime += duration
            }
        }
        // 保存上一个视频的播放记录
        this.savePlayRecord()
        const oldIndex = this.currentIndex;
        this.currentIndex = e.detail.current;
        const videoContext = uni.createVideoContext(`video${oldIndex}`, this);
        // 暂停上一个视频
        videoContext.pause();
        this.startPauseTime = 0;
        // 设置当前播放视频的总时长
        this.duration = this.videoList[this.currentIndex].videoDuration;
        this.formartDuration = this.sliderFormatTime(this.duration);
        // 播放当前视频
        const videoContext1 = uni.createVideoContext(`video${this.currentIndex}`, this);
        videoContext1.play()
        // 如果剩余视频不足,触发请求获取更多视频
        if (this.videoList.length - 1 < this.currentIndex + this.videoLiveOffset) {
            this.loadVideos()
        }
    },
    // 开始触摸
    handleSwiperStart(e) {
        this.touchXY.startX = e.touches[0].pageX
        this.touchXY.startY = e.touches[0].pageY
    },
    // 触摸中
    handleSwiperMove(e) {
        this.touchXY.endX = e.touches[0].pageX
        this.touchXY.endY = e.touches[0].pageY
    },
    // 结束触摸
    handleSwiperEnd(item) {
        // 防止滑动滚动条也触发跳转
        if (this.showProcess) {
            return
        }
        
        this.startPauseTime = 0;
        // 播放当前视频
        if (this.videoContexts[this.currentIndex]) {
            this.videoContexts[this.currentIndex].play();
        const diffX = this.touchXY.endX - this.touchXY.startX
        const diffY = this.touchXY.endY - this.touchXY.startY
        // 判断是否是横向滑动(X轴变化大于Y轴变化)
        if (Math.abs(diffX) > Math.abs(diffY)) {
          if (diffX > 0) {
            console.log('右滑')
            if (item.goodsList && item.goodsList.length > 0) {
                this.jumpToPay(item.id)
            }
          } else {
            console.log('左滑')
          }
        }
        // 重置坐标
        this.touchXY = {
            startX: 0,
            endX: 0,
            startY: 0,
            endY: 0
        }
    },
    },
    
    // 获取进度条的位置和尺寸
    getBarRect() {
@@ -703,7 +758,8 @@
      this.startProgress = this.progress; // 记录开始时的进度
      this.startX = e.touches[0].pageX;
      console.log("记录开始时的进度", this.startProgress);
      this.videoContexts[this.currentIndex].pause()
      const videoContext = uni.createVideoContext(`video${this.currentIndex}`, this);
      videoContext.pause()
      // this.updateProgress(e);
    },
    
@@ -711,7 +767,6 @@
    handleTouchMove(e) {
      if (!this.isDragging || !this.barWidth) return;
      clearTimeout(this.processHidenTimer)
      this.videoContexts[this.currentIndex].pause()
      this.updateProgress(e);
    },
    
@@ -719,11 +774,12 @@
    handleTouchEnd() {
      this.isDragging = false;
      console.log("滑动结束", this.duration * this.progress);
      this.videoContexts[this.currentIndex].seek(this.duration * this.progress / 100)
      this.videoContexts[this.currentIndex].play()
      const videoContext = uni.createVideoContext(`video${this.currentIndex}`, this);
      videoContext.seek(this.duration * this.progress / 100)
      videoContext.play()
      this.processHidenTimer = setTimeout(() => {
          this.showProcess = false;
        }, 1000);
                this.showProcess = false;
              }, 1000);
    },
    
    // 更新进度
@@ -736,7 +792,6 @@
        
        // 将像素距离转换为进度增量
        const deltaProgress = (deltaX / this.barWidth) * 100;
        console.log("进度增量", deltaProgress);
        // 计算新进度 = 开始时的进度 + 滑动增量
        let newProgress = this.startProgress + deltaProgress;
        
@@ -770,16 +825,16 @@
    },
    // 单击屏幕:暂停或继续播放
    togglePlay(index) {
        console.log("单击视频", index);
        const videoContext = uni.createVideoContext(`video${index}`, this);
        if(this.currentVideoIsPlaying) {
            this.videoContexts[index].pause();
            videoContext.pause();
        } else {
            this.videoContexts[index].play();
            videoContext.play();
        }
    },
    // 视频播放事件
    onPlay(id, index) {
        this.getBarRect()
        this.progress = 0
        if(index === this.currentIndex) {
            this.currentVideoIsPlaying = true;
            if(! this.duration) {
@@ -788,9 +843,11 @@
                this.formartDuration = this.sliderFormatTime(this.duration);
            }
        } else {
            this.currentVideoIsPlaying = false;
            return
        }
        this.getBarRect()
        this.progress = 0
        console.log(id, index, "触发播放");
        this.playRecord.videoId = id;
        // 没初始化才赋值,因为一个视频重复播放onPlay会重复触发
        if(this.playRecord.startPlayTime === 0) {
@@ -800,6 +857,7 @@
            const duration = Date.now() - this.startPauseTime
            this.totalPauseTime += duration
        }
        this.videoLoading = false
    },
    
    // 视频暂停事件
@@ -807,11 +865,8 @@
        console.log(index, "触发暂停");
        if(index === this.currentIndex) {
            this.currentVideoIsPlaying = false;
        } else {
            this.currentVideoIsPlaying = true;
            return
            this.startPauseTime = Date.now()
        }
      this.startPauseTime = Date.now()
    },
    
    // 视频结束事件
@@ -821,9 +876,17 @@
    
    // 记录播放时长
    onTimeUpdate(e) {
        this.videoLoading = false
        this.playRecord.playAt = e.detail.currentTime
        this.currentTime = e.detail.currentTime;
        this.progress = (e.detail.currentTime / this.duration) * 100
    },
    // 视频缓冲
    videoWaiting(index) {
        if (index === this.currentIndex) {
            console.log("视频缓冲中。。。");
            this.videoLoading = true;
        }
    },
    // 获取视频总时长
    onLoadedMetadata(e) {
@@ -872,7 +935,7 @@
    .video-item {
      width: 100%;
      height: 100%;
      object-fit: cover;
      /* object-fit: cover; */
    }
    .play-icon {
      position: absolute;
uni_modules/zero-loading/changelog.md
New file
@@ -0,0 +1,51 @@
## 1.4.2(2025-04-03)
## 增加两个动画
| locating | 定位 (自定义颜色) |
| photo    | 照片 (自定义颜色) |
## 1.4.1(2024-07-02)
### 1. 增加动画equal(等边), wobble(摇摆)
### 2. 原来的triangle(三角)改为surround(环绕)
### 3. 新增可自定义颜色项
## 1.4.0(2024-06-28)
## 增加 loading 加载文字提醒配置项,默认 false
## 1.3.2(2023-10-31)
修改遮罩默认透明度为 0.1
## 1.3.1(2023-10-31)
## 新增支持,自定义动画颜色(仅部分动画支持)
## 新增动画-annulus(圆环)
## 1.3.0(2023-08-11)
支持 vue3 使用, 增加动画类型 radar(雷达)
## 1.2.2(2023-06-12)
增加 maskOpacity, maskMini, maskDark 自定义参数, 提供更丰富的自定义遮罩层能力
## 1.2.1(2022-09-09)
增加齿轮动画 type=gear
## 1.2.0(2022-05-27)
1. 增加加载类型-剑气(sword),原子(atom)
2. 默认类型改为 atom
3. 遮罩透明度调整
## 1.1.1(2022-04-02)
更新使用说明
## 1.1.0(2022-02-23)
增加 type="love" 的心形加载动画
## 1.0.0(2022-01-28)
首次发布
uni_modules/zero-loading/components/zero-loading/static/loading-annulus.vue
New file
@@ -0,0 +1,45 @@
<template>
  <view class="animations">
    <view class="loader" :style="{ '--color': color }"></view>
  </view>
</template>
<script>
export default {
  name: "loading-annulus",
  props: {
    color: {
      type: String,
      default: "#0396FF",
    },
  },
  data() {
    return {};
  },
};
</script>
<style lang="scss" scoped>
.loader {
  width: 60px;
  height: 60px;
}
.loader::before {
  content: "";
  box-sizing: border-box;
  position: absolute;
  width: 60px;
  height: 60px;
  border-radius: 50%;
  border-top: 2px solid var(--color);
  border-right: 2px solid transparent;
  animation: spinner 1s linear infinite;
}
@keyframes spinner {
  to {
    transform: rotate(360deg);
  }
}
</style>
uni_modules/zero-loading/components/zero-loading/static/loading-atom.vue
New file
@@ -0,0 +1,108 @@
<template>
  <view class="animations">
    <view class="box" :style="{ '--color': color }">
      <view class="atom"></view>
      <view class="atom"></view>
      <view class="atom"></view>
      <view class="dot"></view>
    </view>
  </view>
</template>
<script>
export default {
  name: "loading-atom",
  props: {
    color: {
      type: String,
      default: "#0396FF",
    },
  },
  data() {
    return {};
  },
};
</script>
<style lang="scss" scoped>
.box {
  position: relative;
  width: 120rpx;
  height: 120rpx;
}
.dot {
  position: absolute;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background: var(--color);
  animation: dotbreath 2s linear infinite;
}
.atom {
  position: absolute;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  border-left-width: 6rpx;
  border-top-width: 6rpx;
  border-left-color: var(--color);
  border-left-style: solid;
  border-top-style: solid;
  border-top-color: transparent;
}
.atom:nth-of-type(1) {
  left: 0%;
  top: 0%;
  animation: atom1 1s linear infinite;
}
.atom:nth-of-type(2) {
  right: 0%;
  top: 0%;
  animation: atom2 1s linear infinite;
}
.atom:nth-of-type(3) {
  right: 0%;
  bottom: 0%;
  animation: atom3 1s linear infinite;
}
@keyframes dotbreath {
  0% {
    opacity: 1;
  }
  50% {
    opacity: 0.5;
  }
  100% {
    opacity: 1;
  }
}
@keyframes atom1 {
  0% {
    transform: rotateZ(120deg) rotateX(66deg) rotateZ(0deg);
  }
  100% {
    transform: rotateZ(120deg) rotateX(66deg) rotateZ(360deg);
  }
}
@keyframes atom2 {
  0% {
    transform: rotateZ(240deg) rotateX(66deg) rotateZ(0deg);
  }
  100% {
    transform: rotateZ(240deg) rotateX(66deg) rotateZ(360deg);
  }
}
@keyframes atom3 {
  0% {
    transform: rotateZ(360deg) rotateX(66deg) rotateZ(0deg);
  }
  100% {
    transform: rotateZ(360deg) rotateX(66deg) rotateZ(360deg);
  }
}
</style>
uni_modules/zero-loading/components/zero-loading/static/loading-bounce.vue
New file
@@ -0,0 +1,84 @@
<template>
  <view class="animations">
    <view class="box">
      <view class="dot dot1"></view>
      <view class="dot dot2"></view>
      <view class="dot dot3"></view>
    </view>
  </view>
</template>
<script>
export default {
  name: "loading-bounce",
  data() {
    return {};
  },
};
</script>
<style lang="scss" scoped>
.box {
  width: 100rpx;
  height: 50rpx;
  position: relative;
}
.dot {
  width: 14rpx;
  height: 14rpx;
  background: #007aff;
  border-radius: 50%;
  position: absolute;
  top: calc(50% - 5rpx);
}
.dot1 {
  background: #1fa2ff;
  left: 0rpx;
  -webkit-animation: bounce 0.5s cubic-bezier(0.77, 0.47, 0.64, 0.28) alternate
    infinite;
  animation: bounce 0.5s cubic-bezier(0.77, 0.47, 0.64, 0.28) alternate infinite;
}
.dot2 {
  background: #12d8fa;
  left: 40rpx;
  -webkit-animation: bounce 0.5s 0.2s cubic-bezier(0.77, 0.47, 0.64, 0.28)
    alternate infinite;
  animation: bounce 0.5s 0.2s cubic-bezier(0.77, 0.47, 0.64, 0.28) alternate
    infinite;
}
.dot3 {
  background: #29ffc6;
  left: 80rpx;
  -webkit-animation: bounce 0.5s 0.4s cubic-bezier(0.77, 0.47, 0.64, 0.28)
    alternate infinite;
  animation: bounce 0.5s 0.4s cubic-bezier(0.77, 0.47, 0.64, 0.28) alternate
    infinite;
}
@-webkit-keyframes bounce {
  0% {
    -webkit-transform: translateY(0);
    transform: translateY(0);
  }
  100% {
    -webkit-transform: translateY(-20rpx);
    transform: translateY(-20rpx);
  }
}
@keyframes bounce {
  0% {
    -webkit-transform: translateY(0);
    transform: translateY(0);
  }
  100% {
    -webkit-transform: translateY(-20rpx);
    transform: translateY(-20rpx);
  }
}
</style>
uni_modules/zero-loading/components/zero-loading/static/loading-circle.vue
New file
@@ -0,0 +1,96 @@
<template>
  <view class="animations">
    <view class="loader" :style="{ '--color': color }"></view>
  </view>
</template>
<script>
export default {
  name: "loading-circle",
  props: {
    color: {
      type: String,
      default: "#0396FF",
    },
  },
  data() {
    return {};
  },
};
</script>
<style lang="scss" scoped>
// .container {
//     position: absolute;
//     top: 50%;
//     left: 50%;
//     transform: translate(-50%, -50%);
// }
.loader {
  display: block;
  width: 120rpx;
  height: 120rpx;
  border-radius: 50%;
  border: 3rpx solid transparent;
  border-top-color: var(--color);
  -webkit-animation: spin 2s linear infinite;
  animation: spin 2s linear infinite;
  position: relative;
}
.loader::before {
  content: "";
  position: absolute;
  top: 8rpx;
  left: 8rpx;
  right: 8rpx;
  bottom: 8rpx;
  border-radius: 50%;
  border: 3rpx solid transparent;
  border-top-color: var(--color);
  -webkit-animation: spin 3s linear infinite;
  animation: spin 3s linear infinite;
}
.loader::after {
  content: "";
  position: absolute;
  top: 16rpx;
  left: 16rpx;
  right: 16rpx;
  bottom: 16rpx;
  border-radius: 50%;
  border: 3rpx solid transparent;
  border-top-color: var(--color);
  -webkit-animation: spin 1.5s linear infinite;
  animation: spin 1.5s linear infinite;
}
@-webkit-keyframes spin {
  0% {
    -webkit-transform: rotate(0deg);
    -ms-transform: rotate(0deg);
    transform: rotate(0deg);
  }
  100% {
    -webkit-transform: rotate(360deg);
    -ms-transform: rotate(360deg);
    transform: rotate(360deg);
  }
}
@keyframes spin {
  0% {
    -webkit-transform: rotate(0deg);
    -ms-transform: rotate(0deg);
    transform: rotate(0deg);
  }
  100% {
    -webkit-transform: rotate(360deg);
    -ms-transform: rotate(360deg);
    transform: rotate(360deg);
  }
}
</style>
uni_modules/zero-loading/components/zero-loading/static/loading-equal.vue
New file
@@ -0,0 +1,81 @@
<template>
  <view class="animations">
    <view class="loader" :style="{ '--color': color }"></view>
  </view>
</template>
<script>
export default {
  name: "loading-equal",
  props: {
    color: {
      type: String,
      default: "#ff1919",
    },
  },
  data() {
    return {};
  },
};
</script>
<style lang="scss" scoped>
.loader {
  width: 50px;
  aspect-ratio: 1.154;
  position: relative;
  background: conic-gradient(
    from 120deg at 50% 64%,
    #0000,
    var(--color) 1deg 120deg,
    #0000 121deg
  );
  animation: spin 1.5s infinite cubic-bezier(0.3, 1, 0, 1);
}
.loader:before,
.loader:after {
  content: "";
  position: absolute;
  inset: 0;
  background: inherit;
  transform-origin: 50% 66%;
  animation: separate 1.5s infinite;
}
.loader:after {
  --s: -1;
}
@keyframes spin {
  0%,
  30% {
    transform: rotate(0);
  }
  70% {
    transform: rotate(120deg);
  }
  70.01%,
  100% {
    transform: rotate(360deg);
  }
}
@keyframes separate {
  0% {
    transform: rotate(calc(var(--s, 1) * 120deg)) translate(0);
  }
  30%,
  70% {
    transform: rotate(calc(var(--s, 1) * 120deg))
      translate(calc(var(--s, 1) * -5px), 10px);
  }
  100% {
    transform: rotate(calc(var(--s, 1) * 120deg)) translate(0);
  }
}
</style>
uni_modules/zero-loading/components/zero-loading/static/loading-eyes.vue
New file
@@ -0,0 +1,78 @@
<template>
  <view class="animations">
    <view class="box">
      <view class="eye"></view>
      <view class="eye"></view>
    </view>
  </view>
</template>
<script>
export default {
  name: "loading-eyes",
  data() {
    return {};
  },
};
</script>
<style lang="scss" scoped>
.box {
  width: 110rpx;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.eye {
  width: 50rpx;
  height: 50rpx;
  background: linear-gradient(135deg, #1fa2ff, #12d8fa);
  border-radius: 50%;
  position: relative;
}
.eye:after {
  background-color: #ffffff;
  width: 18rpx;
  height: 18rpx;
  border-radius: 50%;
  left: 20rpx;
  top: 24rpx;
  position: absolute;
  content: "";
  -webkit-animation: eyeball 1s linear infinite alternate;
  -moz-animation: eyeball 1s linear infinite alternate;
  animation: eyeball 1s linear infinite alternate;
}
@-webkit-keyframes eyeball {
  0% {
    left: 30rpx;
  }
  100% {
    left: 2rpx;
  }
}
@-moz-keyframes eyeball {
  0% {
    left: 30rpx;
  }
  100% {
    left: 2rpx;
  }
}
@keyframes eyeball {
  0% {
    left: 30rpx;
  }
  100% {
    left: 2rpx;
  }
}
</style>
uni_modules/zero-loading/components/zero-loading/static/loading-gear.vue
New file
@@ -0,0 +1,118 @@
<template>
  <view class="animations">
    <view class="box">
      <view class="gear1">
        <view class="inner inner1"> </view>
        <view class="inner inner2"> </view>
        <view class="inner inner3"> </view>
      </view>
      <view class="gear2">
        <view class="inner inner1"> </view>
        <view class="inner inner2"> </view>
        <view class="inner inner3"> </view>
      </view>
      <view class="gear3">
        <view class="inner inner1"> </view>
        <view class="inner inner2"> </view>
        <view class="inner inner3"> </view>
      </view>
    </view>
  </view>
</template>
<script>
export default {
  name: "loading-gear",
  data() {
    return {};
  },
};
</script>
<style lang="scss" scoped>
$size: 80rpx;
$bgc: red;
.box {
  width: 200rpx;
  height: 200rpx;
  position: relative;
}
@mixin gear($size: $size, $bgc: $bgc) {
  width: $size;
  height: $size;
  .inner {
    position: absolute;
    width: $size;
    height: $size;
    top: 0;
    left: 0;
    background: $bgc;
    border-radius: 6rpx;
    mask: radial-gradient(transparent 40%, #fff 60%);
  }
  .inner2 {
    transform: rotate(120deg);
  }
  .inner3 {
    transform: rotate(240deg);
  }
  // &:after {
  //     position: absolute;
  //     content: '';
  //     background: #fff;
  //     width: $size / 1.8;
  //     height: $size / 1.8;
  //     border-radius: 100%;
  //     top: 50%;
  //     left: 50%;
  //     transform: translate(-50%, -50%);
  // }
}
.gear1 {
  @include gear(60rpx, #0396ff);
  position: absolute;
  top: 35rpx;
  left: 35rpx;
  animation: rotate 5s infinite linear;
}
.gear2 {
  @include gear(50rpx, #dd524d);
  position: absolute;
  top: 50rpx;
  left: 110rpx;
  animation: rotateR 5s infinite linear;
}
.gear3 {
  @include gear(50rpx, #f0ad4e);
  position: absolute;
  top: 110rpx;
  left: 50rpx;
  animation: rotateR 5s infinite linear;
}
@keyframes rotate {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}
@keyframes rotateR {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(-360deg);
  }
}
</style>
uni_modules/zero-loading/components/zero-loading/static/loading-locating.vue
New file
@@ -0,0 +1,81 @@
<template>
    <view class="animations">
      <view class="loader" :style="{ '--color': color }"></view>
    </view>
  </template>
  <script>
  export default {
    name: "loading-locating",
    props: {
      color: {
        type: String,
        default: "#0396FF",
      },
    },
    data() {
      return {};
    },
  };
  </script>
  <style lang="scss" scoped>
  .loader {
    width: 96rpx;
    height: 96rpx;
    display: block;
    margin: 40rpx auto;
    box-sizing: border-box;
    position: relative;
  }
  .loader::after {
    content: '';
    width: 96rpx;
    height: 96rpx;
    left: 0;
    bottom: 0;
    position: absolute;
    border-radius: 50% 50% 0;
    border: 30rpx solid var(--color);
    transform: rotate(45deg) translate(0, 0);
    box-sizing: border-box;
    animation: animMarker 0.4s ease-in-out infinite alternate;
  }
  .loader::before {
    content: '';
    box-sizing: border-box;
    position: absolute;
    left: 0;
    right: 0;
    margin: auto;
    top: 150%;
    width: 48rpx;
    height: 8rpx;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.2);
    animation: animShadow 0.4s ease-in-out infinite alternate;
  }
  @keyframes animMarker {
    0% {
      transform: rotate(45deg) translate(10rpx, 10rpx);
    }
    100% {
      transform: rotate(45deg) translate(-10rpx, -10rpx);
    }
  }
  @keyframes animShadow {
    0% {
      transform: scale(0.5);
    }
    100% {
      transform: scale(1);
    }
  }
</style>
uni_modules/zero-loading/components/zero-loading/static/loading-love.vue
New file
@@ -0,0 +1,201 @@
<template>
  <view class="animations">
    <view class="box">
      <view class="item"></view>
      <view class="item"></view>
      <view class="item"></view>
      <view class="item"></view>
      <view class="item"></view>
      <view class="item"></view>
      <view class="item"></view>
      <view class="item"></view>
      <view class="item"></view>
    </view>
  </view>
</template>
<script>
export default {
  name: "loading-love",
  data() {
    return {};
  },
};
</script>
<style lang="scss" scoped>
.box {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-flow: row nowrap;
  height: 160rpx;
}
.item {
  background: linear-gradient(to bottom, #f00000, #e73827);
  width: 16rpx;
  height: 16rpx;
  border-radius: 20rpx;
  margin-right: 10rpx;
}
.item:nth-child(1) {
  animation: love1 4s infinite;
}
.item:nth-child(2) {
  animation: love2 4s infinite;
  animation-delay: 0.15s;
}
.item:nth-child(3) {
  animation: love3 4s infinite;
  animation-delay: 0.3s;
}
.item:nth-child(4) {
  animation: love4 4s infinite;
  animation-delay: 0.45s;
}
.item:nth-child(5) {
  animation: love5 4s infinite;
  animation-delay: 0.6s;
}
.item:nth-child(6) {
  animation: love4 4s infinite;
  animation-delay: 0.75s;
}
.item:nth-child(7) {
  animation: love3 4s infinite;
  animation-delay: 0.9s;
}
.item:nth-child(8) {
  animation: love2 4s infinite;
  animation-delay: 1.05s;
}
.item:nth-child(9) {
  animation: love1 4s infinite;
  animation-delay: 1.2s;
}
@keyframes love1 {
  30%,
  50% {
    height: 50rpx;
    transform: translateY(-20rpx);
  }
  75%,
  100% {
    height: 20rpx;
    transform: translateY(0);
  }
}
@keyframes love2 {
  30%,
  50% {
    height: 90rpx;
    transform: translateY(-25rpx);
  }
  75%,
  100% {
    height: 20rpx;
    transform: translateY(0);
  }
}
@keyframes love3 {
  30%,
  50% {
    height: 120rpx;
    transform: translateY(-20rpx);
  }
  75%,
  100% {
    height: 20rpx;
    transform: translateY(0);
  }
}
@keyframes love4 {
  30%,
  50% {
    height: 130rpx;
    transform: translateY(-10rpx);
  }
  75%,
  100% {
    height: 20rpx;
    transform: translateY(0);
  }
}
@keyframes love5 {
  30%,
  50% {
    height: 130rpx;
    transform: translateY(10rpx);
  }
  75%,
  100% {
    height: 20rpx;
    transform: translateY(0);
  }
}
// .item:nth-child(1) {
//     height: 50rpx;
//     transform: translateY(-20rpx);
// }
// .item:nth-child(2) {
//     height: 90rpx;
//     transform: translateY(-25rpx);
// }
// .item:nth-child(3) {
//     height: 120rpx;
//     transform: translateY(-20rpx);
// }
// .item:nth-child(4) {
//     height: 130rpx;
//     transform: translateY(-10rpx);
// }
// .item:nth-child(5) {
//     height: 130rpx;
//     transform: translateY(10rpx);
// }
// .item:nth-child(6) {
//     height: 130rpx;
//     transform: translateY(-10rpx);
// }
// .item:nth-child(7) {
//     height: 120rpx;
//     transform: translateY(-20rpx);
// }
// .item:nth-child(8) {
//     height: 90rpx;
//     transform: translateY(-25rpx);
// }
// .item:nth-child(9) {
//     height: 50rpx;
//     transform: translateY(-20rpx);
// }
</style>
uni_modules/zero-loading/components/zero-loading/static/loading-photo.vue
New file
@@ -0,0 +1,87 @@
<template>
  <view class="animations">
    <view class="loader" :style="{ '--color': color }"></view>
  </view>
</template>
<script>
export default {
  name: "loading-photo",
  props: {
    color: {
      type: String,
      default: "#0396FF",
    },
  },
  data() {
    return {};
  },
};
</script>
<style lang="scss" scoped>
.loader {
  width: 128rpx;
  height: 128rpx;
  position: relative;
  background: #f4f4f4;
  border-radius: 8rpx;
  overflow: hidden;
}
.loader:before {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 80rpx;
  height: 80rpx;
  transform: rotate(45deg) translate(30%, 40%);
  background: var(--color);
  box-shadow: 64rpx -68rpx 0 10rpx var(--color);
  animation: slide 2s infinite ease-in-out alternate;
}
.loader:after {
  content: "";
  position: absolute;
  left: 20rpx;
  top: 20rpx;
  width: 28rpx;
  height: 28rpx;
  border-radius: 50%;
  background: var(--color);
  transform: rotate(0deg);
  transform-origin: 70rpx 290rpx;
  animation: rotate 2s infinite ease-in-out;
}
@keyframes slide {
  0% , 100% {
    bottom: -70rpx
  }
  25% , 75% {
    bottom: -4rpx
  }
  20% , 80% {
    bottom: 4rpx
  }
}
@keyframes rotate {
  0% {
    transform: rotate(-15deg)
  }
  25% , 75% {
    transform: rotate(0deg)
  }
  100% {
    transform: rotate(25deg)
  }
}
</style>
uni_modules/zero-loading/components/zero-loading/static/loading-pulse.vue
New file
@@ -0,0 +1,67 @@
<template>
  <view class="animations">
    <view class="box" :style="{ '--color': color }">
      <view class="pulse-bubble pulse-bubble-1"></view>
      <view class="pulse-bubble pulse-bubble-2"></view>
      <view class="pulse-bubble pulse-bubble-3"></view>
    </view>
  </view>
</template>
<script>
export default {
  name: "loading-pulse",
  props: {
    color: {
      type: String,
      default: "#0396FF",
    },
  },
  data() {
    return {};
  },
};
</script>
<style lang="scss" scoped>
.box {
  width: 100rpx;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.pulse-bubble {
  width: 16rpx;
  height: 16rpx;
  border-radius: 50%;
  background: var(--color);
}
.pulse-bubble-1 {
  // background: #1fa2ff;
  animation: pulse 0.4s ease 0s infinite alternate;
}
.pulse-bubble-2 {
  // background: #12d8fa;
  animation: pulse 0.4s ease 0.2s infinite alternate;
}
.pulse-bubble-3 {
  // background: #29ffc6;
  animation: pulse 0.4s ease 0.4s infinite alternate;
}
@keyframes pulse {
  from {
    opacity: 1;
    transform: scale(1);
  }
  to {
    opacity: 0.25;
    transform: scale(0.75);
  }
}
</style>
uni_modules/zero-loading/components/zero-loading/static/loading-radar.vue
New file
@@ -0,0 +1,132 @@
<template>
  <view class="animations">
    <view class="radar">
      <view class="dot dot-1"></view>
      <view class="dot dot-2"></view>
      <view class="dot dot-3"></view>
      <view class="cover"></view>
    </view>
  </view>
</template>
<script>
export default {
  name: "loading-radar",
  data() {
    return {};
  },
};
</script>
<style lang="scss" scoped>
$size: 180rpx;
$dotSize: 4rpx;
$maincolor: #2da3f6;
.radar {
  position: relative;
  z-index: 1;
  height: $size;
  width: $size;
  background: -webkit-repeating-radial-gradient(
    rgba(45, 163, 246, 0) 0%,
    rgba(45, 163, 246, 0) 23%,
    rgba(45, 163, 246, 0.7) 24%,
    rgba(45, 163, 246, 0) 25%
  );
  margin: 0 auto;
  border-radius: 50%;
  border: 2rpx solid rgba(45, 163, 246, 0.7);
  overflow: hidden;
}
.radar::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: $dotSize;
  height: $dotSize;
  background: $maincolor;
  margin-left: -1rpx;
  margin-top: -1rpx;
  border-radius: 1rpx;
}
.dot {
  position: absolute;
  width: $dotSize;
  height: $dotSize;
  background: $maincolor;
  opacity: 0;
  border-radius: 50%;
  animation: breath 3s linear infinite;
  box-shadow: 0 0 2rpx 2rpx rgba(45, 163, 246, 0.5);
}
.dot-1 {
  top: 50rpx;
  left: 30rpx;
  animation-delay: 1s;
}
.dot-2 {
  top: 60rpx;
  right: 20rpx;
  animation-delay: 0.2s;
}
.dot-3 {
  top: 140rpx;
  right: 100rpx;
  animation-delay: 2.3s;
}
.cover {
  transform-origin: bottom right;
  border-right: 1rpx solid $maincolor;
  background: linear-gradient(
    45deg,
    rgba(255, 255, 255, 0) 45%,
    $maincolor 100%
  );
  width: 50%;
  height: 50%;
  position: absolute;
  top: 0;
  left: 0;
  animation: rotation 3s linear infinite;
}
@keyframes rotation {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}
@keyframes breath {
  0% {
    opacity: 0;
  }
  10% {
    opacity: 1;
  }
  20% {
    opacity: 1;
  }
  40% {
    opacity: 0;
  }
  100% {
    opacity: 0;
  }
}
</style>
uni_modules/zero-loading/components/zero-loading/static/loading-sun.vue
New file
@@ -0,0 +1,140 @@
<template>
  <view class="animations">
    <view class="box">
      <view class="sun"></view>
      <view class="orbit orbit1">
        <view class="planetX planet1"></view>
      </view>
      <view class="orbit orbit2">
        <view class="planetX planet2"></view>
      </view>
      <view class="orbit orbit3">
        <view class="planetX planet3"></view>
      </view>
    </view>
  </view>
</template>
<script>
export default {
  name: "loading-sun",
  data() {
    return {};
  },
};
</script>
<style lang="scss" scoped>
.box {
  width: 210rpx;
  height: 210rpx;
  position: relative;
}
.sun {
  background: radial-gradient(#ff0, #f90);
  height: 50rpx;
  width: 50rpx;
  border-radius: 50%;
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  margin: auto;
}
.planetX {
  position: absolute;
  z-index: 100;
  border-radius: 50%;
}
.planet1 {
  left: 20rpx;
  height: 13rpx;
  width: 13rpx;
  background-color: #fed313;
}
.planet2 {
  left: 23rpx;
  height: 20rpx;
  width: 20rpx;
  background: linear-gradient(#00ff00, #09f, #09f);
  -webkit-animation: rotation 1s infinite linear;
  animation: rotation 1s infinite linear;
}
.planet3 {
  left: 49rpx;
  height: 17rpx;
  width: 17rpx;
  background: radial-gradient(#ff9900, #ff4400);
}
.orbit {
  background: transparent;
  border-radius: 50%;
  border: 1rpx solid #cccccc;
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  margin: auto;
}
.orbit1 {
  height: 100rpx;
  width: 100rpx;
  -webkit-animation: rotation 2s infinite linear;
  -moz-animation: rotation 2s infinite linear;
  animation: rotation 2s infinite linear;
}
.orbit2 {
  height: 150rpx;
  width: 150rpx;
  -webkit-animation: rotation 3s infinite linear;
  -moz-animation: rotation 3s infinite linear;
  animation: rotation 3s infinite linear;
}
.orbit3 {
  height: 200rpx;
  width: 200rpx;
  -moz-animation: rotation 6s infinite linear;
  -webkit-animation: rotation 6s infinite linear;
  animation: rotation 6s infinite linear;
}
@-webkit-keyframes rotation {
  from {
    -webkit-transform: rotate(0deg);
  }
  to {
    -webkit-transform: rotate(359deg);
  }
}
@keyframes rotation {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(359deg);
  }
}
@-moz-keyframes rotation {
  from {
    -moz-transform: rotate(0deg);
  }
  to {
    -moz-transform: rotate(359deg);
  }
}
</style>
uni_modules/zero-loading/components/zero-loading/static/loading-surround.vue
New file
@@ -0,0 +1,91 @@
<template>
  <view class="animations">
    <view class="box">
      <view class="loader">
        <view class="loader__ball"></view>
        <view class="loader__ball"></view>
        <view class="loader__ball"></view>
      </view>
    </view>
  </view>
</template>
<script>
export default {
  name: "loading-triangle",
  data() {
    return {};
  },
};
</script>
<style lang="scss" scoped>
$dotColor: linear-gradient(135deg, #1fa2ff, #12d8fa, #29ffc6);
$dotSize: 30rpx;
$duration: 2s;
.animations {
  width: 160rpx;
  height: 160rpx;
  position: relative;
}
.box {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
.loader {
  animation: rotate $duration linear infinite normal;
  position: relative;
  transform-origin: 50% 50%;
  &__ball {
    height: $dotSize;
    width: $dotSize;
    left: -$dotSize * 0.5;
    position: absolute;
    top: -$dotSize * 0.5;
    transform-origin: 50% 50%;
    &:nth-of-type(2) {
      transform: rotate(120deg);
    }
    &:nth-of-type(3) {
      transform: rotate(240deg);
    }
    &::after {
      animation: move $duration * 0.5 ease-in-out infinite alternate;
      background: $dotColor;
      border-radius: 50%;
      content: "";
      display: inline-block;
      height: 100%;
      width: 100%;
      transform-origin: 50% 50%;
    }
  }
}
@keyframes rotate {
  from {
    transform: rotate(0);
  }
  to {
    transform: rotate(360deg);
  }
}
@keyframes move {
  0%,
  15% {
    transform: translateY(0);
  }
  100% {
    transform: translateY(-150%);
  }
}
</style>
uni_modules/zero-loading/components/zero-loading/static/loading-sword.vue
New file
@@ -0,0 +1,81 @@
<template>
  <view class="animations">
    <view class="box" :style="{ '--color': color }">
      <view class="sword"></view>
      <view class="sword"></view>
      <view class="sword"></view>
    </view>
  </view>
</template>
<script>
export default {
  name: "loading-sword",
  props: {
    color: {
      type: String,
      default: "#ED213A",
    },
  },
  data() {
    return {};
  },
};
</script>
<style lang="scss" scoped>
.box {
  position: relative;
  width: 120rpx;
  height: 120rpx;
}
.sword {
  position: absolute;
  width: 100%;
  height: 100%;
  border-radius: 50%;
}
.sword:nth-of-type(1) {
  left: 0%;
  top: 0%;
  border-bottom: 8rpx solid var(--color);
  animation: sword1 0.8s linear infinite;
}
.sword:nth-of-type(2) {
  right: 0%;
  top: 0%;
  border-right: 8rpx solid var(--color);
  animation: sword2 0.8s linear infinite;
}
.sword:nth-of-type(3) {
  right: 0%;
  bottom: 0%;
  border-top: 8rpx solid var(--color);
  animation: sword3 0.8s linear infinite;
}
@keyframes sword1 {
  0% {
    transform: rotateX(35deg) rotateY(-45deg) rotateZ(0deg);
  }
  100% {
    transform: rotateX(35deg) rotateY(-45deg) rotateZ(360deg);
  }
}
@keyframes sword2 {
  0% {
    transform: rotateX(50deg) rotateY(10deg) rotateZ(0deg);
  }
  100% {
    transform: rotateX(50deg) rotateY(10deg) rotateZ(360deg);
  }
}
@keyframes sword3 {
  0% {
    transform: rotateX(35deg) rotateY(55deg) rotateZ(0deg);
  }
  100% {
    transform: rotateX(35deg) rotateY(55deg) rotateZ(360deg);
  }
}
</style>
uni_modules/zero-loading/components/zero-loading/static/loading-wobble.vue
New file
@@ -0,0 +1,127 @@
<template>
  <view class="animations">
    <view class="three-body" :style="{ '--color': color }">
      <view class="three-body__dot"></view>
      <view class="three-body__dot"></view>
      <view class="three-body__dot"></view>
    </view>
  </view>
</template>
<script>
export default {
  name: "loading-wobble",
  props: {
    color: {
      type: String,
      default: "#0396FF",
    },
  },
  data() {
    return {};
  },
};
</script>
<style lang="scss" scoped>
$size: 100rpx;
$speed: 1s;
.three-body {
  position: relative;
  display: inline-block;
  height: $size;
  width: $size;
  animation: spin78236 calc($speed * 2.5) infinite linear;
}
.three-body__dot {
  position: absolute;
  height: 100%;
  width: 27%;
}
.three-body__dot:after {
  content: "";
  position: absolute;
  height: 0%;
  width: 100%;
  padding-bottom: 100%;
  background-color: var(--color);
  border-radius: 50%;
}
.three-body__dot:nth-child(1) {
  bottom: 5%;
  left: 0;
  transform: rotate(60deg);
  transform-origin: 50% 85%;
}
.three-body__dot:nth-child(1)::after {
  bottom: 0;
  left: 0;
  animation: wobble1 $speed infinite ease-in-out;
  animation-delay: calc($speed * -0.3);
}
.three-body__dot:nth-child(2) {
  bottom: 5%;
  right: 0;
  transform: rotate(-60deg);
  transform-origin: 50% 85%;
}
.three-body__dot:nth-child(2)::after {
  bottom: 0;
  left: 0;
  animation: wobble1 $speed infinite calc($speed * -0.15) ease-in-out;
}
.three-body__dot:nth-child(3) {
  bottom: -5%;
  left: 0;
  transform: translateX(116.666%);
}
.three-body__dot:nth-child(3)::after {
  top: 0;
  left: 0;
  animation: wobble2 $speed infinite ease-in-out;
}
@keyframes spin78236 {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}
@keyframes wobble1 {
  0%,
  100% {
    transform: translateY(0%) scale(1);
    opacity: 1;
  }
  50% {
    transform: translateY(-66%) scale(0.65);
    opacity: 0.8;
  }
}
@keyframes wobble2 {
  0%,
  100% {
    transform: translateY(0%) scale(1);
    opacity: 1;
  }
  50% {
    transform: translateY(66%) scale(0.65);
    opacity: 0.8;
  }
}
</style>
uni_modules/zero-loading/components/zero-loading/zero-loading.vue
New file
@@ -0,0 +1,186 @@
<template>
  <!--  -->
  <view
    :style="{ position: position, 'z-index': zIndex, '--opacity': maskOpacity }"
    class="container"
    :class="[
      mask ? 'mask' : '',
      maskMini ? 'mask-mini' : '',
      (mask || maskMini) && maskDark ? 'mask-dark' : '',
    ]"
    @click.prevent="handleClick"
  >
    <view>
      <view class="main">
        <loading0 v-if="type == 'circle'" :color="color"></loading0>
        <loading1 v-if="type == 'pulse'" :color="color"></loading1>
        <loading2 v-if="type == 'bounce'"></loading2>
        <loading3 v-if="type == 'eyes'"></loading3>
        <loading4 v-if="type == 'surround'"></loading4>
        <loading5 v-if="type == 'sun'"></loading5>
        <loading6 v-if="type == 'love'"></loading6>
        <loading7 v-if="type == 'sword'" :color="color"></loading7>
        <loading8 v-if="type == 'atom'" :color="color"></loading8>
        <loading9 v-if="type == 'gear'"></loading9>
        <loading10 v-if="type == 'radar'"></loading10>
        <loading11 v-if="type == 'annulus'" :color="color"></loading11>
        <loading12 v-if="type == 'wobble'" :color="color"></loading12>
        <loading13 v-if="type == 'equal'" :color="color"></loading13>
        <loading14 v-if="type == 'photo'" :color="color"></loading14>
        <loading15 v-if="type == 'locating'" :color="color"></loading15>
      </view>
      <view
        class="tips"
        v-if="showText"
        :style="{ color: textColor, fontSize: textSize, marginTop: textGap }"
        >{{ text }}</view
      >
    </view>
  </view>
</template>
<script>
import loading0 from "./static/loading-circle.vue";
import loading1 from "./static/loading-pulse.vue";
import loading2 from "./static/loading-bounce.vue";
import loading3 from "./static/loading-eyes.vue";
import loading4 from "./static/loading-surround.vue";
import loading5 from "./static/loading-sun.vue";
import loading6 from "./static/loading-love.vue";
import loading7 from "./static/loading-sword.vue";
import loading8 from "./static/loading-atom.vue";
import loading9 from "./static/loading-gear.vue";
import loading10 from "./static/loading-radar.vue";
import loading11 from "./static/loading-annulus.vue";
import loading12 from "./static/loading-wobble.vue";
import loading13 from "./static/loading-equal.vue";
import loading14 from "./static/loading-photo.vue";
import loading15 from "./static/loading-locating.vue";
export default {
  name: "zero-loading",
  components: {
    loading0,
    loading1,
    loading2,
    loading3,
    loading4,
    loading5,
    loading6,
    loading7,
    loading8,
    loading9,
    loading10,
    loading11,
    loading12,
    loading13,
    loading14,
    loading15,
  },
  props: {
    type: {
      type: String,
      default: "atom",
    },
    position: {
      type: String,
      default: "fixed",
    },
    zIndex: {
      type: Number,
      default: 9,
    },
    mask: {
      type: Boolean,
      default: false,
    },
    maskOpacity: {
      type: Number,
      default: 0.1,
    },
    maskMini: {
      type: Boolean,
      default: false,
    },
    maskDark: {
      type: Boolean,
      default: true,
    },
    color: {
      type: String,
      default: "#0396FF",
    },
    showText: {
      type: Boolean,
      default: false,
    },
    text: {
      type: String,
      default: "加载中...",
    },
    textSize: {
      type: String,
      default: "28rpx",
    },
    textColor: {
      type: String,
      default: "#333333",
    },
    textGap: {
      type: String,
      default: "40rpx",
    },
  },
  data() {
    return {};
  },
  methods: {
    handleClick() {
      this.$emit("click");
    },
  },
};
</script>
<style lang="scss" scoped>
.container {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  align-items: center;
  justify-content: center;
}
.tips {
  //   margin-top: 40rpx;
  text-align: center;
}
.mask {
  z-index: 999 !important;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  height: 100vh;
  width: 100vw;
  background: rgba(255, 255, 255, var(--opacity));
  transform: translate(0, 0);
}
.mask-mini {
  height: 300rpx;
  width: 300rpx;
  border-radius: 20rpx;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
.mask-dark {
  background: rgba(7, 17, 27, var(--opacity));
}
</style>
uni_modules/zero-loading/package.json
New file
@@ -0,0 +1,83 @@
{
  "id": "zero-loading",
  "displayName": "zero-loading(加载动画)",
  "version": "1.4.2",
  "description": "纯css加载动画, 一个标签元素即可实现炫酷的全屏loading效果,支持vue2,vue3",
  "keywords": [
    "loading",
    "加载动画",
    "css动画",
    "加载"
],
  "repository": "",
  "engines": {
    "HBuilderX": "^3.1.0"
  },
"dcloudext": {
    "sale": {
      "regular": {
        "price": "0.00"
      },
      "sourcecode": {
        "price": "0.00"
      }
    },
    "contact": {
      "qq": ""
    },
    "declaration": {
      "ads": "无",
      "data": "插件不采集任何数据",
      "permissions": "无"
    },
    "npmurl": "",
    "type": "component-vue"
  },
  "uni_modules": {
    "dependencies": [],
    "encrypt": [],
    "platforms": {
      "cloud": {
        "tcb": "y",
        "aliyun": "y",
        "alipay": "n"
      },
      "client": {
        "Vue": {
          "vue2": "y",
          "vue3": "y"
        },
        "App": {
            "app-vue": "u",
            "app-nvue": "u",
            "app-harmony": "u",
            "app-uvue": "u"
        },
        "H5-mobile": {
          "Safari": "y",
          "Android Browser": "y",
          "微信浏览器(Android)": "y",
          "QQ浏览器(Android)": "y"
        },
        "H5-pc": {
          "Chrome": "y",
          "IE": "u",
          "Edge": "y",
          "Firefox": "y",
          "Safari": "y"
        },
        "小程序": {
          "微信": "y",
          "阿里": "u",
          "百度": "u",
          "字节跳动": "u",
          "QQ": "u"
        },
        "快应用": {
          "华为": "u",
          "联盟": "u"
        }
      }
    }
  }
}
uni_modules/zero-loading/readme.md
New file
@@ -0,0 +1,69 @@
# zero-loading
> 仅测试于 vue2, vue3, 微信小程序. 其他平台自行测试
## 使用方法
导入 `uni_modules` 后直接使用即可
提供多种加载动画类型,传入 type 改变 loading 样式,不传默认 circle
### 全屏使用
```html
<zero-loading v-if="loading"></zero-loading>
```
### 局部使用
**父元素的 `position` 记得改为 `relative` 不然可能影响效果**
```html
<zero-loading type="pulse" position="absolute"></zero-loading>
```
## 参数说明
| 参数        | 类型     | 默认值    | 描述                                           |
| ----------- | -------- | --------- | ---------------------------------------------- |
| type        | String   | atom      | 样式                                           |
| position    | String   | fixed     | 定位方式                                       |
| zIndex      | Number   | 9         |                                                |
| mask        | Boolean  | false     | 是否需要遮罩 (默认为全屏遮罩,背景色默认为黑色) |
| maskOpacity | Number   | 0.1       | 遮罩透明度                                     |
| maskMini    | Boolean  | false     | 传入 true 时,使用小遮罩                        |
| maskDark    | Boolean  | true      | 传入 false 时,遮罩背景色为白色                 |
| color       | String   | #0396FF   | 自定义颜色,仅部分支持                          |
| showText    | showText | false     | 是否显示文字                                   |
| text        | String   | 加载中... | 文本内容                                       |
| textSize    | String   | 28rpx     | 文字大小                                       |
| textColor   | String   | #333333   | 文字颜色                                       |
| textGap     | String   | 40rpx     | 文字与 loading 动画的间距                      |
### type 可选值:
| type 值  | 描述              |
| -------- | ----------------- |
| locating | 定位 (自定义颜色) |
| photo    | 照片 (自定义颜色) |
| equal    | 等边 (自定义颜色) |
| wobble   | 摇摆 (自定义颜色) |
| annulus  | 圆环 (自定义颜色) |
| sword    | 剑气 (自定义颜色) |
| atom     | 原子 (自定义颜色) |
| pulse    | 脉冲 (自定义颜色) |
| circle   | 圆圈 (自定义颜色) |
| eyes     | 眼睛              |
| surround | 环绕              |
| bounce   | 弹跳              |
| radar    | 雷达              |
| gear     | 齿轮              |
| love     | 爱心              |
| sun      | 太阳              |
插件预览:
![code](https://cdn.zerojs.cn/image/common/code-z_1722414660881_1.jpg?imageMogr2/thumbnail/200x)
> 小程序搜索: 零技术
> 预览的小程序不一定能及时更新当前插件