最近在搞一个微信小程序,有一个圆环的进度条,而且要求颜色要渐变的,本来想用秋云插件实现,但是秋云的插件不能滑动这个进度条,后面用canvas实现
成品效果图:
避坑:
<canvas id="myCanvas" type="2d"></canvas> <canvas canvas-id="myCanvas" ></canvas>
两个canvas标签,一个是新版的,一个是旧版的,如果指定了type,那么必须要指定id属性值,不然wx.createSelectorQuery()是无法获取到标签的
如果不指定type,使用旧版代码 wx.createCanvasContext('myCanvas')实现,会出现层级问题,canvas标签永远在最顶层,而且是无解的, 所以使用新的canvas2d去实现项目需求。
新canvas的宽高默认是300*150的,这个值不会根据css样式改变,需要手动设置
wx.createSelectorQuery() .select('#myCanvas').fields({ node: true, size: true }) .exec((res) => { const canvas = res[0].node console.log(canvas.width, canvas.height);canvas.width = 250 canvas.height = 250
})
新版的canvas使用了web3C标准,推荐文档:https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D
实现代码:
js代码
Page({ /** * 页面的初始数据 */ data: { progress: 74, // 当前转盘值 isDragging: false, // 是否触摸中 startAngle: -0.5 * Math.PI, // 开始角度 endAngle: -0.5 * Math.PI, // 结束角度 radius: 100, // 圆环半径 centerX: 0, centerY: 0 }, onReady() { this.setData({ centerX: 250 / 2, centerY: 250 / 2 }) // 计算圆盘的初始值 let endAngleVal:number = (this.data.progress - 25) / (5) * 0.1 this.setData({ endAngle: endAngleVal * Math.PI }) setTimeout(() => { this.drawCircle(); }, 200) }, drawCircle() { wx.createSelectorQuery() .select('#myCanvas').fields({ node: true, size: true }) .exec((res) => { const canvas = res[0].node canvas.width = 250 canvas.height = 250 const ctx = canvas.getContext('2d') ctx.clearRect(0, 0, canvas.width, canvas.height) // 清除画布 ctx.moveTo(0, 0.5); // 使用0.5增量对齐像素,消除锯齿 // 背景圆弧渲染 ctx.beginPath(); ctx.arc(this.data.centerX, this.data.centerY, this.data.radius, 0, 2 * Math.PI); ctx.strokeStyle = '#a6a6a6'; // 设置描边颜色 ctx.lineWidth = 20; // 设置描边宽度 ctx.stroke(); // 进度条渲染 ctx.beginPath(); ctx.arc(this.data.centerX, this.data.centerY, this.data.radius, this.data.startAngle, this.data.endAngle, false); // 渐变颜色 const gradient = ctx.createConicGradient(-10, 125, 125); // const gradient = ctx.createLinearGradient(100, 0, 125, 125); gradient.addColorStop(0, "#00a8e3"); gradient.addColorStop(0.5, "#fde001"); gradient.addColorStop(1, "#f00"); // ctx.strokeStyle = '#007BFF'; // 描边颜色 ctx.strokeStyle = gradient; ctx.lineWidth = 20; // 设置描边宽度 ctx.lineCap = 'round' // 线条类型 ctx.stroke(); // 描边路径,绘制环形进度条 // 滑动圆点 ctx.beginPath(); let whitePoint = { x: this.data.centerX + this.data.radius * Math.cos(this.data.endAngle), y: this.data.centerY + this.data.radius * Math.sin(this.data.endAngle) }; ctx.strokeStyle = '#FFF' ctx.lineCap = 'round'; ctx.lineWidth = 8; ctx.arc(whitePoint.x, whitePoint.y, 6, 0, 2 * Math.PI); // 空心圆 ctx.stroke(); ctx.closePath(); // 结束画布路径 }) }, updateAngle(x:number, y:number) { const dx = x - this.data.centerX; const dy = y - this.data.centerY; const angle = Math.atan2(dy, dx); // 计算触摸点与中心点的连线与x轴的夹角 if(angle > -1.778 && angle < -1.56) { return } let progressVal = (angle / (2 * Math.PI) + 0.5) * 100 let progressRange = parseFloat(Math.max(0, Math.min(100, progressVal)).toFixed(0)) if (progressRange > 25) { progressRange -= 25 } else { progressRange += 75 } if (progressRange >= 100) { progressRange = 0 } this.setData({ endAngle : angle, // 确保进度在0到100之间 progress : progressRange }) }, doTouchStart(e:any) {//触摸开始 this.setData({ isDragging : true }) this.updateAngle(e.touches[0].x, e.touches[0].y); this.drawCircle(); }, doTouchMove (e:any) {//触摸移动 if (this.data.isDragging) { this.updateAngle(e.touches[0].x, e.touches[0].y); this.drawCircle(); } }, doTouchend() {//触摸结束 this.setData({ isDragging : false }) }, }
wxml代码
<canvas id="myCanvas" type="2d" disable-scroll="{{true}}" bindtouchmove="doTouchMove" bindtouchstart="doTouchStart" bindtouchend="doTouchend"
bindtouchcancel="doTouchend" class="canvas-style"></canvas>
<text>{{progress}}</text>
在微信开发者工具中显示正常,但是仍有问题
官网中说canvas2D是支持全部的web标准了,确实在开发者工具中没有错误,但是真机调试中有错误
createConicGradient 这个方法报错了,这个方法是渐变颜色,圆椎形态渐变,很适合做圆环的渐变,
var ctx = canvas.getContext('2d'); ctx.beginPath(); const gradient = ctx.createConicGradient(-1.569, 200, 200); gradient.addColorStop(0, "#00a8e3"); gradient.addColorStop(0.5, "#fde001aa"); gradient.addColorStop(1, "#f00"); ctx.fillStyle = gradient; ctx.fillRect(0, 0, 500, 500); ctx.closePath();
效果图:
奈何微信小程序不支持,后面我有想到了放这张锥形渐变图片上去,使用createPattern方法,
先在html中试过了可以实现
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>可滑动的环形进度条</title> <style> canvas { border: 1px solid black; } </style> </head> <body> <canvas id="progressCanvas" width="300" height="300"></canvas> <style> #progressCanvas { /* background: linear-gradient(45deg, #ff7e5f, #feb47b, #fef); */ } </style> <script> const canvas = document.getElementById('progressCanvas'); const ctx = canvas.getContext('2d'); let progress = 0; // 进度值,范围从0到100 let isDragging = false; // 是否正在拖动进度条 let startAngle = -0.5 * Math.PI; // 起始角度 let endAngle = startAngle; // 结束角度 const radius = 100; // 进度条半径 const centerX = canvas.width / 2; // 进度条中心X坐标 const centerY = canvas.height / 2; // 进度条中心Y坐标 // 绘制环形进度条 function drawRingProgress() { ctx.clearRect(0, 0, canvas.width, canvas.height); // 清除画布 // 背景圆弧 ctx.beginPath(); ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI); ctx.strokeStyle = '#a6a6a6'; // 设置描边颜色 ctx.lineWidth = 20; // 设置描边宽度 ctx.stroke(); // 描边路径,绘制环形进度条 // 进度颜色 ctx.beginPath(); ctx.arc(centerX, centerY, radius, startAngle, endAngle, false); // 绘制环形进度条的路径 // 替换成渐变颜色 let iamge = new Image() iamge.src = './a.png' // ctx.strokeStyle = '#007BFF'; // 设置描边颜色 ctx.strokeStyle = ctx.createPattern(iamge, "no-repeat") ctx.lineWidth = 20; // 设置描边宽度 ctx.lineCap = 'round' ctx.stroke(); // 描边路径,绘制环形进度条 // 滑动圆点 ctx.beginPath(); let whitePoint = { x: centerX + radius * Math.cos(endAngle), y: centerY + radius * Math.sin(endAngle) }; ctx.strokeStyle = '#FFF' ctx.lineCap = 'round'; ctx.lineWidth = 8; ctx.arc(whitePoint.x, whitePoint.y, 6, 0, 2 * Math.PI); // 空心圆 ctx.stroke(); ctx.closePath(); } // 更新进度条的角度 function updateAngle(x, y) { const dx = x - centerX; const dy = y - centerY; const angle = Math.atan2(dy, dx); // 计算触摸点与中心点的连线与x轴的夹角 endAngle = angle; progress = (angle / (2 * Math.PI) + 0.5) * 100; // 将角度转换为百分比进度 progress = Math.max(0, Math.min(100, progress)); // 确保进度在0到100之间 } // 监听鼠标按下事件 canvas.addEventListener('mousedown', (e) => { isDragging = true; updateAngle(e.clientX, e.clientY); drawRingProgress(); }); // 监听鼠标移动事件 canvas.addEventListener('mousemove', (e) => { if (isDragging) { updateAngle(e.clientX, e.clientY); drawRingProgress(); } }); // 监听鼠标松开事件 canvas.addEventListener('mouseup', () => { isDragging = false; }); // 监听鼠标离开画布事件 canvas.addEventListener('mouseleave', () => { isDragging = false; }); // 初始绘制 drawRingProgress(); </script> </body> </html>
效果图
后面再到微信小程序这边,我才想起来,微信小程序是没有 Image() 这个方法的,获取不到图片对象,没有办法通过图片去实现。
所以还是用了createLinearGradient方法。希望微信后续可以修改好这个问题。