| | |
| | | <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 |
| | |
| | | <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)" |
| | |
| | | @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 }"> |
| | |
| | | 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, |
| | |
| | | } |
| | | }) |
| | | }, |
| | | // 初始化视频上下文 |
| | | 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() { |
| | |
| | | ), |
| | | ]; |
| | | } |
| | | this.$nextTick(() => { |
| | | this.initVideoContexts(); |
| | | }); |
| | | this.loading = false; |
| | | if(res.data.data.length < this.videoQuery.pageSize) { |
| | | this.videoNoMore = true; |
| | |
| | | |
| | | // 滑动切换视频 |
| | | onSwiperChange(e) { |
| | | this.videoLoading = false |
| | | // 如果视频处于暂停状态往下刷视频,那么需要再计算一次暂停时间 |
| | | if(!this.currentVideoIsPlaying) { |
| | | if(this.startPauseTime !== 0) { |
| | |
| | | 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) { |
| | |
| | | }, |
| | | // 触摸中 |
| | | 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 |
| | | |
| | |
| | | }, |
| | | // 单击屏幕:暂停或继续播放 |
| | | 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) { |
| | |
| | | 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) { |
| | |
| | | const duration = Date.now() - this.startPauseTime |
| | | this.totalPauseTime += duration |
| | | } |
| | | |
| | | this.videoLoading = false |
| | | }, |
| | | |
| | | // 视频暂停事件 |
| | |
| | | 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) { |
| | |
| | | |
| | | // 记录播放时长 |
| | | onTimeUpdate(e) { |
| | | this.videoLoading = false |
| | | this.playRecord.playAt = e.detail.currentTime; |
| | | |
| | | this.currentTime = e.detail.currentTime; |
| | |
| | | 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); |
| | | }, |
| | | |
| | |
| | | handleTouchMove(e) { |
| | | if (!this.isDragging || !this.barWidth) return; |
| | | clearTimeout(this.processHidenTimer) |
| | | this.videoContexts[this.currentIndex].pause() |
| | | this.updateProgress(e); |
| | | }, |
| | | |
| | |
| | | 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); |
| | |
| | | |
| | | // 将像素距离转换为进度增量 |
| | | const deltaProgress = (deltaX / this.barWidth) * 100; |
| | | console.log("进度增量", deltaProgress); |
| | | // 计算新进度 = 开始时的进度 + 滑动增量 |
| | | let newProgress = this.startProgress + deltaProgress; |
| | | |
| | |
| | | newProgress = Math.max(0, Math.min(100, newProgress)); |
| | | |
| | | this.progress = newProgress; |
| | | }, |
| | | // 视频缓冲 |
| | | videoWaiting(index) { |
| | | if (index === this.currentIndex) { |
| | | console.log("视频缓冲中。。。"); |
| | | this.videoLoading = true; |
| | | } |
| | | }, |
| | | // 获取视频总时长 |
| | | onLoadedMetadata(e) { |
| | |
| | | .video-item { |
| | | width: 100%; |
| | | height: 100%; |
| | | object-fit: cover; |
| | | /* object-fit: cover; */ |
| | | } |
| | | .play-icon { |
| | | position: absolute; |
| | |
| | | <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> |
| | |
| | | <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)" |
| | | > |
| | |
| | | </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> --> |
| | |
| | | <!-- <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" |
| | |
| | | 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({ |
| | |
| | | height: 70rpx; |
| | | line-height: 70rpx; |
| | | padding: 0 40rpx; |
| | | |
| | | |
| | | &::after { |
| | | border: none; |
| | | } |
| | | |
| | | |
| | | &.followed { |
| | | background-color: #f5f5f5; |
| | | color: #666; |
| | | } |
| | | } |
| | | |
| | | </style> |
| | | </style> |
| | |
| | | <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)" |
| | |
| | | @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 }"> |
| | |
| | | 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 |
| | |
| | | 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(); |
| | |
| | | } |
| | | }) |
| | | }, |
| | | // 初始化视频上下文 |
| | | initVideoContexts() { |
| | | this.videoContexts = this.videoList.map((_, index) => { |
| | | let videoContent = uni.createVideoContext(`video${index}`, this); |
| | | return videoContent; |
| | | }); |
| | | }, |
| | | |
| | | // 加载视频数据 |
| | | async loadVideos() { |
| | |
| | | ), |
| | | ]; |
| | | } |
| | | this.$nextTick(() => { |
| | | this.initVideoContexts(); |
| | | }); |
| | | this.loading = false; |
| | | if(res.data.data.length < this.videoQuery.pageSize) { |
| | | this.videoNoMore = true; |
| | |
| | | |
| | | // 滑动切换视频 |
| | | 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() { |
| | |
| | | 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); |
| | | }, |
| | | |
| | |
| | | handleTouchMove(e) { |
| | | if (!this.isDragging || !this.barWidth) return; |
| | | clearTimeout(this.processHidenTimer) |
| | | this.videoContexts[this.currentIndex].pause() |
| | | this.updateProgress(e); |
| | | }, |
| | | |
| | |
| | | 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); |
| | | }, |
| | | |
| | | // 更新进度 |
| | |
| | | |
| | | // 将像素距离转换为进度增量 |
| | | const deltaProgress = (deltaX / this.barWidth) * 100; |
| | | console.log("进度增量", deltaProgress); |
| | | // 计算新进度 = 开始时的进度 + 滑动增量 |
| | | let newProgress = this.startProgress + deltaProgress; |
| | | |
| | |
| | | }, |
| | | // 单击屏幕:暂停或继续播放 |
| | | 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) { |
| | |
| | | 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) { |
| | |
| | | const duration = Date.now() - this.startPauseTime |
| | | this.totalPauseTime += duration |
| | | } |
| | | this.videoLoading = false |
| | | }, |
| | | |
| | | // 视频暂停事件 |
| | |
| | | console.log(index, "触发暂停"); |
| | | if(index === this.currentIndex) { |
| | | this.currentVideoIsPlaying = false; |
| | | } else { |
| | | this.currentVideoIsPlaying = true; |
| | | return |
| | | this.startPauseTime = Date.now() |
| | | } |
| | | this.startPauseTime = Date.now() |
| | | }, |
| | | |
| | | // 视频结束事件 |
| | |
| | | |
| | | // 记录播放时长 |
| | | 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) { |
| | |
| | | .video-item { |
| | | width: 100%; |
| | | height: 100%; |
| | | object-fit: cover; |
| | | /* object-fit: cover; */ |
| | | } |
| | | .play-icon { |
| | | position: absolute; |
| New file |
| | |
| | | ## 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) |
| | | |
| | | 首次发布 |
| New file |
| | |
| | | <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> |
| New file |
| | |
| | | <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> |
| New file |
| | |
| | | <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> |
| New file |
| | |
| | | <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> |
| New file |
| | |
| | | <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> |
| New file |
| | |
| | | <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> |
| New file |
| | |
| | | <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> |
| New file |
| | |
| | | <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> |
| New file |
| | |
| | | <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> |
| New file |
| | |
| | | <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> |
| New file |
| | |
| | | <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> |
| New file |
| | |
| | | <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> |
| New file |
| | |
| | | <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> |
| New file |
| | |
| | | <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> |
| New file |
| | |
| | | <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> |
| New file |
| | |
| | | <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> |
| New file |
| | |
| | | <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> |
| New file |
| | |
| | | { |
| | | "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" |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | # 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 | 太阳 | |
| | | |
| | | 插件预览: |
| | |  |
| | | |
| | | > 小程序搜索: 零技术 |
| | | |
| | | > 预览的小程序不一定能及时更新当前插件 |