uniapp实现一个倒计时大圆圈

兼容H5、APP、微信小程序

效果图

1.封装成组件 /components/CountdownTip/CountdownTip.vue

<template>
	<view class="wrapper">
		<!-- 右半边圆 -->
		<view class="container container1">
			<!-- 里面的半圆 -->
			<view class="halfCir"
				:style="[{ background: pathColor , 'animation-play-state':(timer===null)? 'paused':'running','animation-duration':`${animationDuration}s`,}]">
			</view>
		</view>
		<!-- 左半边圆 -->
		<view class="container container2">
			<!-- 里面的半圆 -->
			<view class="halfCir"
				:style="[{ background: pathColor ,'animation-play-state':(timer===null)? 'paused':'running','animation-duration':`${animationDuration}s`,}]">
			</view>
		</view>
		<!-- 这两个是控制圆角的小圆 -->
		<view class="cir cir1" :style="{ background: pathColor }"></view>
		<view class="cir cir2"
			:style="[{ background: pathColor,'animation-play-state':(timer===null)? 'paused':'running','animation-duration':`${animationDuration}s`,}]">
		</view>
		<!-- 当前秒数 -->
		<view class="label" :style="{ color: pathColor }">
			<text class="num" :style="{fontSize:`${numSize}px`}">{{time}}</text><text class="unit"
				:style="{fontSize:`${unitSize}px`}">s</text>
		</view>
	</view>
</template>

<script>
	const FULL_DASH_ARRAY = 283;
	const DEFAULT_COLOR = '#2e9a3c';
	export default {
		name: 'CountdownTip',
		props: {
			endTime: {
				type: Number,
				default: 0
			}, // 结束时间,单位s
			startTime: Number, // 开始时间,单位s
			step: {
				type: Number,
				default: -1
			}, // 间隔时间,单位s,倒计时为负数,正计时为正数
			onFinished: Function, // 计时结束事件
			thresholds: Array, // 阶段阈值,以及颜色变化。
			wh: {
				type: Number,
				default: 220
			}, // 圆圈宽度高度。
			numSize: {
				type: Number,
				default: 80
			}, // 数子字体大小
			unitSize: {
				type: Number,
				default: 60
			}, // 单位字体大小
			isPausedTimer: Boolean,
		},

		data() {
			return {
				time: this.startTime,
				animationDuration: this.startTime,
				timer: null,
				timeLimit: Math.abs(this.startTime - this.endTime),
			}
		},

		computed: {
			// 路径颜色
			pathColor() {
				let _this = this
				let result = DEFAULT_COLOR
				if (Array.isArray(this.thresholds)) {
					_this.thresholds
						.sort((a, b) => a.threshold - b.threshold)
						.some(item => {
							if (this.time <= this.timeLimit * item.threshold) {
								// 根据当前时间获取距离最近的阈值的颜色
								result = item.color;
								return true;
							}
							return false;
						});
				}
				return result
			},

			// stroke虚线数组
			strokeDashArray() {
				// 圆滑过渡
				const rawTimeFraction = this.time / this.timeLimit;
				const timeFraction = rawTimeFraction - (1 / this.timeLimit) * (1 - rawTimeFraction);
				return `${(timeFraction * FULL_DASH_ARRAY).toFixed(
	        0,
	      )} ${FULL_DASH_ARRAY}`
			}
		},


		mounted() {
			this.timer = setInterval(() => {
				// step>0正计时 和 step<0倒计时
				if (
					(this.step < 0 && this.time <= this.endTime) ||
					(this.step > 0 && this.time >= this.endTime)
				) {
					this.onFinished?.();
					clearInterval(this.timer);
				} else {
					const cur = this.time + this.step;
					this.time = cur <= 0 ? 0 : cur;
				}
			}, Math.abs(this.step) * 1000);
		},

		beforeDestroye() {
			clearInterval(this.timer);
			this.timer = null
		},
		watch: {
			isPausedTimer: {
				handler(isTrue) {
					console.log("isTrue===",isTrue);
					if (isTrue) {
						clearInterval(this.timer);
						this.timer = null
					}
				},
				deep: true
			}
		}


	}
</script>

<style lang="scss" scoped>
	.wrapper {
		position: relative;
		width: 220px;
		height: 220px;
		margin: auto;
		/* 加上背景色和圆角 */
		border-radius: 50%;
		background: #f5f5f5;
	}

	.container {
		position: absolute;
		top: 0;
		bottom: 0;
		width: 110px;
		overflow: hidden;
	}

	.halfCir {
		width: 110px;
		height: 220px;
		background-color: #1A1A1A;
	}

	.container1 {
		left: 110px;
	}

	.container1 .halfCir {
		left: 0;
		border-radius: 0 220px 220px 0;
		transform-origin: 0 50%;
		animation: halfCir1 60s 1 linear;
	}

	.container2 {
		left: 0;
	}

	.container2 .halfCir {
		border-radius: 220px 0 0 220px;
		transform-origin: 110px 110px;
		animation: halfCir2 60s 1 linear;
	}

	@keyframes halfCir1 {

		50%,
		100% {
			transform: rotateZ(180deg);
		}
	}

	@keyframes halfCir2 {

		0%,
		50% {
			transform: rotateZ(0);
		}

		100% {
			transform: rotateZ(180deg);
		}
	}

	.wrapper::after {
		position: absolute;
		top: 50%;
		left: 50%;
		transform: translate(-50%, -50%);
		width: 180px;
		height: 180px;
		border-radius: 50%;
		content: "";
		background-color: #fff;
	}

	.cir {
		position: absolute;
		top: 0;
		right: 0;
		left: 0;
		width: 20px;
		height: 20px;
		margin: auto;
		background: #FFD600;
		border-radius: 50%;
	}

	.cir1 {
		// left: -10px;
		// border-radius: 0 20px 20px 0;
		// background: #f5f5f5;
		// z-index: -1;
	}

	.cir2 {
		transform-origin: 50% 110px;
		animation: cir2 60s 1 linear;
	}

	@keyframes cir2 {
		100% {
			transform: rotateZ(360deg);
		}
	}

	.label {
		position: absolute;
		top: 50%;
		left: 50%;
		z-index: 10;
		transform: translate(-50%, -50%);
		font-size: 36px;
		color: #FFD600;
	}
</style>

2.页面引入

<template>
	<view class="" style="margin-top: 200px;">
		<CountdownTip :start-time="timeout" :step="-1" :isPausedTimer="isPausedTimer" :thresholds="thresholds">
		</CountdownTip>
	</view>
</template>

<script>
	import CountdownTip from '@/components/CountdownTip/CountdownTip.vue'
	export default {
		components: {
			CountdownTip
		},
		data() {
			return {
				timeout: 60,
				isPausedTimer: false,
				thresholds: [{
						color: '#fed45f',
						threshold: 0.5
					},
					{
						color: '#FF1100',
						threshold: 0.25
					},
					{
						color: '#f5f5f5',
						threshold: 0
					}
				]
			}
		},
		onLoad() {},
		mounted() {},
		beforeDestroy() {
			this.pausedCountdown()
		},
		// 使用hash路由刷新页面不会触发销毁函数 在该方法执行你的逻辑即可
		beforeRouteLeave(to, from, next) {
			this.pausedCountdown()
			next()
		},
		methods: {
			pausedCountdown() {
				this.isPausedTimer = true
			},
		},
	}
</script>

<style lang="scss" scoped>

</style>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值