用uni-app写一个圆形倒计时组件(包括vue和nvue)

2023-11-06 02:50

本文主要是介绍用uni-app写一个圆形倒计时组件(包括vue和nvue),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

今天来分享一个我自己写的圆形倒计时组件。我在用uni-app做一个APP,里面新增了一个要做倒计时的需求,也就是旁边提示“看视频,得xx奖励”的那种。这个东西在多个页面上都要使用,所以我封装了一个组件,现在详细讲一下其中的过程,因为里面有一些我觉得值得思考的地方。测试环境是在安卓手机的APP,有vue的页面,也有nvue的页面。如果有差不多需要的同学们可以参考,也欢迎给我提出建议。(长文预警,所以这次我做了个目录给大家)

目录

一、成品效果

二、思路和写这个组件的大致过程

三、详细过程

第一步

第二步

第三步

第四步

第五步

总结


一、成品效果

OK,首先来看下效果,我从我自己手机上截了两张图出来,一个是vue的页面,一个是nvue的页面。这个组件我做了两个版本。(因为nvue的一些规定比较特别,我担心nvue这里无法正常使用vue的组件,所以我又做了个nvue的版本。)可以看出来,除了由于字体不同导致效果略有不同(图一是vue的页面,字体是黑体,图二是nvue的页面,字体是我自己在手机上设置的字体,vue的页面在打包前就是固定的黑体),效果是大体相同的,细节上也没有明显的硬伤。

有兴趣的朋友可以看看我写这个组件的过程,现在简单介绍一下我的思路和做组件的大致过程。

二、思路和写这个组件的大致过程

我的思路是这样的:①这个东西应该是一个负责展示的零件,展示的数据就是父组件要传递的prop,我认为要展示的东西是提示文字,如“看视频,得蜜獾”,以及左边的读秒;②另外,组件需要保持在左上角显示,所以根组件需要fixed固定定位,还有,由于有的页面在滚动后下面的其他东西(如菜单)也会置顶,如商城首页这里的“精选 特惠……”这一行,这样,这个组件的上偏移量top的值就不确定了,也应该作为一个输入,具体的值由父组件计算好后传入;③还有,组件在倒计时完毕后要隐藏掉,所以组件是否显示出来也应该由父组件控制;④圆形的进度应该不只是倒计时读秒,弧度应该由百分比决定,所以要知道总时长百分比才可以——也就是说,这个组件的props里面有5个属性:数字类型的读秒值time,字符串类型的提示文字tip,数字类型的top坐标top,布尔类型的是否显示isShow,数字类型的总时长total。写出来就像这样:

props: {// 显示的位置top: {type: Number,default: 0},// 提示文字tip: {type: String,default: '看视频 赚蜜獾'},// 是否显示,为true显示isShow: {type: Boolean,default: false},// 还剩多少时间time: {type: Number,default: 100},// 共多少时间total: {type: Number,default: 180}
},

大致的过程是这样的:1.最基本的事情,是要做出一个圆形进度条的CSS动画效果,要能正常实现走完一圈一圈的效果。2. 把效果改成设计稿要的样子,包括大小、进度条、轨道、中间圆的颜色。3.把这个效果搬到uni-app组件中,把动画效果改成读秒和总时长是由父组件传入的。 4.在vue的页面上进行测试。5.在nvue的页面上进行测试。

三、详细过程

现在来详细讲一下写这个的过程。

第一步

OK,第一步的代码并不是我亲手写的,但我之前有了解过圆形进度条的大致原理(左右放两个用来旋转的半圆,半圆下方有用来切掉超出半圆超出部分的矩形,底部放一个作为进度条颜色的大圆,半圆旋转后露出大圆的一部分,即扇形,最上层再使用小圆把扇形的中间挡住,露出环形的一部分,然后控制前半部分和后半部分哪个半圆旋转就行了),懒得自己凹细节,网上类似的代码很多,我挑了一个带圆角的案例,拷过来自己改了。我使用的是这篇博客里的代码,非常感谢原作者,不太明白的同学可以去看看——https://blog.csdn.net/qq_42565994/article/details/85228451

第二步

第二步是改细节,我拿过来以后,颜色和大小并不是像设计稿那样的,中间也没有数字,然后我进行了相应的修改。贴一下到这一步,我修改后的代码:


<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>环形进度条</title><style>.wrapper {position: absolute;top: 0;right: 0;bottom: 0;left: 0;width: 104px;height: 104px;margin: auto;/* 加上背景色和圆角 */border-radius: 50%;/* background-color: #1A1A1A; */background: #FFD600;}.container {position: absolute;top: 0;bottom: 0;width: 52px;overflow: hidden;}.halfCir {width: 52px;height: 104px;background-color: #1A1A1A;/* background: #FFD600; */}.container1 {left: 52px;}.container1 .halfCir {left: 0;border-radius: 0 104px 104px 0;transform-origin: 0 50%;animation: halfCir1 4s infinite linear;           }.container2 {left: 0;}.container2 .halfCir {border-radius: 104px 0 0 104px;transform-origin: 52px 52px;animation: halfCir2 4s infinite linear;}@keyframes halfCir1 {50%, 100% {transform: rotateZ(180deg);}}@keyframes halfCir2 {0%, 50% {transform: rotateZ(0);}100% {transform: rotateZ(180deg);}}.wrapper::after {position: absolute;top: 8px;left: 8px;width: 88px;height: 88px;border-radius: 50%;content: "";    background-color: #000;}.cir {position: absolute;top: 0;right: 0;left: 0;width: 8px;height: 8px;margin: auto;/* background: #1a1a1a; */background: #FFD600;border-radius: 50%;}.cir2 {transform-origin: 50% 52px;animation: cir2 4s infinite linear;}@keyframes cir2 {100% {transform: rotateZ(360deg);}}.number {position: absolute;top: 50%;left: 50%;z-index: 10;transform: translate(-50%, -50%);font-size: 36px;color: #FFD600;}</style>
</head>
<body><p>我做的改动如下:1.最外层加上圆角和背景色。2. 给最里面的小圆,也就是遮盖层,也就是外圆的after伪元素,加上背景色,这里的背景色是中间的颜色,即黑色。3. 往里面加上了数字的显示,定位到最中间,z-index最大。4. 设置里面的半圆的背景颜色,因为黄色要越来越大,所以黄色是最底层的颜色,灰黑是上层半圆的颜色色</p><div class="wrapper"> <div class="container container1"> <div class="halfCir"></div> </div> <div class="container container2"> <div class="halfCir"></div> </div><!-- 这两个是控制圆角的小圆 --><div class="cir cir1"></div> <div class="cir cir2"></div><span class="number">58</span></div>
</body>
</html>

效果如下:(我这边要求的旋转方向与那篇博客里写的不一样,是顺时针的。黄色是随着转动越来越大的颜色,所以转动的半圆应该是看起来是轨道色的灰黑色,最底层的半圆才是黄色。然后进度条的圆角效果是用两个小圆来做的,小圆的宽高等于黄色弧形的宽度,border-radius为50%,被固定定位到最上方水平居中的位置,其中一个跟着进行着360%的旋转,正好转到黄色的那一头。这里的哪个颜色在上层,哪个颜色在下层是困扰我的第一个难点,我曾一度为此苦恼,直到我突然发现,颜色互换一下就迎刃而解了)

第三步

第三步是把它拿到uni-app的项目中来用,改写成真正的组件的样子。这是非常重要的一步。我可以细分成几个小步骤

1.在项目的components文件夹新建countdown-tip.vue文件,然后把上面这个HTML文件的主要部分DOM结构和CSS样式搬到这个vue文件中来,记得把<div>改成<view>。这个vue文件的根组件要包含两个view,左边是这个倒计时圆形,右边是文字提示,右边的文字比较简单。因为右边文字的左边圆角并没有完全显示出来,所以父级并不是用flex来并排显示的,而是要让左边的圆使用绝对定位,盖住右边文字提示的左半部分。

2.既然是组件,那么它怎么转,就不能是CSS动画,而应该由父组件来决定它当前转到哪里,转得多快了。所以现在让我们把CSS里面的animation的代码全部删掉,然后把原本CSS要做的动画效果用style动态写到元素上。我假设这一刻右边的半圆转动了half1Turn度(deg),左边的半圆转动了half2Turn度,黄色的小圆(圆弧终点的圆角)转动了ratio个turn(一个trun是360度,0.5turn就是半圈的意思,这也是一个CSS转动角度的值)

(这里的效果是倒计时到0秒,黄色进度条才走满一圈的效果,并不是一秒走一圈的效果)

所以组件左半边的代码变成这样:(注意前半圈转动的是右边的半圆而不是左边的,所以右边的半圆写在前面)

<!-- 左边,倒计时的部分 -->
<view class="time-warpper" :style="{'transform': 'scale('+ rpxRatio +')'}"><!-- 右半边圆 --><view class="container container1"><!-- 里面的半圆 --><view class="half half-circle1" :style="{'transform': 'rotate('+ half1Turn +'deg)'}"></view></view><!-- 左半边圆 --><view class="container container2"><!-- 里面的半圆 --><view class="half half-circle2" :style="{'transform': 'rotate('+ half2Turn +'deg)'}"></view></view><!-- 这两个是控制圆角的小圆 --><view class="cir cir1"></view> <view class="cir cir2" :style="{'transform': 'rotate('+ ratio +'turn)'}"></view><!-- 当前秒数 --><text class="time-text">{{time}}</text>
</view>

这些值是怎么计算出来的呢?明明父组件只传了数字类型的读秒值time,字符串类型的提示文字tip,数字类型的top坐标top,布尔类型的是否显示isShow,数字类型的总时长total来啊。①这里可以使用计算属性,把读秒值time和总时长total两个数据拿来加工,读秒值意味着倒计时还剩下多少秒,总时长减去这个值就得到已经过了多少秒,再除以总时长就是已经过了这段时间的多少比例

前半段时间转动的是右边的半圆,后半段转动的是左边的半圆,所以判断现在的比例是否大于0.5,是就让右边的半圆固定转180度,减去0.5的部分除以0.5,再乘以180,看看左边的半圆需要转动多少度,否就让右边的半圆转动相应的度数,左边的半圆不动,保持在0即可。

③小圆转动的角度就直接是ratio * 1turn,因为这个小圆在一整圈都在转。

computed: {// 计算这段时间已经过去了百分之多少ratio() {return (this.total - this.time) / this.total;},// 右边的半圆转的度数half1Turn() {// 大于50%则不转,停留在180deg这里if(this.ratio > 0.5) {return 180;} else {// 小于50%则判断现在占50%的多少,根据这个比例乘180return (this.ratio / 0.5) * 180;}},// 左边边的半圆转的度数half2Turn() {// 小于50%则不转,停留在0这里if(this.ratio < 0.5) {return 0;} else {// 大于50%则判断大于50%的部分占50%的多少,根据这个比例乘180return ((this.ratio - 0.5) / 0.5) * 180;}}
}

3.那么父组件,也就是vue页面要怎么使用这个组件呢——首先当然是导入组件,指定为自己的子组件,使用对应的标签,并为子组件准备并传递数据。这些都是基本的操作,就不细谈了。要制造读秒的动态效果,那就还需要在onLoad生命周期里启动定时器:

// 让倒计时组件动起来的测试代码
this.t = setInterval(() => {if(this.currentTime > 0) {this.currentTime--}
}, 1000)

currentTime就是当前传给子组件time属性的数据,这里让它每一秒都自减,这样就会让子组件里的time越来越小,于是子组件的数字改变,进度条也动起来了。(小提示:如果为了测试,可以把这里的1000调小一点,这样变化快,测试起来也方便一些,我测试的时候用的是200)这里使用t来存储计时器的id也是为了在组件卸载的时候可以通过clearInterval(this.t)来清理掉这个定时器,防止内存泄漏。

第四步

第四步,在真机上调试vue组件。到第三部结束后,前面的理论整理完毕,那么来看看在vue页面真机上的效果吧——我们都知道uni-app让页面的宽度都为750rpx来进行适配的,那么我这里的样式的单位改成rpx肯定能正常适配各个屏幕吧,而且改起来很方便。诶,实际效果怎么翻车了?这里是困扰我的第二个难点。

我来详细说一下当时的情况吧。UI给的图是一倍图,圆的宽度是52px,根据我的经验,拿到uni-app上来,肯定是数字乘以2后改成rpx单位啊,所以我把52px改成了104rpx。结果放到手机上的效果一言难尽,可以说是大致效果都有,就是细节上差强人意——如整体看起来不够圆;右边的半圆和左边的半圆之间有一条黑色的缝,并没有完全合在一起;黄色的圆环在边缘不够圆润,粗细不均匀;最明显的是,黄色的小圆位置偏离了圆环,好像从圆环这里脱落了一样。在细节上多多少少有偏差,难道是我计算的不对?我仔细排查了多遍都没有用,最后才明白,解铃还须系铃人,问题就出在rpx这个单位上——不同的设备,屏幕的逻辑像素宽度五花八门,却始终以750rpx为100%的宽度,可想而知rpx计算出来有多少位小数,而我之前测试的圆润效果,是在HTML文件里用px单位做的,所以这里也必须用px来做,否则用非整数像素的rpx没办法做的那么精确

那么问题又来了——我们这里用的px,是屏幕的逻辑像素,也就是300多到400多点,104px的圆环显得太大了,而且在不同屏幕上效果差别很大,如何让它大小正常呢?我们需要计算px与rpx大小之间的比例了,屏幕宽度可以用uni.getSystemInfo来获取。假设屏幕宽度为a px, 正好等于750 rpx, 所以1 rpx = ( a / 750 )px = 1 px * ( a / 750 )。我们在组件被挂载后马上开始计算它。

mounted() {uni.getSystemInfo({success: (res) => {console.log(res.windowWidth)this.rpxRatio = (res.windowWidth / 750)}})
},

我准备了一个数据较rpxRatio,用于存储rpx与px的比例,然后用来对左边的圆进行整体缩放:<view class="time-warpper" :style="{'transform': 'scale('+ rpxRatio +')'}">,这样就把里面使用到的1px整体转成1rpx了,由于是整体缩小,所以缩小的比例一致,并没有乱掉。transform的缩放是看起来更小,并不影响实际占位空间的,但是因为左边的圆绝对定位了,所以不会影响到右边的文字部分,记得设置左边的圆的transform-origin为left top,让它贴着自己的左上角缩小,否则看起来位置会怪怪的,因为中间的圆心还在原地。至此,在vue页面上显示效果大功告成。

第五步

第五步,在nvue页面上调试。这个项目有需要播放视频的页面,这个用vue页面做不了,只能用nvue的页面,所以也需要做nvue版本的组件。因为vue相对于nvue上写起来更简单更可控,所以我会先做vue页面的版本,然后再把它复制一份,改成nvue的后缀。我们之前的考虑那么周全,现在应该也没什么大问题吧?结果效果让我很不满意,对nvue页面的适配是第三个难点

大体上在功能和计算方便并没有区别,区别主要在样式上,我可以告诉大家有哪些是需要注意的——

1. 在nvue中,写给<view>的border-radius不可以合在一起写,否则看起来没有圆角。这里的半圆要是没有圆角,那简直转了个寂寞。记得border-radius写多个值的顺序是上左,上右,下右,下左,拆开来写的顺序最好跟这个一致,否则容易写错,导致悲剧。

2.nvue中不支持display属性,组件如何显示隐藏?我最常用的显示隐藏方法就是修改display,但是nvue的display是flex,并且不接受修改,怎么办?我之前的一篇博客说过,可以把宽度或高度改成0或者正常大小,这个组件的高度是确定的,所以可以对高度下手——isShow为false的时候高度为0,isShow为true时,高度为104rpx。

3.nvue中不支持z-index。这个问题只能用“先来后到”的办法解决,也就是谁写在后面,谁就盖住别人。这里的圆环要盖住右边文字提示的左半部分,可是我们习惯把右边的东西写在左边后面,所以加了定位没有用,左边的圆环被右边的提示盖住了。那就只能把右边的提示写到左边的圆环前面。

4.nvue中不支持turn这个单位。我测试的时候,看到圆环边缘处不够圆润,也就是黄色的小圆没有随着圆环转动,联想到小圆转动的单位跟半圆的单位不同,我把1 turn当成 360 deg进行换算后,小圆也可以正常转动了

另外还有之前的博客就提到过的一些要点——文字不能写在<view>里,要单独写到<text>里; CSS样式写的时候只能用单类名选择器,其他选择器和复杂的选择器都不能生效,这些是我首先会修改的地方,我是改完了这些再来改以上四个问题的 

解决了以上四个问题,终于,nvue版本的组件在页面上效果也正常了。以上这五步,我折腾了大半天的时间才搞定的。

总结

这是一个比较简单,看起来也比较有趣的倒计时组件。由于文字提示和组件位置可以传入,所以也容易复用。

要做好这个组件,首先要有能正常转动的圆形进度条动画,然后要弄清楚的是有哪些数据应该由父组件传入,还有哪些数据可以从父组件传入的数据中计算出来,要靠父组件传值来实现动画,再就是要注意样式适配的细节,注意不要用rpx单位来做过于精细的效果展示,否则细节上表现不佳,在nvue页面上,注意样式要写得保守一些,最好查阅weex文档再决定什么样式可以用,遇到不起作用的样式不要慌,多想想曲线救国的备用方案。

希望能帮助到有类似需求的大家。如果你有好的意见和建议,也希望告诉我呀。

这篇关于用uni-app写一个圆形倒计时组件(包括vue和nvue)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/354049

相关文章

前端如何通过nginx访问本地端口

《前端如何通过nginx访问本地端口》:本文主要介绍前端如何通过nginx访问本地端口的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、nginx安装1、下载(1)下载地址(2)系统选择(3)版本选择2、安装部署(1)解压(2)配置文件修改(3)启动(4)

HTML中meta标签的常见使用案例(示例详解)

《HTML中meta标签的常见使用案例(示例详解)》HTMLmeta标签用于提供文档元数据,涵盖字符编码、SEO优化、社交媒体集成、移动设备适配、浏览器控制及安全隐私设置,优化页面显示与搜索引擎索引... 目录html中meta标签的常见使用案例一、基础功能二、搜索引擎优化(seo)三、社交媒体集成四、移动

HTML input 标签示例详解

《HTMLinput标签示例详解》input标签主要用于接收用户的输入,随type属性值的不同,变换其具体功能,本文通过实例图文并茂的形式给大家介绍HTMLinput标签,感兴趣的朋友一... 目录通用属性输入框单行文本输入框 text密码输入框 password数字输入框 number电子邮件输入编程框

HTML img标签和超链接标签详细介绍

《HTMLimg标签和超链接标签详细介绍》:本文主要介绍了HTML中img标签的使用,包括src属性(指定图片路径)、相对/绝对路径区别、alt替代文本、title提示、宽高控制及边框设置等,详细内容请阅读本文,希望能对你有所帮助... 目录img 标签src 属性alt 属性title 属性width/h

CSS3打造的现代交互式登录界面详细实现过程

《CSS3打造的现代交互式登录界面详细实现过程》本文介绍CSS3和jQuery在登录界面设计中的应用,涵盖动画、选择器、自定义字体及盒模型技术,提升界面美观与交互性,同时优化性能和可访问性,感兴趣的朋... 目录1. css3用户登录界面设计概述1.1 用户界面设计的重要性1.2 CSS3的新特性与优势1.

HTML5 中的<button>标签用法和特征

《HTML5中的<button>标签用法和特征》在HTML5中,button标签用于定义一个可点击的按钮,它是创建交互式网页的重要元素之一,本文将深入解析HTML5中的button标签,详细介绍其属... 目录引言<button> 标签的基本用法<button> 标签的属性typevaluedisabled

HTML5实现的移动端购物车自动结算功能示例代码

《HTML5实现的移动端购物车自动结算功能示例代码》本文介绍HTML5实现移动端购物车自动结算,通过WebStorage、事件监听、DOM操作等技术,确保实时更新与数据同步,优化性能及无障碍性,提升用... 目录1. 移动端购物车自动结算概述2. 数据存储与状态保存机制2.1 浏览器端的数据存储方式2.1.

基于 HTML5 Canvas 实现图片旋转与下载功能(完整代码展示)

《基于HTML5Canvas实现图片旋转与下载功能(完整代码展示)》本文将深入剖析一段基于HTML5Canvas的代码,该代码实现了图片的旋转(90度和180度)以及旋转后图片的下载... 目录一、引言二、html 结构分析三、css 样式分析四、JavaScript 功能实现一、引言在 Web 开发中,

CSS place-items: center解析与用法详解

《CSSplace-items:center解析与用法详解》place-items:center;是一个强大的CSS简写属性,用于同时控制网格(Grid)和弹性盒(Flexbox)... place-items: center; 是一个强大的 css 简写属性,用于同时控制 网格(Grid) 和 弹性盒(F

CSS实现元素撑满剩余空间的五种方法

《CSS实现元素撑满剩余空间的五种方法》在日常开发中,我们经常需要让某个元素占据容器的剩余空间,本文将介绍5种不同的方法来实现这个需求,并分析各种方法的优缺点,感兴趣的朋友一起看看吧... css实现元素撑满剩余空间的5种方法 在日常开发中,我们经常需要让某个元素占据容器的剩余空间。这是一个常见的布局需求