优化 canvasToPng
This commit is contained in:
@@ -578,6 +578,23 @@ export function random(min: number, max: number): number {
|
|||||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将base64转换为blob
|
||||||
|
* @param data base64数据
|
||||||
|
* @returns blob数据
|
||||||
|
*/
|
||||||
|
export function base64ToBlob(data: string, type: string = "image/jpeg"): Blob {
|
||||||
|
// #ifdef H5
|
||||||
|
let bytes = window.atob(data.split(",")[1]);
|
||||||
|
let ab = new ArrayBuffer(bytes.length);
|
||||||
|
let ia = new Uint8Array(ab);
|
||||||
|
for (let i = 0; i < bytes.length; i++) {
|
||||||
|
ia[i] = bytes.charCodeAt(i);
|
||||||
|
}
|
||||||
|
return new Blob([ab], { type });
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查是否为小程序环境
|
* 检查是否为小程序环境
|
||||||
* @returns 是否为小程序环境
|
* @returns 是否为小程序环境
|
||||||
|
|||||||
81
cool/utils/file.ts
Normal file
81
cool/utils/file.ts
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import { base64ToBlob } from "./comm";
|
||||||
|
|
||||||
|
export type CanvasToPngOptions = {
|
||||||
|
canvasId: string;
|
||||||
|
proxy?: ComponentPublicInstance;
|
||||||
|
canvasRef: UniElement;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将canvas转换为png图片
|
||||||
|
* @param options 转换参数
|
||||||
|
* @returns 图片路径
|
||||||
|
*/
|
||||||
|
export function canvasToPng(options: CanvasToPngOptions): Promise<string> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
// #ifdef APP
|
||||||
|
options.canvasRef.parentElement!.takeSnapshot({
|
||||||
|
success(res) {
|
||||||
|
resolve(res.tempFilePath);
|
||||||
|
},
|
||||||
|
fail(err) {
|
||||||
|
console.error(err);
|
||||||
|
resolve("");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifdef H5
|
||||||
|
const url = URL.createObjectURL(
|
||||||
|
base64ToBlob(
|
||||||
|
(options.canvasRef as unknown as HTMLCanvasElement)
|
||||||
|
.querySelector("canvas")
|
||||||
|
?.toDataURL("image/png", 1) ?? ""
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
resolve(url);
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifdef MP
|
||||||
|
uni.createCanvasContextAsync({
|
||||||
|
id: options.canvasId,
|
||||||
|
component: options.proxy,
|
||||||
|
success(context) {
|
||||||
|
// 获取2D绘图上下文
|
||||||
|
const ctx = context.getContext("2d")!;
|
||||||
|
const canvas = ctx.canvas;
|
||||||
|
|
||||||
|
// 将canvas转换为base64格式的PNG图片数据
|
||||||
|
const data = canvas.toDataURL("image/png", 1);
|
||||||
|
// 获取base64数据部分(去掉data:image/png;base64,前缀)
|
||||||
|
const bdataBase64 = data.split(",")[1];
|
||||||
|
|
||||||
|
// 获取文件系统管理器
|
||||||
|
const fileMg = uni.getFileSystemManager();
|
||||||
|
// 生成临时文件路径
|
||||||
|
// @ts-ignore
|
||||||
|
const filepath = `${wx.env.USER_DATA_PATH}/${uuid()}.png`;
|
||||||
|
// 将base64数据写入文件
|
||||||
|
fileMg.writeFile({
|
||||||
|
filePath: filepath,
|
||||||
|
data: bdataBase64,
|
||||||
|
encoding: "base64",
|
||||||
|
success() {
|
||||||
|
// 写入成功返回文件路径
|
||||||
|
resolve(filepath);
|
||||||
|
},
|
||||||
|
fail() {
|
||||||
|
// 写入失败返回空字符串
|
||||||
|
resolve("");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
fail(err) {
|
||||||
|
console.error(err);
|
||||||
|
resolve("");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// #endif
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -3,3 +3,4 @@ export * from "./storage";
|
|||||||
export * from "./path";
|
export * from "./path";
|
||||||
export * from "./day";
|
export * from "./day";
|
||||||
export * from "./parse";
|
export * from "./parse";
|
||||||
|
export * from "./file";
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
<cl-page>
|
<cl-page>
|
||||||
<cl-sign
|
<cl-sign
|
||||||
ref="signRef"
|
ref="signRef"
|
||||||
|
:height="isFullscreen ? windowHeight - 200 : 200"
|
||||||
:width="windowWidth"
|
:width="windowWidth"
|
||||||
:fullscreen="isFullscreen"
|
|
||||||
:enable-brush="isBrush"
|
:enable-brush="isBrush"
|
||||||
></cl-sign>
|
></cl-sign>
|
||||||
|
|
||||||
@@ -30,19 +30,21 @@
|
|||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import DemoItem from "../components/item.uvue";
|
import DemoItem from "../components/item.uvue";
|
||||||
|
|
||||||
const { windowWidth } = uni.getWindowInfo();
|
const { windowWidth, windowHeight } = uni.getWindowInfo();
|
||||||
|
|
||||||
const isFullscreen = ref(false);
|
const isFullscreen = ref(false);
|
||||||
const isBrush = ref(true);
|
const isBrush = ref(true);
|
||||||
const signRef = ref<ClSignComponentPublicInstance | null>(null);
|
const signRef = ref<ClSignComponentPublicInstance | null>(null);
|
||||||
|
|
||||||
function clear() {
|
function clear() {
|
||||||
signRef.value?.clear();
|
signRef.value!.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
function preview() {
|
function preview() {
|
||||||
signRef.value?.toPng().then((res) => {
|
signRef.value!.toPng().then((url) => {
|
||||||
console.log(res);
|
uni.previewImage({
|
||||||
|
urls: [url]
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
9
types/uni-app.d.ts
vendored
9
types/uni-app.d.ts
vendored
@@ -377,6 +377,15 @@ declare const onUnhandledRejection: (
|
|||||||
declare const onUnload: (hook: () => any, target?: ComponentInternalInstance | null) => void;
|
declare const onUnload: (hook: () => any, target?: ComponentInternalInstance | null) => void;
|
||||||
|
|
||||||
declare interface UniElement {
|
declare interface UniElement {
|
||||||
|
firstChild: UniElement;
|
||||||
|
lastChild: UniElement;
|
||||||
|
previousSibling: UniElement;
|
||||||
|
parentElement: UniElement;
|
||||||
|
children: UniElement[];
|
||||||
|
attributes: Map<string, any>;
|
||||||
|
dataset: Map<string, any>;
|
||||||
|
style: CSSStyleDeclaration;
|
||||||
|
classList: string[];
|
||||||
takeSnapshot(options: {
|
takeSnapshot(options: {
|
||||||
success: (res: { tempFilePath: string }) => void;
|
success: (res: { tempFilePath: string }) => void;
|
||||||
fail: (err: { errCode: number; errMsg: string }) => void;
|
fail: (err: { errCode: number; errMsg: string }) => void;
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<view
|
<view :style="{ width: getPx(props.width) + 'px', height: getPx(props.height) + 'px' }">
|
||||||
ref="qrcodeRef"
|
|
||||||
:style="{ width: getPx(props.width) + 'px', height: getPx(props.height) + 'px' }"
|
|
||||||
>
|
|
||||||
<canvas
|
<canvas
|
||||||
ref="canvasRef"
|
ref="canvasRef"
|
||||||
:canvas-id="qrcodeId"
|
:canvas-id="qrcodeId"
|
||||||
@@ -22,13 +19,13 @@ import {
|
|||||||
nextTick,
|
nextTick,
|
||||||
computed,
|
computed,
|
||||||
type PropType,
|
type PropType,
|
||||||
onUnmounted
|
onUnmounted,
|
||||||
|
shallowRef
|
||||||
} from "vue";
|
} from "vue";
|
||||||
|
|
||||||
import { drawQrcode, type QrcodeOptions } from "./draw";
|
import { drawQrcode, type QrcodeOptions } from "./draw";
|
||||||
import { getPx, isHarmony, uuid } from "@/cool";
|
import { canvasToPng, getPx, isHarmony, uuid } from "@/cool";
|
||||||
import type { ClQrcodeMode } from "../../types";
|
import type { ClQrcodeMode } from "../../types";
|
||||||
import { base64ToBlob } from "./utils";
|
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "cl-qrcode"
|
name: "cl-qrcode"
|
||||||
@@ -97,11 +94,8 @@ const { proxy } = getCurrentInstance()!;
|
|||||||
// 二维码组件id
|
// 二维码组件id
|
||||||
const qrcodeId = ref<string>("cl-qrcode-" + uuid());
|
const qrcodeId = ref<string>("cl-qrcode-" + uuid());
|
||||||
|
|
||||||
// 二维码组件元素
|
|
||||||
const qrcodeRef = ref<UniElement | null>(null);
|
|
||||||
|
|
||||||
// 二维码组件画布
|
// 二维码组件画布
|
||||||
const canvasRef = ref(null);
|
const canvasRef = shallowRef<UniElement | null>(null);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 主绘制方法,根据当前 props 生成二维码并绘制到 canvas。
|
* 主绘制方法,根据当前 props 生成二维码并绘制到 canvas。
|
||||||
@@ -148,71 +142,10 @@ function drawer() {
|
|||||||
* @param call 回调函数,返回图片路径,失败返回空字符串
|
* @param call 回调函数,返回图片路径,失败返回空字符串
|
||||||
*/
|
*/
|
||||||
function toPng(): Promise<string> {
|
function toPng(): Promise<string> {
|
||||||
return new Promise((resolve) => {
|
return canvasToPng({
|
||||||
// #ifdef APP
|
canvasId: qrcodeId.value,
|
||||||
qrcodeRef.value!.takeSnapshot({
|
proxy,
|
||||||
success(res) {
|
canvasRef: canvasRef.value!
|
||||||
resolve(res.tempFilePath);
|
|
||||||
},
|
|
||||||
fail(err) {
|
|
||||||
console.error(err);
|
|
||||||
resolve("");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// #ifdef H5
|
|
||||||
const url = URL.createObjectURL(
|
|
||||||
base64ToBlob(
|
|
||||||
(canvasRef.value as unknown as HTMLCanvasElement)
|
|
||||||
.querySelector("canvas")
|
|
||||||
?.toDataURL("image/png", 1) ?? ""
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
resolve(url);
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// #ifdef MP-WEIXIN
|
|
||||||
uni.createCanvasContextAsync({
|
|
||||||
id: qrcodeId.value,
|
|
||||||
component: proxy,
|
|
||||||
success(context) {
|
|
||||||
// 获取2D绘图上下文
|
|
||||||
const ctx = context.getContext("2d")!;
|
|
||||||
const canvas = ctx.canvas;
|
|
||||||
|
|
||||||
// 将canvas转换为base64格式的PNG图片数据
|
|
||||||
const data = canvas.toDataURL("image/png", 1);
|
|
||||||
// 获取base64数据部分(去掉data:image/png;base64,前缀)
|
|
||||||
const bdataBase64 = data.split(",")[1];
|
|
||||||
|
|
||||||
// 获取文件系统管理器
|
|
||||||
const fileMg = uni.getFileSystemManager();
|
|
||||||
// 生成临时文件路径
|
|
||||||
// @ts-ignore
|
|
||||||
const filepath = `${wx.env.USER_DATA_PATH}/${uuid()}.png`;
|
|
||||||
// 将base64数据写入文件
|
|
||||||
fileMg.writeFile({
|
|
||||||
filePath: filepath,
|
|
||||||
data: bdataBase64,
|
|
||||||
encoding: "base64",
|
|
||||||
success() {
|
|
||||||
// 写入成功返回文件路径
|
|
||||||
resolve(filepath);
|
|
||||||
},
|
|
||||||
fail() {
|
|
||||||
// 写入失败返回空字符串
|
|
||||||
resolve("");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
fail(err) {
|
|
||||||
console.error(err);
|
|
||||||
resolve("");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// #endif
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
/**
|
|
||||||
* 将base64转换为blob
|
|
||||||
* @param data base64数据
|
|
||||||
* @returns blob数据
|
|
||||||
*/
|
|
||||||
export function base64ToBlob(data: string, type: string = "image/jpeg"): Blob {
|
|
||||||
// #ifdef H5
|
|
||||||
let bytes = window.atob(data.split(",")[1]);
|
|
||||||
let ab = new ArrayBuffer(bytes.length);
|
|
||||||
let ia = new Uint8Array(ab);
|
|
||||||
for (let i = 0; i < bytes.length; i++) {
|
|
||||||
ia[i] = bytes.charCodeAt(i);
|
|
||||||
}
|
|
||||||
return new Blob([ab], { type });
|
|
||||||
// #endif
|
|
||||||
}
|
|
||||||
@@ -2,21 +2,22 @@
|
|||||||
<view class="cl-sign" :class="[pt.className]">
|
<view class="cl-sign" :class="[pt.className]">
|
||||||
<canvas
|
<canvas
|
||||||
class="cl-sign__canvas"
|
class="cl-sign__canvas"
|
||||||
|
ref="canvasRef"
|
||||||
:id="canvasId"
|
:id="canvasId"
|
||||||
:style="{
|
:style="{
|
||||||
height: `${size.height}px`,
|
height: `${height}px`,
|
||||||
width: `${size.width}px`
|
width: `${width}px`
|
||||||
}"
|
}"
|
||||||
@touchstart="onTouchStart"
|
@touchstart="onTouchStart"
|
||||||
@touchmove="onTouchMove"
|
@touchmove.stop.prevent="onTouchMove"
|
||||||
@touchend="onTouchEnd"
|
@touchend="onTouchEnd"
|
||||||
></canvas>
|
></canvas>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { parsePt, uuid } from "@/cool";
|
import { canvasToPng, parsePt, uuid } from "@/cool";
|
||||||
import { computed, getCurrentInstance, onMounted, onUnmounted, ref, watch } from "vue";
|
import { computed, getCurrentInstance, onMounted, ref, shallowRef, watch } from "vue";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "cl-sign"
|
name: "cl-sign"
|
||||||
@@ -76,22 +77,19 @@ const props = defineProps({
|
|||||||
autoRotate: {
|
autoRotate: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true
|
default: true
|
||||||
},
|
|
||||||
// 是否全屏
|
|
||||||
fullscreen: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(["change"]);
|
const emit = defineEmits(["change"]);
|
||||||
|
|
||||||
const { proxy } = getCurrentInstance()!;
|
const { proxy } = getCurrentInstance()!;
|
||||||
const { windowWidth, windowHeight } = uni.getWindowInfo();
|
|
||||||
|
|
||||||
// 触摸点类型
|
// 触摸点类型
|
||||||
type Point = { x: number; y: number; time: number };
|
type Point = { x: number; y: number; time: number };
|
||||||
|
|
||||||
|
// 矩形类型
|
||||||
|
type Rect = { left: number; top: number };
|
||||||
|
|
||||||
// 透传样式类型定义
|
// 透传样式类型定义
|
||||||
type PassThrough = {
|
type PassThrough = {
|
||||||
className?: string;
|
className?: string;
|
||||||
@@ -100,8 +98,8 @@ type PassThrough = {
|
|||||||
// 解析透传样式配置
|
// 解析透传样式配置
|
||||||
const pt = computed(() => parsePt<PassThrough>(props.pt));
|
const pt = computed(() => parsePt<PassThrough>(props.pt));
|
||||||
|
|
||||||
// canvas组件上下文
|
// 签名组件画布
|
||||||
let canvasCtx: CanvasContext | null = null;
|
const canvasRef = shallowRef<UniElement | null>(null);
|
||||||
|
|
||||||
// 绘图上下文
|
// 绘图上下文
|
||||||
let drawCtx: CanvasRenderingContext2D | null = null;
|
let drawCtx: CanvasRenderingContext2D | null = null;
|
||||||
@@ -121,57 +119,73 @@ let currentStrokeWidth = ref(3);
|
|||||||
// 速度缓冲数组(用于平滑速度变化)
|
// 速度缓冲数组(用于平滑速度变化)
|
||||||
const velocityBuffer: number[] = [];
|
const velocityBuffer: number[] = [];
|
||||||
|
|
||||||
// 当前是否为横屏
|
// canvas位置信息缓存
|
||||||
const isLandscape = ref(false);
|
let canvasRect: Rect | null = null;
|
||||||
|
|
||||||
// 动态计算的画布尺寸
|
// 获取canvas位置信息
|
||||||
const size = computed(() => {
|
function getCanvasRect(): Promise<Rect> {
|
||||||
if (props.fullscreen) {
|
return new Promise((resolve) => {
|
||||||
const { windowWidth, windowHeight } = uni.getWindowInfo();
|
// #ifdef MP
|
||||||
|
uni.createSelectorQuery()
|
||||||
|
.in(proxy)
|
||||||
|
.select(`#${canvasId}`)
|
||||||
|
.boundingClientRect((rect: any) => {
|
||||||
|
if (rect) {
|
||||||
|
canvasRect = { left: rect.left, top: rect.top };
|
||||||
|
resolve(canvasRect!);
|
||||||
|
} else {
|
||||||
|
resolve({ left: 0, top: 0 });
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.exec();
|
||||||
|
// #endif
|
||||||
|
|
||||||
return {
|
// #ifndef MP
|
||||||
width: windowWidth,
|
// 非小程序平台,在需要时通过DOM获取位置信息
|
||||||
height: windowHeight
|
canvasRect = { left: 0, top: 0 };
|
||||||
};
|
resolve(canvasRect!);
|
||||||
}
|
// #endif
|
||||||
|
});
|
||||||
if (isLandscape.value) {
|
}
|
||||||
const { windowWidth } = uni.getWindowInfo();
|
|
||||||
|
|
||||||
return {
|
|
||||||
width: windowWidth,
|
|
||||||
height: props.height
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
width: props.width,
|
|
||||||
height: props.height
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取触摸点在canvas中的坐标
|
// 获取触摸点在canvas中的坐标
|
||||||
function getTouchPos(e: TouchEvent): Point {
|
function getTouchPos(e: TouchEvent): Point {
|
||||||
const touch = e.touches[0];
|
const touch = e.touches[0];
|
||||||
|
|
||||||
|
// #ifdef H5
|
||||||
const rect = (e.target as any).getBoundingClientRect();
|
const rect = (e.target as any).getBoundingClientRect();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
x: touch.clientX - rect.left,
|
x: touch.clientX - rect.left,
|
||||||
y: touch.clientY - rect.top,
|
y: touch.clientY - rect.top,
|
||||||
time: Date.now()
|
time: Date.now()
|
||||||
};
|
};
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifndef H5
|
||||||
|
// 小程序中使用缓存的位置信息或直接使用触摸坐标
|
||||||
|
const left = canvasRect?.left ?? 0;
|
||||||
|
const top = canvasRect?.top ?? 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: touch.clientX - left,
|
||||||
|
y: touch.clientY - top,
|
||||||
|
time: Date.now()
|
||||||
|
};
|
||||||
|
// #endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算速度并返回动态线条宽度
|
// 计算速度并返回动态线条宽度
|
||||||
function calculateStrokeWidth(currentPoint: Point): number {
|
function calculateStrokeWidth(currentPoint: Point): number {
|
||||||
if (!lastPoint || !props.enableBrush) {
|
if (lastPoint == null || !props.enableBrush) {
|
||||||
return props.strokeWidth;
|
return props.strokeWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算距离和时间差
|
// 计算距离和时间差
|
||||||
const distance = Math.sqrt(
|
const distance = Math.sqrt(
|
||||||
Math.pow(currentPoint.x - lastPoint.x, 2) + Math.pow(currentPoint.y - lastPoint.y, 2)
|
Math.pow(currentPoint.x - lastPoint!.x, 2) + Math.pow(currentPoint.y - lastPoint!.y, 2)
|
||||||
);
|
);
|
||||||
const timeDelta = currentPoint.time - lastPoint.time;
|
const timeDelta = currentPoint.time - lastPoint!.time;
|
||||||
|
|
||||||
if (timeDelta <= 0) return currentStrokeWidth.value;
|
if (timeDelta <= 0) return currentStrokeWidth.value;
|
||||||
|
|
||||||
@@ -198,9 +212,17 @@ function calculateStrokeWidth(currentPoint: Point): number {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 触摸开始
|
// 触摸开始
|
||||||
function onTouchStart(e: TouchEvent) {
|
async function onTouchStart(e: TouchEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
isDrawing.value = true;
|
isDrawing.value = true;
|
||||||
|
|
||||||
|
// #ifdef MP
|
||||||
|
// 小程序中,如果没有缓存位置信息,先获取
|
||||||
|
if (canvasRect == null) {
|
||||||
|
await getCanvasRect();
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
|
||||||
lastPoint = getTouchPos(e);
|
lastPoint = getTouchPos(e);
|
||||||
|
|
||||||
// 初始化线条宽度和清空速度缓冲
|
// 初始化线条宽度和清空速度缓冲
|
||||||
@@ -211,7 +233,7 @@ function onTouchStart(e: TouchEvent) {
|
|||||||
// 触摸移动
|
// 触摸移动
|
||||||
function onTouchMove(e: TouchEvent) {
|
function onTouchMove(e: TouchEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!isDrawing.value || !lastPoint || !drawCtx) return;
|
if (!isDrawing.value || lastPoint == null || drawCtx == null) return;
|
||||||
|
|
||||||
const currentPoint = getTouchPos(e);
|
const currentPoint = getTouchPos(e);
|
||||||
|
|
||||||
@@ -221,7 +243,7 @@ function onTouchMove(e: TouchEvent) {
|
|||||||
|
|
||||||
// 绘制线条
|
// 绘制线条
|
||||||
drawCtx!.beginPath();
|
drawCtx!.beginPath();
|
||||||
drawCtx!.moveTo(lastPoint.x, lastPoint.y);
|
drawCtx!.moveTo(lastPoint!.x, lastPoint!.y);
|
||||||
drawCtx!.lineTo(currentPoint.x, currentPoint.y);
|
drawCtx!.lineTo(currentPoint.x, currentPoint.y);
|
||||||
drawCtx!.strokeStyle = props.strokeColor;
|
drawCtx!.strokeStyle = props.strokeColor;
|
||||||
drawCtx!.lineWidth = strokeWidth;
|
drawCtx!.lineWidth = strokeWidth;
|
||||||
@@ -240,86 +262,54 @@ function onTouchEnd(e: TouchEvent) {
|
|||||||
lastPoint = null;
|
lastPoint = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断横竖屏
|
|
||||||
function getOrientation() {
|
|
||||||
const { windowHeight, windowWidth } = uni.getWindowInfo();
|
|
||||||
|
|
||||||
// 判断是否为横屏(宽度大于高度)
|
|
||||||
isLandscape.value = windowWidth > windowHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 屏幕方向变化监听
|
|
||||||
function onOrientationChange() {
|
|
||||||
setTimeout(() => {
|
|
||||||
getOrientation();
|
|
||||||
|
|
||||||
// 重新初始化画布
|
|
||||||
if (props.autoRotate) {
|
|
||||||
initCanvas();
|
|
||||||
}
|
|
||||||
}, 300); // 延迟确保屏幕方向变化完成
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清除画布
|
// 清除画布
|
||||||
function clear() {
|
function clear() {
|
||||||
if (!drawCtx) return;
|
if (drawCtx == null) return;
|
||||||
|
|
||||||
const { width, height } = size.value;
|
|
||||||
|
|
||||||
// #ifdef APP
|
// #ifdef APP
|
||||||
drawCtx!.reset();
|
drawCtx!.reset();
|
||||||
// #endif
|
// #endif
|
||||||
|
|
||||||
// #ifndef APP
|
// #ifndef APP
|
||||||
drawCtx!.clearRect(0, 0, width, height);
|
drawCtx!.clearRect(0, 0, props.width, props.height);
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// 获取设备像素比
|
||||||
|
const dpr = uni.getDeviceInfo().devicePixelRatio ?? 1;
|
||||||
|
|
||||||
|
// #ifndef H5
|
||||||
|
// 设置缩放比例
|
||||||
|
drawCtx!.scale(dpr, dpr);
|
||||||
// #endif
|
// #endif
|
||||||
|
|
||||||
// 填充背景色
|
// 填充背景色
|
||||||
drawCtx!.fillStyle = props.backgroundColor;
|
drawCtx!.fillStyle = props.backgroundColor;
|
||||||
drawCtx!.fillRect(0, 0, width, height);
|
drawCtx!.fillRect(0, 0, props.width, props.height);
|
||||||
|
|
||||||
emit("change");
|
emit("change");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取签名图片
|
// 获取签名图片
|
||||||
function toPng(): Promise<string> {
|
function toPng(): Promise<string> {
|
||||||
return new Promise((resolve, reject) => {
|
return canvasToPng({
|
||||||
if (!canvasCtx) {
|
proxy,
|
||||||
reject(new Error("Canvas context not initialized"));
|
canvasId,
|
||||||
return;
|
canvasRef: canvasRef.value!
|
||||||
}
|
|
||||||
|
|
||||||
uni.canvasToTempFilePath(
|
|
||||||
{
|
|
||||||
canvasId: canvasId,
|
|
||||||
success: (res) => {
|
|
||||||
resolve(res.tempFilePath);
|
|
||||||
},
|
|
||||||
fail: (err) => {
|
|
||||||
reject(err);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
proxy
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化画布
|
// 初始化画布
|
||||||
function initCanvas() {
|
function initCanvas() {
|
||||||
const { width, height } = size.value;
|
|
||||||
|
|
||||||
uni.createCanvasContextAsync({
|
uni.createCanvasContextAsync({
|
||||||
id: canvasId,
|
id: canvasId,
|
||||||
component: proxy,
|
component: proxy,
|
||||||
success: (context: CanvasContext) => {
|
success: (context: CanvasContext) => {
|
||||||
// 设置canvas上下文
|
|
||||||
canvasCtx = context;
|
|
||||||
|
|
||||||
// 获取绘图上下文
|
// 获取绘图上下文
|
||||||
drawCtx = context.getContext("2d")!;
|
drawCtx = context.getContext("2d")!;
|
||||||
|
|
||||||
// 设置宽高
|
// 设置宽高
|
||||||
drawCtx!.canvas.width = width;
|
drawCtx!.canvas.width = props.width;
|
||||||
drawCtx!.canvas.height = height;
|
drawCtx!.canvas.height = props.height;
|
||||||
|
|
||||||
// 优化渲染质量
|
// 优化渲染质量
|
||||||
drawCtx!.textBaseline = "middle";
|
drawCtx!.textBaseline = "middle";
|
||||||
@@ -328,38 +318,26 @@ function initCanvas() {
|
|||||||
|
|
||||||
// 初始化背景
|
// 初始化背景
|
||||||
clear();
|
clear();
|
||||||
|
|
||||||
|
// #ifdef MP
|
||||||
|
// 小程序中初始化时获取canvas位置信息
|
||||||
|
getCanvasRect();
|
||||||
|
// #endif
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 判断横屏竖屏
|
|
||||||
getOrientation();
|
|
||||||
|
|
||||||
// 初始化画布
|
|
||||||
initCanvas();
|
initCanvas();
|
||||||
|
|
||||||
// 监听屏幕方向变化
|
|
||||||
if (props.autoRotate) {
|
|
||||||
uni.onWindowResize(onOrientationChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 监听全屏状态变化
|
|
||||||
watch(
|
watch(
|
||||||
() => props.fullscreen,
|
computed(() => [props.width, props.height]),
|
||||||
() => {
|
() => {
|
||||||
initCanvas();
|
initCanvas();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
// 移除屏幕方向监听
|
|
||||||
if (props.autoRotate) {
|
|
||||||
uni.offWindowResize(onOrientationChange);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
clear,
|
clear,
|
||||||
toPng
|
toPng
|
||||||
|
|||||||
Reference in New Issue
Block a user