微信小程序使用convas画弧形滑块

先来看看效果图
在这里插入图片描述
在这里插入图片描述
两种效果图其实都是一个逻辑, 控制弧度的起始角度与结束角度,再控制粗细即可实现
先在我们页面上创建一个convas元素

<canvas style="{{'width:'+(canvasSize+'px')+';'+('height:'+(canvasSize+'px')+';')}}" canvas-id="slider" type="2d" id="myCanvas" bindtouchstart="onMouseDown" bindtouchmove="onMouseMove" bindtouchend="onChend"></canvas>

canvasSize:控制convas大小的变量,我这里是正方形渲染,所以控制的宽高是一样的大小
onMouseDown:手指按下事件
onMouseMove:手指移动事件
onChend:手指松开事件

现在我们已经有了convas元素,接下来我们先画一个弧形

	//先定义一些全局的变量,写在data里面也可以
	let startAngle, allAngle, lineWidth, ctx, cvs, r, dotR, A, O;
	//.......
	 data: {
	 
	 	//定义一些参数
	 	  ratio: 0,
	 	  integerTemperature:0,
	 	  decimalsTemperature:0,
        canvasSize: 0,
           minTemperature:20, //最小值 这里是我自己的变量,根据你们需求自己定义
            maxTemperature:62, //最大值

            unitTemperature:'°C', //单位
            step:1,// {step:滑块的步进单位}
            value:0, //存储的温度值
		 config: {
		 		
	            size: 450,  //渲染的convas大小
	            dotRatio: 5,//渲染的滑块点大小
	            nowRatio: 50,
	            lineRatio: 4, //线条的粗细
	            allAngle: 300, //结束的弧度
	            canMove: true, //是否可以拖动滑块
	            startAngle: 120, //起始弧度
	            dotColor: '#FFF', //点的颜色
	            actColor: '#e0282f' //激活态颜色
	        }
	 }
 	//.......
	//写一个初始化的函数
    init() {
        const query = wx.createSelectorQuery()
        // 查询canvas实例
        query.select("#myCanvas").fields({
            node: true,
            size: true
        }).exec((res) => {
            // 创建实例
            cvs = res[0].node
            // 创建上下文
            ctx = cvs.getContext("2d")
            const dpr = wx.getSystemInfoSync().pixelRatio
           
            this.data.canvasSize = this.data.config.size / 750 * wx.getSystemInfoSync().windowWidth
            cvs.width = this.data.canvasSize * dpr
            cvs.height = this.data.canvasSize * dpr
            startAngle = this.data.config.startAngle > 360 ? 360 : this.data.config.startAngle
            allAngle = this.data.config.allAngle > 360 ? 360 : this.data.config.allAngle
            this.data.ratio = this.data.config.nowRatio
            r = this.data.canvasSize * .4
            dotR = (r * this.data.config.dotRatio / 100) * dpr
            lineWidth = (r * this.data.config.lineRatio / 100) * dpr
            A = {
                x: this.data.canvasSize / 2,
                y: this.data.canvasSize / 2
            }
            O = {
                x: this.data.canvasSize / 2 + r,
                y: this.data.canvasSize / 2
            }
            const config = this.data.config
            config.nowRatio = Math.floor(this.data.ratio)
            this.setData({
                config,
                canvasSize: this.data.canvasSize,
                ratio: Math.floor(this.data.ratio),
            })
            //开始准备画滑块
            this.drawSlider()
        })

    },
     calcDotPosition() {
        let cosA = Math.cos((startAngle + allAngle * this.data.ratio / 100) * 2 * Math.PI / 360),
            sinA = Math.sin((startAngle + allAngle * this.data.ratio / 100) * 2 * Math.PI / 360)
        return {
            x: (O.x - A.x) * cosA - (O.y - A.y) * sinA + A.x,
            y: (O.y - A.y) * cosA + (O.x - A.x) * sinA + A.y
        }
    },
//------------------------最主要的代码-------------
       drawSlider() {
        // 背景
        const dpr = wx.getSystemInfoSync().pixelRatio
        ctx.beginPath()
        ctx.clearRect(0, 0, this.data.canvasSize * dpr, this.data.canvasSize * dpr)
        const style = ctx.createLinearGradient(0, 0, this.data.canvasSize, 0);
        
        //-----这里是定义滑块非激活态的颜色,我这里是用的渐变色-----
        style.addColorStop(0, '#e3e6ea');
        style.addColorStop(.5, '#e3e6ea');
        style.addColorStop(1, '#e3e6ea');
        
        ctx.shadowBlur = 0
        ctx.shadowColor = "transparent"
        ctx.lineCap = 'butt'
        ctx.strokeStyle = style
        ctx.lineWidth = (lineWidth)
        ctx.arc(A.x * dpr, A.y * dpr, r * dpr, (startAngle - 3) / 180 * Math.PI, (allAngle + startAngle + 3) / 180 * Math.PI)
        ctx.stroke()

        // 进度条 --画激活态
        ctx.beginPath()
        ctx.shadowBlur = 0
        ctx.shadowColor = "transparent" //阴影
        ctx.strokeStyle = (this.data.config.actColor)
        ctx.arc(A.x * dpr, A.y * dpr, r * dpr, (startAngle - 3) / 180 * Math.PI, ((this.data.ratio / 100) * (allAngle / 180) + (startAngle / 180)) * Math.PI)
        ctx.stroke()
        let{
            minTemperature, //最小温度值 这里是我自己的变量,根据你们需求自己定义
            maxTemperature, //最大温度值
            ratio,
            unitTemperature, //温度单位
            value //存储的温度值
            step,
        } = this.data
      
   
  
        //把当前的弧度转换为数值 *我这里做了小数数据与整数分隔 
    	 const num = String((Math.round(((((maxTemperature - minTemperature) * ratio / 100) + minTemperature).toFixed(1)) / step) * step).toFixed(1)).split('.')


        function drawCenteredText(ctx, text, centerX, centerY, size, falg) {
            ctx.fillStyle = "#58595b"; // 文本颜色
            // 设置字体样式
            ctx.font = ` ${size*dpr}px AlibabaPuHuiTi_2_65_Medium`; 
            ctx.textAlign = 'center';
            ctx.textBaseline = 'middle';

            if (falg) {
            // 测量文本宽度
            const textWidth = ctx.measureText(text).width;
            centerX = centerX+textWidth+50
            centerY=centerY-40
            }

            // 渲染文本
            ctx.fillText(text, centerX, centerY);
        }

        // 调用函数
        drawCenteredText(ctx, `${num[0]}`, A.x * dpr, A.y * dpr, 42);
        drawCenteredText(ctx, `${unitTemperature}`, A.x * dpr, A.y * dpr, 14, true);

        // 滑块
        ctx.beginPath()
        ctx.shadowBlur = 0
        ctx.shadowColor = "gray"
        ctx.strokeStyle = "#fff"
        ctx.lineWidth = 5
        ctx.fillStyle = (this.data.config.dotColor)
        ctx.arc(this.calcDotPosition().x * dpr, this.calcDotPosition().y * dpr, dotR, 0, 2 * Math.PI, false)
        ctx.stroke()


        function degreesToRadians(degrees) {
            return degrees * Math.PI / 180;
        }

        // 画刻度
        const numTicks = 7; //要画几个刻度
        const angleSpan = this.config.allAngle; 
        const angleIncrement = angleSpan / numTicks;
        // 在数字下方画小圆球
        const ballDiameter = 3 * dpr; // 圆球直径,考虑dpr

        for (let i = 0; i <= numTicks; i++) {
            const cx = A.x * dpr;
            const cy = A.y * dpr;
            const angle = startAngle + i * angleIncrement;
            const x = cx + ((r * dpr) + 15) * Math.cos(degreesToRadians(angle));
            const y = cy + ((r * dpr) + 15) * Math.sin(degreesToRadians(angle));

            // 文本距离圆周的距离,单位为像素
            const textOffset = 10 * dpr; // 考虑dpr
            const textX = x + textOffset * Math.cos(degreesToRadians(angle));
            const textY = y + textOffset * Math.sin(degreesToRadians(angle));

            // 设置文本样式,根据dpr调整字体大小
            ctx.font = `${10 * dpr}px Arial`; // 字体大小根据dpr调整
            ctx.textAlign = "center"; // 文本居中
            ctx.textBaseline = "middle"; // 文本基线居中
            ctx.fillStyle = "#e1373c"; // 文本颜色

            const num = (maxTemperature - minTemperature) / numTicks
            // 文本的旋转角度,使其垂直于圆周
            const rotationAngle = angle + 90; // Canvas坐标系下,+90度使文本垂直于半径
            ctx.save(); // 保存当前绘图状态
            ctx.translate(textX, textY); // 移动原点至文本位置
            ctx.rotate(degreesToRadians(rotationAngle)); // 旋转
            ctx.fillText(parseInt((num * i) + minTemperature), 0, 0); // 绘制文本
            ctx.restore(); // 恢复之前的绘图状态



            ctx.beginPath();
            const x2 = cx + ((r * dpr - 5)) * Math.cos(degreesToRadians(angle));
            const y2 = cy + ((r * dpr - 5)) * Math.sin(degreesToRadians(angle));
            let textX2 = (x2) + textOffset * Math.cos(degreesToRadians(angle));
            let textY2 = (y2) + textOffset * Math.sin(degreesToRadians(angle));
            ctx.arc(textX2, textY2, ballDiameter / 2, 0, 2 * Math.PI);
            ctx.fillStyle = "#e1373c"; // 圆球颜色
            ctx.fill();
            ctx.closePath();
        }

    },
    

然后在生命周期中调用init() 即可获得一个弧形的滑块了,那么接下来我们完成三个事件中的处理

onMouseDown:手指按下事件

   onMouseDown(e) {
     
        this.data.downValue = this.data.value
        if (!this.data.config.canMove) return
        // 背景
        const {
            x,
            y
        } = e.touches[0]
        this.calcAngle(x, y)
    },
    
    calcAngle(x, y) {
        const dpr = wx.getSystemInfoSync().pixelRatio
        const AB = Math.sqrt(Math.pow(A.x - O.x, 2) + Math.pow(A.y - O.y, 2)),
            AC = Math.sqrt(Math.pow(A.x - x, 2) + Math.pow(A.y - y, 2)),
            BC = Math.sqrt(Math.pow(O.x - x, 2) + Math.pow(O.y - y, 2)),
            cosA = (Math.pow(AB, 2) + Math.pow(AC, 2) - Math.pow(BC, 2)) / (2 * AB * AC)

        // 触摸的点到圆心的距离超过范围
        if (AC > r * 1.2 || AC < r * .7) return
        let angleA = Math.round(Math.acos(cosA) * 180 / (Math.PI))
        // 判断夹角在顺时针还是逆时针
        const dx = x - A.x;
        const dy = y - A.y;
        if ((O.x - A.x) * dy - (O.y - A.y) * dx < 0) {
            angleA = 360 - angleA
        }
        const tempAngle = angleA - startAngle < 0 ? Math.round((360 - startAngle + angleA) / allAngle * 100) : Math.round((angleA - startAngle) / allAngle * 100)
        if (tempAngle > 100) return
        this.data.ratio = tempAngle
        const config = this.data.config
        config.nowRatio = tempAngle
        this.drawSlider()

    },

onMouseMove:手指移动事件

    onMouseMove(e) {
        if (!this.data.config.canMove) return
        const {
            x,
            y
        } = e.touches[0]
        this.calcAngle(x, y)
    },

onChend:手指松开事件

    /**  计算温度 */
    calculateTemperature() {
        const {
            minTemperature,
            maxTemperature,
            ratio,
            temperatureSlider,
            modeSelect
        } = this.data
        const step = temperatureSlider[modeSelect][0].step

        const num = String((Math.round(((((maxTemperature - minTemperature) * ratio / 100) + minTemperature).toFixed(1)) / step) * step).toFixed(1)).split('.')

        // 取出整数
        this.setData({
            integerTemperature: num[0],
            decimalsTemperature: num[1] || 0
        })
    },
 onChend(e) {
 
        this.data.timeTable = setTimeout(() => {
            this.calculateTemperature()
            let{
                integerTemperature,
                decimalsTemperature,
                modeSelect
            } = this.data
            const num = (Number(integerTemperature) + Number(decimalsTemperature / 10))
            if (this.data.downValue == num) {
                return
            }
        }, 1500)


    },
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值