uniapp:微信小程序使用Canvas 和Canvas 2D绘制图形

一、Canvas 画布

canvas 组件 提供了绘制界面,可以在之上进行任意绘制
功能描述

Canvas 画布。2.9.0 起支持一套新 Canvas 2D 接口(需指定 type 属性),同时支持同层渲染,原有接口不再维护。

二、Canvas 和Canvas 2D 区别

  • Canvas 2D 需指定 type 属性
  • Canvas 属性 canvas-id ,Canvas 2D改成 id
  • Canvas 标签默认宽度300px、高度150px
  • Canvas 2D(新接口)需要显式设置画布宽高,默认:300*150,最大:1365*1365
c 复制代码
<canvas class="pie-chart" canvas-id="chartOrg"></canvas>
<canvas class="pie-chart" type="2d" id="chart2d"></canvas>

API的区别

Canvas 新版 Canvas 2D
canvasContext.draw() canvasContext接口没有 draw 方法
canvasContext.setFillStyle("#000000") canvasContext.fillStyle = "#000000"
canvasContext.setFontSize(16) canvasContext.font = '16px PingFang SC'
canvasContext.setTextAlign('center') canvasContext.textAlign = "center"

提醒:上面API的区别是下面示例用到的,还有其他API的变化,感兴趣的读者自行查阅。

三、示例用到的uni API

3.1 uni.createCanvasContext(canvasId, componentInstance)
3.1.1 定义

创建 canvas 绘图上下文(指定 canvasId)。在自定义组件下,第二个参数传入组件实例this,以操作组件内 组件
提醒: 需要指定 canvasId,该绘图上下文只作用于对应的

参数

参数 类型 说明
canvasId String 画布标识,传入定义在 的 canvas-id或id(支付宝小程序是id、其他平台是canvas-id)
componentInstance Object 自定义组件实例 this ,表示在这个自定义组件下查找拥有 canvas-id 的 ,如果省略,则不在任何自定义组件内查找

返回值:CanvasContext

3.2 uni.getSystemInfoSync()

获取系统信息的同步接口。调用参数和返回值和调用 uni.getSystemInfo() 一样。

3.3 uni.createSelectorQuery()

提醒

  • 使用 uni.createSelectorQuery() 需要在生命周期 mounted 后进行调用。
  • 默认需要使用到 selectorQuery.in 方法。
    selectorQuery.in(component)

将选择器的选取范围更改为自定义组件 component 内,返回一个 SelectorQuery 对象实例。(初始时,选择器仅选取页面范围的节点,不会选取任何自定义组件中的节点)。

示例代码

c 复制代码
const query = uni.createSelectorQuery().in(this);
query
  .select("#id")
  .boundingClientRect((data) => {
    console.log("得到布局位置信息" + JSON.stringify(data));
    console.log("节点离页面顶部的距离为" + data.top);
  })
  .exec();

注意: 支付宝小程序不支持 in(component),使用无效果

四、微信小程序使用Canvas 和Canvas 2D绘制图形示例

示例效果图

testCanvas.vue代码

复制代码
<template>
    <view class="view-root-wrap">
        <view class="view-content-wrap">
            <view class="view-content">
                <text>使用canvas绘制</text>
                <text>使用canvas 2D 绘制</text>
            </view>
            <view class="view-content">
                <canvas class="pie-chart" canvas-id="chartOrg"></canvas>
                <canvas class="pie-chart"  type="2d" id="chart2d"></canvas>
            </view>
        </view>
    </view>
</template>
<script>
export default {
    data() {
        return {
            isReady: false,
        }
    },
    onReady() {
        this.isReady = true
        // this.drawChartOrg()
        // this.startDrawChart2D()
    },

    onLoad(options) {
        this.drawChartOrg()
        this.startDrawChart2D()
    },
    methods: {
        // 绘制饼图
        drawChartOrg() {
            console.log(`执行了 drawChartOrg 方法  this.isReady = ${this.isReady}`)

            const canvasContext = uni.createCanvasContext('chartOrg', this)
            const centerX = 70 // 圆心x坐标
            const centerY = 70 // 圆心y坐标
            const innerRadius = 46;// 内圆半径
            const outerRadius = 70; // 外圆半径

            let chartData = [
                { value: 3, color: '#7539f5', name: 'Fortran' },
                { value: 4, color: '#91041c', name: 'Delphi/Object Pascal' },
                { value: 4.5, color: '#0582a9', name: 'Visual Basic' },
                { value: 5.5, color: '#39c8f5', name: 'Go' },
                { value: 6, color: '#f2d407', name: 'JavaScript' },
                { value: 7, color: '#480479', name: 'C#' },
                { value: 9, color: '#f29707', name: 'Java' },
                { value: 11, color: '#5620f3', name: 'C' },
                { value: 15, color: '#0676c2', name: 'C++' },
                { value: 35, color: '#339AF0', name: 'Python' },
            ];

            const totalPercent = 100
            let startAngle = 1.5 * Math.PI // 起始角度

            // 绘制外圆
            chartData.forEach(item => {
                const sliceAngle = (2 * Math.PI * item.value) / totalPercent
                const endAngle = startAngle + sliceAngle
                canvasContext.beginPath()
                canvasContext.moveTo(centerX, centerY)
                canvasContext.arc(centerX, centerY, outerRadius, startAngle, endAngle)
                canvasContext.closePath()
                canvasContext.setFillStyle(item.color)
                canvasContext.fill()
                startAngle = endAngle
            })

            // 绘制内圆
            canvasContext.beginPath()
            canvasContext.arc(centerX, centerY, innerRadius, 0, 2 * Math.PI)
            canvasContext.setFillStyle('#FFFFFF')
            canvasContext.fill()
            // 添加中间文字
            canvasContext.setFontSize(16)
            canvasContext.setFillStyle('#000000')
            canvasContext.setTextAlign('center')
            canvasContext.fillText("编程语言", centerX, centerY + 8)
            canvasContext.draw()
        },


        startDrawChart2D() {
            console.log(`执行了 startDrawChart 方法  this.isReady = ${this.isReady}`)
            let delayTime = 500;
            if (this.isReady) {
                delayTime = 150
            }
            setTimeout(() => {
                this.drawChart2D()
            }, delayTime);
        },

        // 使用 Canvas2D 绘制饼图
        drawChart2D() {
            const query = uni.createSelectorQuery().in(this)
            query.select('#chart2d')
                .fields({ node: true, size: true })
                .exec((res) => {
                    try {
                        let { node, width, height } = res[0];
                        const canvas = node
                        const canvasContext = canvas.getContext('2d')

                        const dpr = uni.getSystemInfoSync().devicePixelRatio
                        const tempWidth = width * dpr
                        canvas.width = tempWidth
                        canvas.height = tempWidth
                        canvasContext.scale(dpr, dpr)

                        const radius = width / 2
                        const centerX = radius  // 圆心x坐标
                        const centerY = radius // 圆心y坐标
                        const outerRadius = radius; // 外圆半径
                        const innerRadius = outerRadius * 0.65; // 内圆半径

                        let chartData = [
                            { value: 3, color: '#7539f5', name: 'Fortran' },
                            { value: 4, color: '#91041c', name: 'Delphi/Object Pascal' },
                            { value: 4.5, color: '#0582a9', name: 'Visual Basic' },
                            { value: 5.5, color: '#39c8f5', name: 'Go' },
                            { value: 6, color: '#f2d407', name: 'JavaScript' },
                            { value: 7, color: '#480479', name: 'C#' },
                            { value: 9, color: '#f29707', name: 'Java' },
                            { value: 11, color: '#5620f3', name: 'C' },
                            { value: 15, color: '#0676c2', name: 'C++' },
                            { value: 35, color: '#339AF0', name: 'Python' },
                        ];

                        const totalPercent = 100
                        let startAngle = 1.5 * Math.PI // 起始角度
                        // 绘制外圆
                        chartData.forEach(item => {
                            const sliceAngle = (2 * Math.PI * item.value) / totalPercent
                            const endAngle = startAngle + sliceAngle
                            canvasContext.beginPath()
                            canvasContext.moveTo(centerX, centerY)
                            canvasContext.arc(centerX, centerY, outerRadius, startAngle, endAngle)
                            canvasContext.closePath()
                            canvasContext.fillStyle = item.color
                            canvasContext.fill()
                            startAngle = endAngle
                        })

                        // 绘制内圆
                        canvasContext.beginPath()
                        canvasContext.arc(centerX, centerY, innerRadius, 0, 2 * Math.PI)
                        canvasContext.fillStyle = "#FFFFFF"
                        canvasContext.fill()
                        // 添加中间文字
                        canvasContext.font = '16px PingFang SC'
                        canvasContext.fillStyle = "#000000"
                        canvasContext.textAlign = "center"
                        canvasContext.fillText("编程语言", centerX, centerY + 8)

                    } catch (error) {

                        console.log("drawChart2D 绘制发生异常: " + error)
                    }
                })
        },

    }
}
</script>
<style scoped>
.view-root-wrap {
    padding: 12px;
}

.view-content-wrap {
    display: flex;
    flex-direction: column;
    justify-content: center;
    background-color: white;
    padding: 28rpx;
    border-radius: 16rpx;
}

.view-content {
    display: flex;
    flex-direction: row;
    justify-content: space-around;
    margin-top: 32rpx;
}

.pie-chart {
    width: 140px;
    height: 140px;
}
</style>