在前端网络操作是异步的,一般都需要一个进度条,在很多应用中,我们经常可以看到环形进度条。
但是小程序原生的progress组件,是一个从左到右的方形进度条,那么我们可不可以自实现一个环形进度条呢?
一、原生实例代码
可自定义的属性有关于颜色值的,颜色的使用需要符合微信小程序设计规范
https://developers.weixin.qq.com/miniprogram/design/
index.wxml
<!-- 单击模拟网络异步 -->
<view class="gap" >代码示例,单击模拟网络异步</view>
<progress show-info bindtap="onTapProgressBar"
stroke-width="2" percent="{{percentValue}}"
backgroundColor="#f2f2f2" active-mode="forwards"
active bindactiveend="onProgressActiveEnd" />
index.js
Page({
data: {
percentValue: 0
},
onProgressActiveEnd(e){
console.log(e)
},
onTapProgressBar(e) {
console.log(e)
let progress = this.data.percentValue
if (progress < 100) {
progress += 5
this.setData({
percentValue: Math.min(100, progress)
})
}
}
})
二、相关问题
2.1 如何实现一个下载文件并显示动态进度条的功能?
文档中percent这个属性必须设置固定的值,例如80,但是进度是一直变化的,该如何实现动态进度条呢?
我们前面的示例代码基本就可以满足这个需求了。当启用active,并将active-mode设置为forwards后,动画就会随下载进度动起来。
通过文件下载的总大小和已完成大小,可以实时计算出percent数值,需要注意的是percent属性是动态绑定的,
每次变化后我们需要显式使用setData触发视图更新,不然动画是不动的
2.2 progress 已产生的进度条如何设置圆角?
<progress border-radius="5" percent="20" show-info />
2.3 已经加载完的进度条 progress,如何点击某个按钮,让它重新加载呢?
有人说开启active动画后,当进度条完成以后,直接在设置一下percent属性的值就可以实现。
但是这个方法行不通,直接将percent再设置为100或其他数值,并不能让动画重新播放。
有人设想改变两次percent,借助nextTick或延时定时器,分别在两个渲染周期设置。
nextTick是基础库2.2.3版本以上支持的,所以这位作者用了wx.canIUse这个接口,判断能不能使用这个API,
如果不能使用,则改用setTimeout设置一个延时定时器,先将percent的数值设置为0,
过了一个渲染周期或延时17毫秒再设置一次,这样就可以得到动画重新播放的效果。
this.setData({ percentValue: 0 });
if (wx.canIUse('nextTick')) {
wx.nextTick(() => {
this.setData({ percentValue: 100 });
});
} else {
setTimeout(() => {
this.setData({ percentValue: 100 });
}, 17)
}
其实还有更简单的方法,每一次setData在底层都需要调用evaluateJavascript这个底层函数,
这个函数用于逻辑层与视图层之间的通讯,它的执行本来就需要时间,因此,直接调用两次setData,也可以达到同样的效果。
连续两次调用setData可能看起来不是那么优雅,但是有时候看起来不那么优雅的代码,可能才是最简单和有效的代码。
onTapReloadBtn(e){
this.setData({percentValue:0})
this.setData({percentValue:50})
}
2.4 能否实现一个圆环形进度条呢?
用CSS绘制动画比较麻烦,可以用Canvas绘制,使用Component创建一个自定义组件,例如名字就叫circle-progress,
在这个组件的WXML代码里放置一个Canvas组件,并给他设置一个ID,这个ID为runCanvas的Canvas组件,它用于绘制上面绿色的圆,
灰色的圆圈是由一个灰色的底圆bigCircle,加一个白色的稍微小一点的圆littleCircle组合出来的,它是有CSS样式实现出来的,
在自定义组件中,通过一个percent的属性用于标识进度,这个属性与官方的progress组件具有相同的名称,这方便我们迁移代码和减小记忆负担。
observer用于自动监听属性变化,当进度增加时,调用draw函数绘制新增的绿色进度条,
在draw函数及后续调用的函数中,计算出需要绘制的弧度,及使用Canvas的弧度绘制Api arc进行绘制是实现环形效果的关键。
circle-progress是一个独立的组件,在使用时,
需要先在JSON配置中声明对组件的引用,circle-progress是声明的名称。
声明后在WXML中就可以把它当做标签使用了。
在button触发的JSON函数中,模拟网络变化改变进度值,就可以看到动画效果了。
实现自定义组件并不复杂,但有两点需要注意,
第一点,在自定义组件中使用wx.createCanvasContent创建画布的上下文绘制对象时,
需要在第二个参数处传递this对象,这样才是在组件中查找画布,不然只是在主页面中查找。
第二点,使用wx.createSelectorQuery创建的对象的select方法,以ID查找组件对象时,
如果在自定义组件中,必须在查找前先调用一下它的in方法,把this对象传递进去,不然组件是查找不到的。
默认组件查询也仅是在主页面中查找,不会涉及主页面中的子组件。
circle-progress组件
components/circle-progress/index.wxml
<view class='canvasBox'>
<!-- 外部灰色的圆 -->
<view class='bigCircle'></view>
<!-- 内部白色的圆 -->
<view class='littleCircle'></view>
<canvas canvas-id="runCanvas" id="runCanvas" class='canvas'></canvas>
</view>
components/circle-progress/index.wxss
.canvasBox{
height: 500rpx;
position: relative;
background-color: white;
}
/* 外部灰色的圆 */
.bigCircle{
width: 420rpx;
height: 420rpx;
border-radius: 50%;
position: absolute;
top:0;
bottom: 0;
left: 0;
right: 0;
margin: auto auto;
background-color: #f2f2f2;
}
/* 内部白色的圆 */
.littleCircle{
width: 350rpx;
height: 350rpx;
border-radius: 50%;
position: absolute;
top:0;
bottom: 0;
left: 0;
right: 0;
margin: auto auto;
background-color: white;
}
.canvas{
width: 420rpx;
height: 420rpx;
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
margin: auto auto;
z-index: 99;
}
components/circle-progress/index.js
Component({
runTimerid:0,
behaviors: [],
properties: {
percent: {
type: Number,
value: 0,
observer: function (newVal, oldVal) {
this.draw(newVal);
}
},
},
data: {
percentage: '', //百分比
animTime: '', // 动画执行时间
}, // 私有数据,可用于模版渲染
lifetimes: {
// 生命周期函数,可以为函数,或一个在methods段中定义的方法名
attached: function () { },
moved: function () { },
detached: function () { },
},
// 生命周期函数,可以为函数,或一个在methods段中定义的方法名
attached: function () { }, // 此处attached的声明会被lifetimes字段中的声明覆盖
pageLifetimes: {
// 组件所在页面的生命周期函数
show: function () { },
},
created() { },
ready() {
if (this.data.percent) this.draw(this.data.percent);
},
methods: {
// 绘制圆形进度条方法
run(c, w, h) {
let that = this;
var num = (2 * Math.PI / 100 * c) - 0.5 * Math.PI;
that.ctx2.arc(w, h, w - 8, -0.5 * Math.PI, num)
that.ctx2.setStrokeStyle("#09bb07");//绿色
that.ctx2.setLineWidth("16");
that.ctx2.setLineCap("butt");
that.ctx2.stroke();
that.ctx2.beginPath();
that.ctx2.setFontSize(40); //注意不要加引号
that.ctx2.setFillStyle("#b2b2b2");//浅灰色字体
that.ctx2.setTextAlign("center");
that.ctx2.setTextBaseline("middle");
that.ctx2.fillText(c + "%", w, h);
that.ctx2.draw();
},
// 动画效果实现
canvasTap(start, end, time, w, h) {
let that = this;
start++;
if (start > end) {
return false;
}
that.run(start, w, h);
that.runTimerid = setTimeout(function () {
that.canvasTap(start, end, time, w, h);
}, time);
},
draw(percent) {
const id = 'runCanvas'
const animTime = 500
if (percent > 100) return
if (!this.ctx2) {
const ctx2 = wx.createCanvasContext(id, this)
this.ctx2 = ctx2
}
let oldPercentValue = this.data.percentage
this.setData({
percentage: percent,
animTime: animTime
});
var time = this.data.animTime / (this.data.percentage-oldPercentValue);
const query = wx.createSelectorQuery().in(this)
query.select('#' + id).boundingClientRect((res) => {
var w = parseInt(res.width / 2);
var h = parseInt(res.height / 2);
if (this.runTimerid) clearTimeout(this.runTimerid)
this.canvasTap(oldPercentValue, percent, time, w, h)
}).exec()
}
}
})
使用
index.wxml
<!-- 7 环形进度条 -->
<view class="gap">环形进度条</view>
<circle-progress id="progress1" percent="{{percentValue}}" />
<button bindtap="drawProgress">redraw</button>
index.js
// 7环形进度条
drawProgress(){
if (this.data.percentValue >= 100){
this.setData({
percentValue:0
})
}
this.setData({
percentValue:this.data.percentValue+10
})
}
}
index.json
{
"usingComponents": {
"circle-progress":"../../components/circle-progress/index",
}
}
2.5 progress 右边进度条的百分比数字,它的颜色怎么设置?
有两个方法,
1.直接使用内联样式
<progress percent="40" stroke-width="5" show-info style="color:red"/>
2.在本地样式文件中找到类样式的名称 ,然后在页面中重写
.wx-progress-info {
color: red;
}
2.6 progress 组件右侧的百分比文字,与左边离得太近了,可否增加一个边距?
.wx-progress-info {
color: red;margin-left: 5px;
}
2.7 问题3中,为什么setTimeout设置的延时定时器要使用17毫秒
这是因为目前小程序1秒内最大渲染的帧数是60帧,每帧渲染约平均花费16.66毫秒,这是一个渲染周期最小的时间单位,17毫秒约等于这个数。