
简体中文
组件类型:UniCanvasElement
画布
Web | 微信小程序 | Android | iOS | HarmonyOS |
---|---|---|---|---|
4.21 | 4.41 | 4.25 | 4.25 | 4.61 |
App平台4.25之前没有完整的canvas组件,但提供了DrawableContext
。
在绘制形状、文字、图片方面,uni-app x有2种解决方案:canvas组件
和 DOM的DrawableContext API
它们的区别是:
名称 | 类型 | 默认值 | 兼容性 | 描述 |
---|---|---|---|---|
type | string | - | (string) 指定 canvas 类型,支持 2d (2.9.0) 和 webgl (2.7.0) | |
canvas-id | string | - | (string) canvas 组件的唯一标识符,若指定了 type 则无需再指定该属性 | |
disable-scroll | boolean | - | (boolean) 当在 canvas 中移动时且有绑定手势事件时,禁止屏幕滚动以及下拉刷新 | |
@touchstart | eventhandle | - | (eventhandle) 手指触摸动作开始 | |
@touchmove | eventhandle | - | (eventhandle) 手指触摸后移动 | |
@touchend | eventhandle | - | (eventhandle) 手指触摸动作结束 | |
@touchcancel | eventhandle | - | (eventhandle) 手指触摸动作被打断,如来电提醒,弹窗 | |
@longtap | eventhandle | - | (eventhandle) 手指长按 500ms 之后触发,触发了长按事件后进行移动不会触发屏幕的滚动 | |
@error | eventhandle | - | (eventhandle) 当发生错误时触发 error 事件,detail = {errMsg} |
注意:Android平台默认会开启硬件加速无需额外设置
不可以嵌套组件
老版 uni-app 的 canvas 使用了微信小程序的的旧版规范,和 W3C 规范有差异。微信小程序新版的 canvas 规范已经与 W3C 规范拉齐。
uni-app x 中废弃了老版方案,使用了 W3C 规范和微信小程序的新版规范。
注意:在uni-app x 4.21版以前,Web平台开发者写的老版canvas是可以运行的。但从 4.21+ 支持新版规范起,不再支持老版规范。开发者需调整代码。
注意:新版规范需要开发者根据自己的场景手动处理高清屏问题。
canvas相关的API较多,参考如下:
这种方式跨平台,一般推荐这种写法。需HBuilderX 4.25+支持。
组合式
<template>
<canvas id="canvas"></canvas>
</template>
<script setup>
onReady(() => {
// HBuilderX 4.25+
// 异步调用方式, 跨平台写法
uni.createCanvasContextAsync({
id: 'canvas',
component: getCurrentInstance().proxy,
success: (context : CanvasContext) => {
const canvasContext = context.getContext('2d')!;
const canvas = canvasContext.canvas;
// 处理高清屏逻辑
const dpr = uni.getDeviceInfo().devicePixelRatio ?? 1;
canvas.width = canvas.offsetWidth * dpr;
canvas.height = canvas.offsetHeight * dpr;
canvasContext.scale(dpr, dpr); // 仅需调用一次,当调用 reset 方法后需要再次 scale
}
})
})
</script>
选项式
<template>
<view>
<canvas id="canvas"></canvas>
</view>
</template>
<script>
export default {
name: 'canvas',
data() {
return {
}
},
onReady() {
uni.createCanvasContextAsync({
id: 'canvas',
component: this,
success: (context : CanvasContext) => {
const canvasContext = context.getContext('2d')!;
const canvas = canvasContext.canvas;
// 处理高清屏逻辑
const dpr = uni.getDeviceInfo().devicePixelRatio ?? 1;
canvas.width = canvas.offsetWidth * dpr;
canvas.height = canvas.offsetHeight * dpr;
canvasContext.scale(dpr, dpr); // 仅需调用一次,当调用 reset 方法后需要再次 scale
}
})
}
}
</script>
文档详见
需HBuilderX 4.21+支持。
同步方式不支持小程序。仅App和web可以使用。
<template>
<canvas id="canvas"></canvas>
</template>
<script setup>
onReady(() => {
// 同步调用方式,仅支持 app/web
const canvas = uni.getElementById("canvas") as UniCanvasElement
const context = canvas.getContext("2d")!;
// 处理高清屏逻辑
const dpr = uni.getDeviceInfo().devicePixelRatio ?? 1;
canvas.width = canvas.offsetWidth * dpr;
canvas.height = canvas.offsetHeight * dpr;
context.scale(dpr, dpr); // 仅需调用一次,当调用 reset 方法后需要再次 scale
// 省略绘制代码,和 w3c 规范保持一致
})
</script>
示例为hello uni-app x alpha分支,与最新HBuilderX Alpha版同步。与最新正式版同步的master分支示例另见
示例
<template>
<view class="page" id="page-canvas">
<canvas id="canvas" class="canvas-element"></canvas>
<scroll-view class="scroll-view">
<!-- #ifdef WEB -->
<button class="canvas-drawing-button" @click="canvasToBlob">canvasToBlob</button>
<view>
<text>testToBlobResult: {{testToBlobResult}}</text>
</view>
<!-- #endif -->
<button class="canvas-drawing-button" id="toDataURL" @click="canvasToDataURL">canvasToDataURL</button>
<view class="text-group" v-if="dataBase64.length>0">
<text>canvasToDataURL:</text>
<text>{{dataBase64.slice(0,22)}}...</text>
</view>
<button @click="onCreateImage">createImage</button>
<button @click="onCreatePath2D">createPath2D</button>
<button @click="startAnimationFrame">requestAnimationFrame</button>
<button @click="stopAnimationFrame">cancelAnimationFrame</button>
<view style="padding: 8px 10px;">CanvasContext API 演示</view>
<navigator url="./canvas-context">
<button>CanvasContext API</button>
</navigator>
<view class="text-group">
<text>获取 CanvasContext 结果:</text>
<text id="testCanvasContext">{{testCanvasContext}}</text>
</view>
<view class="text-group">
<text>测试 ToDataURL 结果:</text>
<text id="testToDataURLResult">{{testToDataURLResult}}</text>
</view>
<view class="text-group">
<text>测试 createImage 结果:</text>
<text id="testCreateImage">{{testCreateImage}}</text>
</view>
<view class="text-group">
<text>测试 createPath2D 结果:</text>
<text id="testCreatePath2D">{{testCreatePath2D}}</text>
</view>
<view class="text-group">
<text>测试 createCanvasContextAsync 结果:</text>
<view @click="testCreateContextAsync" id="createCanvasContextAsync">{{testCanvasCtx}}</view>
</view>
<canvas-child ref="canvas-child"></canvas-child>
</scroll-view>
</view>
</template>
<script>
import CanvasChild from './canvas-child.uvue'
function hidpi(canvas : UniCanvasElement) {
const context = canvas.getContext("2d")!;
const dpr = uni.getWindowInfo().pixelRatio;
canvas.width = canvas.offsetWidth * dpr;
canvas.height = canvas.offsetHeight * dpr;
context.scale(dpr, dpr);
}
export default {
components: {
CanvasChild
},
data() {
return {
title: 'Context2D',
canvas: null as UniCanvasElement | null,
canvasContext: null as CanvasContext | null,
renderingContext: null as CanvasRenderingContext2D | null,
canvasWidth: 0,
canvasHeight: 0,
dataBase64: '',
taskId: 0,
lastTime: 0,
frameCount: 0,
// 仅测试
testCanvasContext: false,
testToBlobResult: false,
testToDataURLResult: false,
testCreateImage: false,
testCreatePath2D: false,
testFrameCount: 0,
testCanvasCtx1: false,
testCanvasCtx2: false,
testCounter: 0
}
},
computed: {
testCanvasCtx() {
return this.testCanvasCtx1 && this.testCanvasCtx2
}
},
onLoad() {
// HBuilderX 4.25+
// 异步调用方式, 跨平台写法
uni.createCanvasContextAsync({
id: 'canvas',
component: this,
success: (context : CanvasContext) => {
this.canvasContext = context;
this.renderingContext = context.getContext('2d')!;
this.canvas = this.renderingContext!.canvas;
hidpi(this.canvas!);
this.canvasWidth = this.canvas!.width;
this.canvasHeight = this.canvas!.height;
// #ifdef WEB
context.toBlob((blob : Blob) => {
this.testToBlobResult = (blob.size > 0 && blob.type == 'image/jpeg')
}, 'image/jpeg', 0.95);
// #endif
// #ifdef APP || WEB || MP
setTimeout(() => {
this.testToDataURLResult = this.canvasContext!.toDataURL().startsWith('data:image/png;base64')
}, 50)
// #endif
this.testCanvasContext = true
}
})
uni.$on('canvasChildReady', this.onChildReady)
},
onReady() {
// 同步调用方式,仅支持 app/web
// let canvas = uni.getElementById("canvas") as UniCanvasElement
// this.renderingContext = canvas.getContext("2d")
// hidpi(canvas);
// this.canvas = canvas;
// this.canvasWidth = canvas.width;
// this.canvasHeight = canvas.height;
},
onUnload() {
uni.$off('canvasChildReady', this.onChildReady)
if (this.taskId > 0) {
this.stopAnimationFrame()
}
},
methods: {
// #ifdef WEB
canvasToBlob() {
this.canvasContext!.toBlob((blob : Blob) => {
this.testToBlobResult = (blob.size > 0 && blob.type == 'image/jpeg')
}, 'image/jpeg', 0.95)
},
// #endif
canvasToDataURL() {
this.dataBase64 = this.canvasContext!.toDataURL()
},
onCreateImage() {
this.renderingContext!.clearRect(0, 0, this.canvasWidth, this.canvasHeight)
let image = this.canvasContext!.createImage();
image.src = "/static/logo.png"
image.onload = () => {
this.testCreateImage = true
this.renderingContext?.drawImage(image, 0, 0, 100, 100);
}
},
onCreatePath2D() {
this.renderingContext!.clearRect(0, 0, this.canvasWidth, this.canvasHeight)
const context = this.renderingContext!
let path2D = this.canvasContext!.createPath2D()
this.testCreatePath2D = true
const amplitude = 64;
const wavelength = 64;
for (let i = 0; i < 5; i++) {
const x1 = 0 + (i * wavelength);
const y1 = 128;
const x2 = x1 + wavelength / 4;
const y2 = y1 - amplitude;
const x3 = x1 + 3 * wavelength / 4;
const y3 = y1 + amplitude;
const x4 = x1 + wavelength;
const y4 = y1;
context.moveTo(x1, y1);
path2D.bezierCurveTo(x2, y2, x3, y3, x4, y4);
}
context.stroke(path2D);
},
startAnimationFrame() {
this.taskId = this.canvasContext!.requestAnimationFrame((timestamp : number) => {
this.testFrameCount++
this.updateFPS(timestamp)
this.startAnimationFrame()
})
},
stopAnimationFrame() {
this.canvasContext!.cancelAnimationFrame(this.taskId)
this.taskId = 0
},
updateFPS(timestamp : number) {
this.frameCount++
if (timestamp - this.lastTime >= 1000) {
const timeOfFrame = (1000 / this.frameCount)
this.renderingContext!.clearRect(0, 0, this.canvasWidth, this.canvasHeight)
this.renderingContext!.fillText(`${this.frameCount} / ${timeOfFrame.toFixed(3)}ms`, 10, 18)
this.frameCount = 0
this.lastTime = timestamp
}
},
testCreateContextAsync() {
uni.createCanvasContextAsync({
id: 'canvas',
component: this,
success: () => {
this.testCanvasCtx1 = true
}
})
// no `component` param
uni.createCanvasContextAsync({
id: 'canvas',
success: () => {
this.testCanvasCtx2 = true
}
})
},
onChildReady() {
const childInstance = (this.$refs['canvas-child'] as ComponentPublicInstance);
this.testCounter = childInstance.$data['testCounter'] as number;
}
}
}
</script>
<style>
.page {
flex: 1;
height: 100%;
overflow: hidden;
}
.scroll-view {
flex: 1;
}
.canvas-element {
width: 100%;
height: 250px;
background-color: #ffffff;
}
.btn-to-image {
margin: 10px;
}
.text-group {
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
align-items: center;
padding: 8px 10px;
}
</style>
canvas有很多应用场景,插件市场有很多封装好的插件:
一些web平台的canvas插件,并没有适配uts。此时使用web-view中的canvas也是一种方案,uvue页面里的web-view组件可以和uvue页面里的uts代码双向通信。