297 行
8.5 KiB
Plaintext
297 行
8.5 KiB
Plaintext
<template>
|
||
<view class="page" id="page-canvas">
|
||
<page-intro content="本页演示 canvas 画布组件:toDataURL、createImage、createPath2D、requestAnimationFrame 等;可跳转 CanvasContext API 子页,展示绘图与异步上下文。"></page-intro>
|
||
<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: {{data.testToBlobResult}}</text>
|
||
</view>
|
||
<!-- #endif -->
|
||
<button class="canvas-drawing-button" id="toDataURL" @click="canvasToDataURL">canvasToDataURL</button>
|
||
<view class="text-group" v-if="data.dataBase64.length>0">
|
||
<text>canvasToDataURL:</text>
|
||
<text>{{data.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">{{data.testCanvasContext}}</text>
|
||
</view>
|
||
<view class="text-group">
|
||
<text>测试 ToDataURL 结果:</text>
|
||
<text id="testToDataURLResult">{{data.testToDataURLResult}}</text>
|
||
</view>
|
||
|
||
<view class="text-group">
|
||
<text>测试 createImage 结果:</text>
|
||
<text id="testCreateImage">{{data.testCreateImage}}</text>
|
||
</view>
|
||
|
||
<view class="text-group">
|
||
<text>测试 createPath2D 结果:</text>
|
||
<text id="testCreatePath2D">{{data.testCreatePath2D}}</text>
|
||
</view>
|
||
|
||
<view class="text-group">
|
||
<text>测试 createCanvasContextAsync 结果:</text>
|
||
<text @click="testCreateContextAsync" id="createCanvasContextAsync">{{testCanvasCtx}}</text>
|
||
</view>
|
||
<canvas-child ref="canvasChildRef"></canvas-child>
|
||
</scroll-view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="uts">
|
||
import CanvasChild from './canvas-child.uvue'
|
||
|
||
const instance = getCurrentInstance()!.proxy
|
||
|
||
type DataType = {
|
||
title: string;
|
||
canvas: UniCanvasElement | null;
|
||
canvasContext: CanvasContext | null;
|
||
renderingContext: CanvasRenderingContext2D | null;
|
||
canvasWidth: number;
|
||
canvasHeight: number;
|
||
dataBase64: string;
|
||
taskId: number;
|
||
lastTime: number;
|
||
frameCount: number;
|
||
testCanvasContext: boolean;
|
||
testToBlobResult: boolean;
|
||
testToDataURLResult: boolean;
|
||
testCreateImage: boolean;
|
||
testCreatePath2D: boolean;
|
||
testFrameCount: number;
|
||
testCanvasCtx1: boolean;
|
||
testCanvasCtx2: boolean;
|
||
testCounter: number;
|
||
}
|
||
|
||
const data = reactive({
|
||
title: 'Context2D',
|
||
canvas: null,
|
||
canvasContext: null,
|
||
renderingContext: 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
|
||
} as DataType)
|
||
|
||
const canvasChildRef = ref<ComponentPublicInstance | null>(null)
|
||
|
||
const testCanvasCtx = computed(() => {
|
||
return data.testCanvasCtx1 && data.testCanvasCtx2
|
||
})
|
||
|
||
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);
|
||
}
|
||
|
||
// #ifdef WEB
|
||
const canvasToBlob = () => {
|
||
data.canvasContext!.toBlob((blob : Blob) => {
|
||
data.testToBlobResult = (blob.size > 0 && blob.type == 'image/jpeg')
|
||
}, 'image/jpeg', 0.95)
|
||
}
|
||
// #endif
|
||
|
||
const canvasToDataURL = () => {
|
||
data.dataBase64 = data.canvasContext!.toDataURL()
|
||
}
|
||
|
||
const onCreateImage = () => {
|
||
data.renderingContext!.clearRect(0, 0, data.canvasWidth, data.canvasHeight)
|
||
let image = data.canvasContext!.createImage();
|
||
image.src = "/static/test-image/logo.png"
|
||
image.onload = () => {
|
||
data.testCreateImage = true
|
||
data.renderingContext?.drawImage(image, 0, 0, 100, 100);
|
||
}
|
||
}
|
||
|
||
const onCreatePath2D = () => {
|
||
data.renderingContext!.clearRect(0, 0, data.canvasWidth, data.canvasHeight)
|
||
const context = data.renderingContext!
|
||
let path2D = data.canvasContext!.createPath2D()
|
||
data.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);
|
||
}
|
||
|
||
const updateFPS = (timestamp : number) => {
|
||
data.frameCount++
|
||
if (timestamp - data.lastTime >= 1000) {
|
||
const timeOfFrame = (1000 / data.frameCount)
|
||
data.renderingContext!.clearRect(0, 0, data.canvasWidth, data.canvasHeight)
|
||
data.renderingContext!.fillText(`${data.frameCount} / ${timeOfFrame.toFixed(3)}ms`, 10, 18)
|
||
data.frameCount = 0
|
||
data.lastTime = timestamp
|
||
}
|
||
}
|
||
|
||
type StartAnimationFrameType = () => void
|
||
let startAnimationFrame: StartAnimationFrameType = () => {}
|
||
startAnimationFrame = () => {
|
||
data.taskId = data.canvasContext!.requestAnimationFrame((timestamp : number) => {
|
||
data.testFrameCount++
|
||
updateFPS(timestamp)
|
||
startAnimationFrame()
|
||
})
|
||
}
|
||
|
||
const stopAnimationFrame = () => {
|
||
data.canvasContext!.cancelAnimationFrame(data.taskId)
|
||
data.taskId = 0
|
||
}
|
||
|
||
const testCreateContextAsync = () => {
|
||
uni.createCanvasContextAsync({
|
||
id: 'canvas',
|
||
component: instance!,
|
||
success: () => {
|
||
data.testCanvasCtx1 = true
|
||
}
|
||
})
|
||
|
||
uni.createCanvasContextAsync({
|
||
id: 'canvas',
|
||
success: () => {
|
||
data.testCanvasCtx2 = true
|
||
}
|
||
})
|
||
}
|
||
|
||
const onChildReady = (count: number) => {
|
||
// 通过事件传递数据
|
||
data.testCounter = count
|
||
}
|
||
|
||
// TODO 暂时使用 onReady NativeView生命周期存在问题
|
||
onReady(() => {
|
||
uni.createCanvasContextAsync({
|
||
id: 'canvas',
|
||
component: instance!,
|
||
success: (context : CanvasContext) => {
|
||
data.canvasContext = context;
|
||
data.renderingContext = context.getContext('2d')!;
|
||
data.canvas = data.renderingContext!.canvas;
|
||
|
||
hidpi(data.canvas!);
|
||
data.canvasWidth = data.canvas!.width;
|
||
data.canvasHeight = data.canvas!.height;
|
||
|
||
// #ifdef WEB
|
||
context.toBlob((blob : Blob) => {
|
||
data.testToBlobResult = (blob.size > 0 && blob.type == 'image/jpeg')
|
||
}, 'image/jpeg', 0.95);
|
||
// #endif
|
||
// #ifdef APP || WEB || MP
|
||
setTimeout(() => {
|
||
data.testToDataURLResult = data.canvasContext!.toDataURL().startsWith('data:image/png;base64')
|
||
}, 50)
|
||
// #endif
|
||
data.testCanvasContext = true
|
||
}
|
||
})
|
||
})
|
||
|
||
onReady(() => {
|
||
// 同步调用方式,仅支持 app/web
|
||
// let canvas = uni.getElementById("canvas") as UniCanvasElement
|
||
// data.renderingContext = canvas.getContext("2d")
|
||
// hidpi(canvas);
|
||
// data.canvas = canvas;
|
||
// data.canvasWidth = canvas.width;
|
||
// data.canvasHeight = canvas.height;
|
||
})
|
||
|
||
onLoad(() => {
|
||
//监听canvasChildReady从onReady调整到onLoad,避免监听时机过晚导致无法收到此事件
|
||
uni.$on('canvasChildReady', onChildReady)
|
||
})
|
||
|
||
onUnload(() => {
|
||
uni.$off('canvasChildReady', onChildReady)
|
||
if (data.taskId > 0) {
|
||
stopAnimationFrame()
|
||
}
|
||
})
|
||
|
||
defineExpose({
|
||
data,
|
||
canvasToDataURL,
|
||
onCreateImage,
|
||
onCreatePath2D
|
||
})
|
||
</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>
|