图片绘制成功 排查为何之前不行
This commit is contained in:
parent
94e79c4b77
commit
f25c008fb5
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
|
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
|
||||||
{
|
{
|
||||||
"path" : "pages/index/gesture-canvas-page",
|
"path" : "pages/index/index",
|
||||||
"style" :
|
"style" :
|
||||||
{
|
{
|
||||||
"navigationBarTitleText" : ""
|
"navigationBarTitleText" : ""
|
||||||
|
@ -1,299 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view class="container">
|
|
||||||
<view class="gesture-container"
|
|
||||||
@touchstart="handleTouchEvent"
|
|
||||||
@touchmove="handleTouchEvent"
|
|
||||||
@touchend="handleTouchEvent"
|
|
||||||
@touchcancel="handleTouchEvent">
|
|
||||||
|
|
||||||
<!-- 显示调试信息 -->
|
|
||||||
<view class="debug-info">
|
|
||||||
<text>缩放: {{scaleValue.toFixed(2)}} | X: {{transformMatrix.tx.toFixed(1)}} | Y: {{transformMatrix.ty.toFixed(1)}}</text>
|
|
||||||
<text>手势: {{gestureStatus}}</text>
|
|
||||||
<text>触点: {{touchPoints}}</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<transform-canvas
|
|
||||||
ref="canvasRef"
|
|
||||||
:width="canvasWidth"
|
|
||||||
:height="canvasHeight"
|
|
||||||
:matrix="transformMatrix"
|
|
||||||
/>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="controls">
|
|
||||||
<button @click="resetCanvas">重置</button>
|
|
||||||
<button @click="zoomIn">放大</button>
|
|
||||||
<button @click="zoomOut">缩小</button>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import TransformMatrix from './transform-matrix.js'
|
|
||||||
import GestureHandler from './gesture-handler.js'
|
|
||||||
import TransformCanvas from './transform-canvas.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
TransformCanvas
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
canvasWidth: 300,
|
|
||||||
canvasHeight: 300,
|
|
||||||
transformMatrix: new TransformMatrix(),
|
|
||||||
gestureHandler: null,
|
|
||||||
containerRect: null,
|
|
||||||
gestureStatus: '等待手势...',
|
|
||||||
scaleValue: 1,
|
|
||||||
touchPoints: 0,
|
|
||||||
lastGestureTime: 0
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
async mounted() {
|
|
||||||
await this.getContainerPosition();
|
|
||||||
|
|
||||||
// 2. 初始化手势处理器(传入容器位置信息)
|
|
||||||
this.initGestureHandler();
|
|
||||||
|
|
||||||
// 3. 初始绘制
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.updateCanvas();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
// 独立的手势初始化方法
|
|
||||||
initGestureHandler() {
|
|
||||||
try {
|
|
||||||
this.gestureHandler = new GestureHandler(this, this.transformMatrix, {
|
|
||||||
container: this.containerRect
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.error('手势处理器初始化失败:', e);
|
|
||||||
// 简化降级方案
|
|
||||||
this.gestureHandler = {
|
|
||||||
catchEvent: (event) => {
|
|
||||||
// 只处理最基本的行为
|
|
||||||
if (event.touches.length > 0) {
|
|
||||||
const firstTouch = event.touches[0];
|
|
||||||
const x = firstTouch.clientX - this.containerRect.left;
|
|
||||||
const y = firstTouch.clientY - this.containerRect.top;
|
|
||||||
|
|
||||||
// 仅更新调试信息显示
|
|
||||||
this.gestureStatus = "降级模式: 单指拖动";
|
|
||||||
this.touchPoints = event.touches.length;
|
|
||||||
|
|
||||||
// 简单变换
|
|
||||||
if (event.type === 'touchmove') {
|
|
||||||
this.transformMatrix.tx = x;
|
|
||||||
this.transformMatrix.ty = y;
|
|
||||||
this.updateCanvas();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async getContainerPosition() {
|
|
||||||
// 添加重试机制
|
|
||||||
let retryCount = 0;
|
|
||||||
const maxRetries = 3;
|
|
||||||
|
|
||||||
while (retryCount < maxRetries) {
|
|
||||||
try {
|
|
||||||
const rect = await new Promise(resolve => {
|
|
||||||
const query = uni.createSelectorQuery().in(this);
|
|
||||||
query.select('.gesture-container').boundingClientRect(res => {
|
|
||||||
resolve(res);
|
|
||||||
}).exec();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (rect) {
|
|
||||||
this.containerRect = {
|
|
||||||
left: rect.left,
|
|
||||||
top: rect.top,
|
|
||||||
width: rect.width,
|
|
||||||
height: rect.height
|
|
||||||
};
|
|
||||||
console.log('成功获取容器位置:', this.containerRect);
|
|
||||||
return; // 成功获取则退出
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('获取容器位置失败:', e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 等待后重试
|
|
||||||
await new Promise(r => setTimeout(r, 100));
|
|
||||||
retryCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.error(`容器位置获取失败,已重试${maxRetries}次`);
|
|
||||||
},
|
|
||||||
// 事件处理
|
|
||||||
async handleTouchEvent(event) {
|
|
||||||
|
|
||||||
if (!this.gestureHandler || !this.containerRect) {
|
|
||||||
console.warn('手势处理器未就绪,尝试重新初始化...');
|
|
||||||
await this.getContainerPosition();
|
|
||||||
this.initGestureHandler();
|
|
||||||
if (event.type === 'touchend' || event.type === 'touchcancel') {
|
|
||||||
this.lastPoint = null;
|
|
||||||
this.lastPoints = null;
|
|
||||||
this.gestureStatus = '结束';
|
|
||||||
|
|
||||||
// 更新一次画面
|
|
||||||
this.updateCanvas();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 重新处理当前事件
|
|
||||||
if (this.gestureHandler) {
|
|
||||||
return this.handleTouchEvent(event);
|
|
||||||
}
|
|
||||||
// 如果是简单处理器,使用下面优化的基础处理方法
|
|
||||||
if (!this.at) {
|
|
||||||
// 使用优化后的基础处理方法
|
|
||||||
if (correctedEvent.touches.length === 1) {
|
|
||||||
this.handlePan(correctedEvent.touches[0]);
|
|
||||||
} else if (correctedEvent.touches.length > 1) {
|
|
||||||
this.handlePinch(correctedEvent.touches);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 记录时间戳
|
|
||||||
const currentTime = Date.now();
|
|
||||||
|
|
||||||
// 更新触点数量
|
|
||||||
this.touchPoints = event.touches.length;
|
|
||||||
|
|
||||||
// 修正坐标(核心修复点)
|
|
||||||
const correctedTouches = Array.from(event.touches).map(touch => {
|
|
||||||
const x = touch.clientX - this.containerRect.left;
|
|
||||||
const y = touch.clientY - this.containerRect.top;
|
|
||||||
|
|
||||||
console.log(`原始坐标: (${touch.clientX}, ${touch.clientY}) 修正后: (${x}, ${y})`);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...touch,
|
|
||||||
x: x,
|
|
||||||
y: y
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// 创建修正后的事件对象
|
|
||||||
const correctedEvent = {
|
|
||||||
...event,
|
|
||||||
touches: correctedTouches,
|
|
||||||
changedTouches: correctedTouches
|
|
||||||
};
|
|
||||||
|
|
||||||
// 传递事件给手势处理器
|
|
||||||
this.gestureHandler.catchEvent(correctedEvent);
|
|
||||||
|
|
||||||
// 更新手势状态
|
|
||||||
if (event.type === 'touchstart') {
|
|
||||||
this.gestureStatus = event.touches.length > 1 ? '双指开始' : '单指开始';
|
|
||||||
}
|
|
||||||
else if (event.type === 'touchmove') {
|
|
||||||
this.gestureStatus = event.touches.length > 1 ? '双指缩放/移动' : '单指移动';
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.gestureStatus = '结束';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 限制更新频率:至少每50ms更新一次
|
|
||||||
if (currentTime - this.lastGestureTime > 50) {
|
|
||||||
this.lastGestureTime = currentTime;
|
|
||||||
this.updateCanvas();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// 更新Canvas(性能优化版)
|
|
||||||
updateCanvas() {
|
|
||||||
this.$nextTick(() => {
|
|
||||||
const canvas = this.$refs.canvasRef;
|
|
||||||
if (canvas) {
|
|
||||||
canvas.draw();
|
|
||||||
|
|
||||||
// 计算当前缩放值
|
|
||||||
this.scaleValue = Math.sqrt(
|
|
||||||
this.transformMatrix.a * this.transformMatrix.a +
|
|
||||||
this.transformMatrix.b * this.transformMatrix.b
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// 重置画布
|
|
||||||
resetCanvas() {
|
|
||||||
this.transformMatrix.reset();
|
|
||||||
this.scaleValue = 1;
|
|
||||||
this.gestureStatus = "画布已重置";
|
|
||||||
this.updateCanvas();
|
|
||||||
},
|
|
||||||
|
|
||||||
// 放大
|
|
||||||
zoomIn() {
|
|
||||||
this.transformMatrix.scale(1.2, 1.2, this.canvasWidth/2, this.canvasHeight/2);
|
|
||||||
this.updateCanvas();
|
|
||||||
},
|
|
||||||
|
|
||||||
// 缩小
|
|
||||||
zoomOut() {
|
|
||||||
this.transformMatrix.scale(0.8, 0.8, this.canvasWidth/2, this.canvasHeight/2);
|
|
||||||
this.updateCanvas();
|
|
||||||
},
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.container {
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gesture-container {
|
|
||||||
position: relative;
|
|
||||||
width: 300px;
|
|
||||||
height: 300px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.debug-info {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
background: rgba(0,0,0,0.6);
|
|
||||||
color: white;
|
|
||||||
padding: 5px;
|
|
||||||
font-size: 12px;
|
|
||||||
z-index: 10;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.controls {
|
|
||||||
margin-top: 20px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
padding: 8px 16px;
|
|
||||||
background: #4a8cff;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,225 +0,0 @@
|
|||||||
import AnyTouch from 'any-touch'
|
|
||||||
|
|
||||||
class GestureHandler {
|
|
||||||
constructor(context, transformMatrix, { container }) {
|
|
||||||
this.transformMatrix = transformMatrix;
|
|
||||||
this.containerRect = container;
|
|
||||||
this.panThreshold = 5; // 移动5像素以上才认为是拖动
|
|
||||||
this.panStarted = false;
|
|
||||||
|
|
||||||
// 小程序环境兼容处理
|
|
||||||
const atOptions = {
|
|
||||||
getPoint: touch => ({
|
|
||||||
x: touch.clientX - this.containerRect.left,
|
|
||||||
y: touch.clientY - this.containerRect.top
|
|
||||||
}),
|
|
||||||
preventDefault: false
|
|
||||||
};
|
|
||||||
|
|
||||||
// 小程序特化处理 - 跳过DOM相关操作
|
|
||||||
if (typeof window === 'undefined' || typeof HTMLElement === 'undefined') {
|
|
||||||
// 小程序环境下不需要设置DOM属性
|
|
||||||
atOptions._element = {
|
|
||||||
style: {},
|
|
||||||
addEventListener: () => {},
|
|
||||||
removeEventListener: () => {}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.at = new AnyTouch(atOptions);
|
|
||||||
} catch (e) {
|
|
||||||
console.error('AnyTouch初始化失败:', e);
|
|
||||||
// 降级方案:在小程序环境实现基本手势
|
|
||||||
this.handleGesture = this.createSimpleGestureHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.at) {
|
|
||||||
this.setupGestures();
|
|
||||||
console.log('AnyTouch手势处理器已初始化');
|
|
||||||
} else {
|
|
||||||
console.warn('使用简化手势处理器');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建小程序专用的简化手势处理器
|
|
||||||
createSimpleGestureHandler() {
|
|
||||||
return {
|
|
||||||
run: (event) => {
|
|
||||||
// 小程序环境下的基本手势实现
|
|
||||||
const touches = event.touches || [];
|
|
||||||
|
|
||||||
if (touches.length === 1) {
|
|
||||||
// 单指移动
|
|
||||||
this._handlePan(touches[0]);
|
|
||||||
} else if (touches.length > 1) {
|
|
||||||
// 双指缩放
|
|
||||||
this._handlePinch(touches);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
setupGestures() {
|
|
||||||
// 平移手势 - 修复版本
|
|
||||||
this.at.on('pan', (event) => {
|
|
||||||
if (event.type === 'panstart') {
|
|
||||||
console.log('panstart', event);
|
|
||||||
// 重置移动开始状态
|
|
||||||
this.panStarted = false;
|
|
||||||
}
|
|
||||||
else if (event.type === 'panmove') {
|
|
||||||
// 检查是否超过阈值
|
|
||||||
const distance = Math.sqrt(event.deltaX**2 + event.deltaY**2);
|
|
||||||
|
|
||||||
if (!this.panStarted && distance < this.panThreshold) {
|
|
||||||
return; // 小于阈值不处理
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.panStarted) {
|
|
||||||
console.log('拖动手势开始');
|
|
||||||
this.panStarted = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 基于当前缩放比例修正移动量
|
|
||||||
const currentScale = Math.sqrt(
|
|
||||||
this.transformMatrix.a * this.transformMatrix.a +
|
|
||||||
this.transformMatrix.b * this.transformMatrix.b
|
|
||||||
);
|
|
||||||
|
|
||||||
// 逆缩放移动量
|
|
||||||
const dx = event.deltaX / currentScale;
|
|
||||||
const dy = event.deltaY / currentScale;
|
|
||||||
|
|
||||||
|
|
||||||
console.log(`平移: deltaX=${event.deltaX}, deltaY=${event.deltaY} | 修正后: dx=${dx}, dy=${dy}`);
|
|
||||||
|
|
||||||
this.transformMatrix.translate(dx, dy);
|
|
||||||
} else if (event.type === 'panend') {
|
|
||||||
this.panStarted = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 缩放手势 - 修复版本
|
|
||||||
this.at.on('pinch', (event) => {
|
|
||||||
if (event.type === 'pinchstart') {
|
|
||||||
this.lastScale = 1;
|
|
||||||
}
|
|
||||||
else if (event.type === 'pinchmove') {
|
|
||||||
// 计算缩放变化量
|
|
||||||
const scaleChange = event.scale / this.lastScale;
|
|
||||||
this.lastScale = event.scale;
|
|
||||||
|
|
||||||
// 使用两指中心点
|
|
||||||
const centerX = event.center.x;
|
|
||||||
const centerY = event.center.y;
|
|
||||||
|
|
||||||
console.log(`缩放: scale=${event.scale} | 变化=${scaleChange} | 中心点: (${centerX}, ${centerY})`);
|
|
||||||
|
|
||||||
// 应用缩放
|
|
||||||
this.transformMatrix.scale(scaleChange, scaleChange, centerX, centerY);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 点击手势
|
|
||||||
this.at.on('tap', (event) => {
|
|
||||||
console.log('点击事件', event);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 长按手势
|
|
||||||
this.at.on('press', (event) => {
|
|
||||||
console.log('长按事件', event);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 公共接口保持不变
|
|
||||||
catchEvent(event) {
|
|
||||||
try {
|
|
||||||
if (this.at) {
|
|
||||||
this.at.run(event);
|
|
||||||
} else {
|
|
||||||
// 内部处理降级方案
|
|
||||||
const touches = event.touches || [];
|
|
||||||
const eventType = event.type;
|
|
||||||
|
|
||||||
// 在事件开始时重置状态
|
|
||||||
if (eventType === 'touchstart') {
|
|
||||||
this.lastPoint = null;
|
|
||||||
this.lastPoints = null;
|
|
||||||
this.lastDistance = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理不同数量的手指
|
|
||||||
if (touches.length === 1) {
|
|
||||||
this._handlePan(touches[0]);
|
|
||||||
} else if (touches.length > 1) {
|
|
||||||
this._handlePinch(touches);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('手势处理错误:', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 基础平移手势处理
|
|
||||||
_handlePan(touch) {
|
|
||||||
const pointX = touch.x || (touch.clientX - this.containerRect.left);
|
|
||||||
const pointY = touch.y || (touch.clientY - this.containerRect.top);
|
|
||||||
|
|
||||||
if (this.lastPoint) {
|
|
||||||
// 基于容器坐标系的增量计算
|
|
||||||
const dx = pointX - this.lastPoint.x;
|
|
||||||
const dy = pointY - this.lastPoint.y;
|
|
||||||
|
|
||||||
// 使用真实增量,不需要缩放修正
|
|
||||||
this.transformMatrix.translate(dx, dy);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 存储当前点 (容器坐标系)
|
|
||||||
this.lastPoint = {x: pointX, y: pointY};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 基础缩放手势处理
|
|
||||||
_handlePinch(touches) {
|
|
||||||
const point1 = touches[0];
|
|
||||||
const point2 = touches[1];
|
|
||||||
|
|
||||||
// 获取容器坐标系坐标
|
|
||||||
const getPoint = (touch) => ({
|
|
||||||
x: touch.x || (touch.clientX - this.containerRect.left),
|
|
||||||
y: touch.y || (touch.clientY - this.containerRect.top)
|
|
||||||
});
|
|
||||||
|
|
||||||
const currentPoints = [getPoint(point1), getPoint(point2)];
|
|
||||||
|
|
||||||
if (this.lastPoints) {
|
|
||||||
// 计算当前距离 (容器坐标系)
|
|
||||||
const currentDistance = Math.sqrt(
|
|
||||||
Math.pow(currentPoints[1].x - currentPoints[0].x, 2) +
|
|
||||||
Math.pow(currentPoints[1].y - currentPoints[0].y, 2)
|
|
||||||
);
|
|
||||||
|
|
||||||
// 上次距离 (容器坐标系)
|
|
||||||
const prevDistance = Math.sqrt(
|
|
||||||
Math.pow(this.lastPoints[1].x - this.lastPoints[0].x, 2) +
|
|
||||||
Math.pow(this.lastPoints[1].y - this.lastPoints[0].y, 2)
|
|
||||||
);
|
|
||||||
|
|
||||||
// 缩放比例
|
|
||||||
if (prevDistance > 0 && currentDistance > 0) {
|
|
||||||
const scaleChange = currentDistance / prevDistance;
|
|
||||||
|
|
||||||
// 计算中心点 (容器坐标系)
|
|
||||||
const centerX = (currentPoints[0].x + currentPoints[1].x) / 2;
|
|
||||||
const centerY = (currentPoints[0].y + currentPoints[1].y) / 2;
|
|
||||||
|
|
||||||
this.transformMatrix.scale(scaleChange, scaleChange, centerX, centerY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 存储当前点
|
|
||||||
this.lastPoints = currentPoints;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GestureHandler;
|
|
61
pages/index/index.vue
Normal file
61
pages/index/index.vue
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<canvas
|
||||||
|
id="myCanvas"
|
||||||
|
canvas-id="myCanvas"
|
||||||
|
type="2d"
|
||||||
|
style="width: 300px; height: 200px; border: 1px solid #eee;"
|
||||||
|
></canvas>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
onReady() {
|
||||||
|
this.drawImageOnCanvas();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
drawImageOnCanvas() {
|
||||||
|
// 获取Canvas节点
|
||||||
|
const query = uni.createSelectorQuery().in(this);
|
||||||
|
query.select('#myCanvas')
|
||||||
|
.fields({ node: true, size: true })
|
||||||
|
.exec(async (res) => {
|
||||||
|
if (!res[0]) return;
|
||||||
|
|
||||||
|
const canvas = res[0].node;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
const dpr = uni.getSystemInfoSync().pixelRatio;
|
||||||
|
this.canvas = canvas
|
||||||
|
// 适配高清屏
|
||||||
|
canvas.width = res[0].width * dpr;
|
||||||
|
canvas.height = res[0].height * dpr;
|
||||||
|
ctx.scale(dpr, dpr);
|
||||||
|
|
||||||
|
// 加载图片
|
||||||
|
try {
|
||||||
|
const image = await this.loadImage('https://assets.tech.troyrc.com/sx25/images/background/odertupian.png');
|
||||||
|
ctx.drawImage(image, 0, 0, 300, 200);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("图片加载失败:", e);
|
||||||
|
// 可选:绘制错误提示
|
||||||
|
ctx.fillStyle = '#f0f0f0';
|
||||||
|
ctx.fillRect(0, 0, 300, 200);
|
||||||
|
ctx.fillStyle = '#999';
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.fillText('图片加载失败', 150, 100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
loadImage(src) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const image = this.canvas.createImage();
|
||||||
|
image.onload = () => resolve(image);
|
||||||
|
image.onerror = reject;
|
||||||
|
image.src = src;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -1,154 +0,0 @@
|
|||||||
<template>
|
|
||||||
<canvas
|
|
||||||
canvas-id="gestureCanvas"
|
|
||||||
:style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
width: Number,
|
|
||||||
height: Number,
|
|
||||||
matrix: Object
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
ctx: null,
|
|
||||||
canvasWidth: this.width,
|
|
||||||
canvasHeight: this.height
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted() {
|
|
||||||
this.initCanvas();
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
initCanvas() {
|
|
||||||
this.ctx = uni.createCanvasContext('gestureCanvas', this);
|
|
||||||
const pixelRatio = uni.getSystemInfoSync().pixelRatio;
|
|
||||||
this.ctx.scale(pixelRatio, pixelRatio);
|
|
||||||
},
|
|
||||||
|
|
||||||
applyTransform(matrix) {
|
|
||||||
if (!matrix) return;
|
|
||||||
|
|
||||||
// 核心修复:考虑像素比的转换
|
|
||||||
const pixelRatio = uni.getSystemInfoSync().pixelRatio;
|
|
||||||
const tx = matrix.tx * pixelRatio;
|
|
||||||
const ty = matrix.ty * pixelRatio;
|
|
||||||
|
|
||||||
// 直接设置完整的变换矩阵
|
|
||||||
this.ctx.setTransform(
|
|
||||||
matrix.a,
|
|
||||||
matrix.b,
|
|
||||||
matrix.c,
|
|
||||||
matrix.d,
|
|
||||||
tx,
|
|
||||||
ty
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
// 调试网格
|
|
||||||
// drawDebugGrid() {
|
|
||||||
// if (!this.ctx) return;
|
|
||||||
|
|
||||||
// const pixelRatio = uni.getSystemInfoSync().pixelRatio;
|
|
||||||
// const width = this.canvasWidth * pixelRatio;
|
|
||||||
// const height = this.canvasHeight * pixelRatio;
|
|
||||||
|
|
||||||
// // 保存当前状态
|
|
||||||
// this.ctx.save();
|
|
||||||
|
|
||||||
// // 重置变换
|
|
||||||
// this.ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
||||||
|
|
||||||
// // 绘制网格
|
|
||||||
// this.ctx.strokeStyle = 'rgba(200, 200, 200, 0.3)';
|
|
||||||
// this.ctx.lineWidth = 1;
|
|
||||||
|
|
||||||
// // 横向线
|
|
||||||
// for (let y = 0; y <= height; y += 50) {
|
|
||||||
// this.ctx.beginPath();
|
|
||||||
// this.ctx.moveTo(0, y);
|
|
||||||
// this.ctx.lineTo(width, y);
|
|
||||||
// this.ctx.stroke();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // 纵向线
|
|
||||||
// for (let x = 0; x <= width; x += 50) {
|
|
||||||
// this.ctx.beginPath();
|
|
||||||
// this.ctx.moveTo(x, 0);
|
|
||||||
// this.ctx.lineTo(x, height);
|
|
||||||
// this.ctx.stroke();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // 绘制坐标轴
|
|
||||||
// this.ctx.strokeStyle = '#ff0000';
|
|
||||||
// this.ctx.lineWidth = 2;
|
|
||||||
// this.ctx.beginPath();
|
|
||||||
// this.ctx.moveTo(0, 0);
|
|
||||||
// this.ctx.lineTo(width, 0);
|
|
||||||
// this.ctx.stroke();
|
|
||||||
|
|
||||||
// this.ctx.beginPath();
|
|
||||||
// this.ctx.moveTo(0, 0);
|
|
||||||
// this.ctx.lineTo(0, height);
|
|
||||||
// this.ctx.stroke();
|
|
||||||
|
|
||||||
// // 恢复之前的状态
|
|
||||||
// this.ctx.restore();
|
|
||||||
|
|
||||||
// this.ctx.draw();
|
|
||||||
// },
|
|
||||||
|
|
||||||
// 绘制主内容
|
|
||||||
draw() {
|
|
||||||
if (!this.ctx) {
|
|
||||||
console.warn('Canvas上下文未初始化');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. 清除Canvas
|
|
||||||
this.clearCanvas();
|
|
||||||
|
|
||||||
// 2. 应用当前变换
|
|
||||||
this.applyTransform(this.matrix);
|
|
||||||
|
|
||||||
// 3. 绘制内容
|
|
||||||
this.drawContent();
|
|
||||||
},
|
|
||||||
|
|
||||||
clearCanvas() {
|
|
||||||
const pixelRatio = uni.getSystemInfoSync().pixelRatio;
|
|
||||||
const width = this.canvasWidth * pixelRatio;
|
|
||||||
const height = this.canvasHeight * pixelRatio;
|
|
||||||
|
|
||||||
// 重置变换
|
|
||||||
this.ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
||||||
this.ctx.clearRect(0, 0, width, height);
|
|
||||||
},
|
|
||||||
|
|
||||||
drawContent() {
|
|
||||||
// 绘制一个矩形和圆形
|
|
||||||
this.ctx.fillStyle = '#3498db';
|
|
||||||
this.ctx.fillRect(50, 50, 100, 100);
|
|
||||||
|
|
||||||
this.ctx.fillStyle = '#e74c3c';
|
|
||||||
this.ctx.beginPath();
|
|
||||||
this.ctx.arc(150, 150, 50, 0, 2 * Math.PI);
|
|
||||||
this.ctx.fill();
|
|
||||||
|
|
||||||
// 绘制一个文字
|
|
||||||
this.ctx.fillStyle = '#2c3e50';
|
|
||||||
this.ctx.font = '20px sans-serif';
|
|
||||||
this.ctx.fillText('手势Canvas', 50, 250);
|
|
||||||
|
|
||||||
// 执行绘制
|
|
||||||
this.ctx.draw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
@ -1,57 +0,0 @@
|
|||||||
class TransformMatrix {
|
|
||||||
constructor() {
|
|
||||||
this.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
reset() {
|
|
||||||
this.a = 1; // 水平缩放
|
|
||||||
this.b = 0; // 垂直倾斜
|
|
||||||
this.c = 0; // 水平倾斜
|
|
||||||
this.d = 1; // 垂直缩放
|
|
||||||
this.tx = 0; // 水平移动
|
|
||||||
this.ty = 0; // 垂直移动
|
|
||||||
this.stack = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
translate(dx, dy) {
|
|
||||||
this.tx += this.a * dx + this.c * dy;
|
|
||||||
this.ty += this.b * dx + this.d * dy;
|
|
||||||
}
|
|
||||||
|
|
||||||
scale(sx, sy, cx = 0, cy = 0) {
|
|
||||||
// 移动到中心点
|
|
||||||
this.translate(cx, cy);
|
|
||||||
|
|
||||||
// 应用缩放
|
|
||||||
this.a *= sx;
|
|
||||||
this.b *= sx;
|
|
||||||
this.c *= sy;
|
|
||||||
this.d *= sy;
|
|
||||||
|
|
||||||
// 移回原位置
|
|
||||||
this.translate(-cx, -cy);
|
|
||||||
}
|
|
||||||
|
|
||||||
toArray() {
|
|
||||||
return [this.a, this.b, this.c, this.d, this.tx, this.ty];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 用于调试的字符串表示
|
|
||||||
toString() {
|
|
||||||
return `[${this.a.toFixed(2)}, ${this.b.toFixed(2)}, ${this.c.toFixed(2)}, ${this.d.toFixed(2)}, ${this.tx.toFixed(1)}, ${this.ty.toFixed(1)}]`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 克隆方法
|
|
||||||
clone() {
|
|
||||||
const clone = new TransformMatrix();
|
|
||||||
clone.a = this.a;
|
|
||||||
clone.b = this.b;
|
|
||||||
clone.c = this.c;
|
|
||||||
clone.d = this.d;
|
|
||||||
clone.tx = this.tx;
|
|
||||||
clone.ty = this.ty;
|
|
||||||
return clone;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default TransformMatrix;
|
|
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
|||||||
{"version":3,"file":"index.js","sources":["pages/index/index.vue?type=page"],"sourcesContent":["import MiniProgramPage from '/Users/sunmeng/Desktop/wx/canvas/pages/index/index.vue'\nwx.createPage(MiniProgramPage)"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,GAAG,WAAW,eAAe;"}
|
{"version":3,"file":"index.js","sources":["pages/index/index.vue?type=page"],"sourcesContent":["import MiniProgramPage from '/Users/sunmeng/Desktop/wx/canvas/pages/index/index.vue'\nwx.createPage(MiniProgramPage)"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,GAAG,WAAW,eAAe;"}
|
2
unpackage/dist/dev/mp-weixin/app.js
vendored
2
unpackage/dist/dev/mp-weixin/app.js
vendored
@ -2,7 +2,7 @@
|
|||||||
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
||||||
const common_vendor = require("./common/vendor.js");
|
const common_vendor = require("./common/vendor.js");
|
||||||
if (!Math) {
|
if (!Math) {
|
||||||
"./pages/index/gesture-canvas-page.js";
|
"./pages/index/index.js";
|
||||||
}
|
}
|
||||||
const _sfc_main = {
|
const _sfc_main = {
|
||||||
onLaunch: function() {
|
onLaunch: function() {
|
||||||
|
2
unpackage/dist/dev/mp-weixin/app.json
vendored
2
unpackage/dist/dev/mp-weixin/app.json
vendored
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"pages": [
|
"pages": [
|
||||||
"pages/index/gesture-canvas-page"
|
"pages/index/index"
|
||||||
],
|
],
|
||||||
"window": {
|
"window": {
|
||||||
"navigationBarTextStyle": "black",
|
"navigationBarTextStyle": "black",
|
||||||
|
1005
unpackage/dist/dev/mp-weixin/common/vendor.js
vendored
1005
unpackage/dist/dev/mp-weixin/common/vendor.js
vendored
File diff suppressed because it is too large
Load Diff
@ -1,200 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
const common_vendor = require("../../common/vendor.js");
|
|
||||||
const pages_index_transformMatrix = require("./transform-matrix.js");
|
|
||||||
const pages_index_gestureHandler = require("./gesture-handler.js");
|
|
||||||
const TransformCanvas = () => "./transform-canvas.js";
|
|
||||||
const _sfc_main = {
|
|
||||||
components: {
|
|
||||||
TransformCanvas
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
canvasWidth: 300,
|
|
||||||
canvasHeight: 300,
|
|
||||||
transformMatrix: new pages_index_transformMatrix.TransformMatrix(),
|
|
||||||
gestureHandler: null,
|
|
||||||
containerRect: null,
|
|
||||||
gestureStatus: "等待手势...",
|
|
||||||
scaleValue: 1,
|
|
||||||
touchPoints: 0,
|
|
||||||
lastGestureTime: 0
|
|
||||||
};
|
|
||||||
},
|
|
||||||
async mounted() {
|
|
||||||
await this.getContainerPosition();
|
|
||||||
this.initGestureHandler();
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.updateCanvas();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
// 独立的手势初始化方法
|
|
||||||
initGestureHandler() {
|
|
||||||
try {
|
|
||||||
this.gestureHandler = new pages_index_gestureHandler.GestureHandler(this, this.transformMatrix, {
|
|
||||||
container: this.containerRect
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
common_vendor.index.__f__("error", "at pages/index/gesture-canvas-page.vue:76", "手势处理器初始化失败:", e);
|
|
||||||
this.gestureHandler = {
|
|
||||||
catchEvent: (event) => {
|
|
||||||
if (event.touches.length > 0) {
|
|
||||||
const firstTouch = event.touches[0];
|
|
||||||
const x = firstTouch.clientX - this.containerRect.left;
|
|
||||||
const y = firstTouch.clientY - this.containerRect.top;
|
|
||||||
this.gestureStatus = "降级模式: 单指拖动";
|
|
||||||
this.touchPoints = event.touches.length;
|
|
||||||
if (event.type === "touchmove") {
|
|
||||||
this.transformMatrix.tx = x;
|
|
||||||
this.transformMatrix.ty = y;
|
|
||||||
this.updateCanvas();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async getContainerPosition() {
|
|
||||||
let retryCount = 0;
|
|
||||||
const maxRetries = 3;
|
|
||||||
while (retryCount < maxRetries) {
|
|
||||||
try {
|
|
||||||
const rect = await new Promise((resolve) => {
|
|
||||||
const query = common_vendor.index.createSelectorQuery().in(this);
|
|
||||||
query.select(".gesture-container").boundingClientRect((res) => {
|
|
||||||
resolve(res);
|
|
||||||
}).exec();
|
|
||||||
});
|
|
||||||
if (rect) {
|
|
||||||
this.containerRect = {
|
|
||||||
left: rect.left,
|
|
||||||
top: rect.top,
|
|
||||||
width: rect.width,
|
|
||||||
height: rect.height
|
|
||||||
};
|
|
||||||
common_vendor.index.__f__("log", "at pages/index/gesture-canvas-page.vue:123", "成功获取容器位置:", this.containerRect);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
common_vendor.index.__f__("error", "at pages/index/gesture-canvas-page.vue:127", "获取容器位置失败:", e);
|
|
||||||
}
|
|
||||||
await new Promise((r) => setTimeout(r, 100));
|
|
||||||
retryCount++;
|
|
||||||
}
|
|
||||||
common_vendor.index.__f__("error", "at pages/index/gesture-canvas-page.vue:135", `容器位置获取失败,已重试${maxRetries}次`);
|
|
||||||
},
|
|
||||||
// 事件处理
|
|
||||||
async handleTouchEvent(event) {
|
|
||||||
if (!this.gestureHandler || !this.containerRect) {
|
|
||||||
common_vendor.index.__f__("warn", "at pages/index/gesture-canvas-page.vue:141", "手势处理器未就绪,尝试重新初始化...");
|
|
||||||
await this.getContainerPosition();
|
|
||||||
this.initGestureHandler();
|
|
||||||
if (event.type === "touchend" || event.type === "touchcancel") {
|
|
||||||
this.lastPoint = null;
|
|
||||||
this.lastPoints = null;
|
|
||||||
this.gestureStatus = "结束";
|
|
||||||
this.updateCanvas();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.gestureHandler) {
|
|
||||||
return this.handleTouchEvent(event);
|
|
||||||
}
|
|
||||||
if (!this.at) {
|
|
||||||
if (correctedEvent.touches.length === 1) {
|
|
||||||
this.handlePan(correctedEvent.touches[0]);
|
|
||||||
} else if (correctedEvent.touches.length > 1) {
|
|
||||||
this.handlePinch(correctedEvent.touches);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const currentTime = Date.now();
|
|
||||||
this.touchPoints = event.touches.length;
|
|
||||||
const correctedTouches = Array.from(event.touches).map((touch) => {
|
|
||||||
const x = touch.clientX - this.containerRect.left;
|
|
||||||
const y = touch.clientY - this.containerRect.top;
|
|
||||||
common_vendor.index.__f__("log", "at pages/index/gesture-canvas-page.vue:181", `原始坐标: (${touch.clientX}, ${touch.clientY}) 修正后: (${x}, ${y})`);
|
|
||||||
return {
|
|
||||||
...touch,
|
|
||||||
x,
|
|
||||||
y
|
|
||||||
};
|
|
||||||
});
|
|
||||||
const correctedEvent = {
|
|
||||||
...event,
|
|
||||||
touches: correctedTouches,
|
|
||||||
changedTouches: correctedTouches
|
|
||||||
};
|
|
||||||
this.gestureHandler.catchEvent(correctedEvent);
|
|
||||||
if (event.type === "touchstart") {
|
|
||||||
this.gestureStatus = event.touches.length > 1 ? "双指开始" : "单指开始";
|
|
||||||
} else if (event.type === "touchmove") {
|
|
||||||
this.gestureStatus = event.touches.length > 1 ? "双指缩放/移动" : "单指移动";
|
|
||||||
} else {
|
|
||||||
this.gestureStatus = "结束";
|
|
||||||
}
|
|
||||||
if (currentTime - this.lastGestureTime > 50) {
|
|
||||||
this.lastGestureTime = currentTime;
|
|
||||||
this.updateCanvas();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 更新Canvas(性能优化版)
|
|
||||||
updateCanvas() {
|
|
||||||
this.$nextTick(() => {
|
|
||||||
const canvas = this.$refs.canvasRef;
|
|
||||||
if (canvas) {
|
|
||||||
canvas.draw();
|
|
||||||
this.scaleValue = Math.sqrt(
|
|
||||||
this.transformMatrix.a * this.transformMatrix.a + this.transformMatrix.b * this.transformMatrix.b
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// 重置画布
|
|
||||||
resetCanvas() {
|
|
||||||
this.transformMatrix.reset();
|
|
||||||
this.scaleValue = 1;
|
|
||||||
this.gestureStatus = "画布已重置";
|
|
||||||
this.updateCanvas();
|
|
||||||
},
|
|
||||||
// 放大
|
|
||||||
zoomIn() {
|
|
||||||
this.transformMatrix.scale(1.2, 1.2, this.canvasWidth / 2, this.canvasHeight / 2);
|
|
||||||
this.updateCanvas();
|
|
||||||
},
|
|
||||||
// 缩小
|
|
||||||
zoomOut() {
|
|
||||||
this.transformMatrix.scale(0.8, 0.8, this.canvasWidth / 2, this.canvasHeight / 2);
|
|
||||||
this.updateCanvas();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (!Array) {
|
|
||||||
const _component_transform_canvas = common_vendor.resolveComponent("transform-canvas");
|
|
||||||
_component_transform_canvas();
|
|
||||||
}
|
|
||||||
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
||||||
return {
|
|
||||||
a: common_vendor.t($data.scaleValue.toFixed(2)),
|
|
||||||
b: common_vendor.t($data.transformMatrix.tx.toFixed(1)),
|
|
||||||
c: common_vendor.t($data.transformMatrix.ty.toFixed(1)),
|
|
||||||
d: common_vendor.t($data.gestureStatus),
|
|
||||||
e: common_vendor.t($data.touchPoints),
|
|
||||||
f: common_vendor.sr("canvasRef", "2e633000-0"),
|
|
||||||
g: common_vendor.p({
|
|
||||||
width: $data.canvasWidth,
|
|
||||||
height: $data.canvasHeight,
|
|
||||||
matrix: $data.transformMatrix
|
|
||||||
}),
|
|
||||||
h: common_vendor.o((...args) => $options.handleTouchEvent && $options.handleTouchEvent(...args)),
|
|
||||||
i: common_vendor.o((...args) => $options.handleTouchEvent && $options.handleTouchEvent(...args)),
|
|
||||||
j: common_vendor.o((...args) => $options.handleTouchEvent && $options.handleTouchEvent(...args)),
|
|
||||||
k: common_vendor.o((...args) => $options.handleTouchEvent && $options.handleTouchEvent(...args)),
|
|
||||||
l: common_vendor.o((...args) => $options.resetCanvas && $options.resetCanvas(...args)),
|
|
||||||
m: common_vendor.o((...args) => $options.zoomIn && $options.zoomIn(...args)),
|
|
||||||
n: common_vendor.o((...args) => $options.zoomOut && $options.zoomOut(...args))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-2e633000"]]);
|
|
||||||
wx.createPage(MiniProgramPage);
|
|
||||||
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/index/gesture-canvas-page.js.map
|
|
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"navigationBarTitleText": "",
|
|
||||||
"usingComponents": {
|
|
||||||
"transform-canvas": "./transform-canvas"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
<view class="container data-v-2e633000"><view class="gesture-container data-v-2e633000" bindtouchstart="{{h}}" bindtouchmove="{{i}}" bindtouchend="{{j}}" bindtouchcancel="{{k}}"><view class="debug-info data-v-2e633000"><text class="data-v-2e633000">缩放: {{a}} | X: {{b}} | Y: {{c}}</text><text class="data-v-2e633000">手势: {{d}}</text><text class="data-v-2e633000">触点: {{e}}</text></view><transform-canvas wx:if="{{g}}" class="r data-v-2e633000" u-r="canvasRef" u-i="2e633000-0" bind:__l="__l" u-p="{{g}}"/></view><view class="controls data-v-2e633000"><button class="data-v-2e633000" bindtap="{{l}}">重置</button><button class="data-v-2e633000" bindtap="{{m}}">放大</button><button class="data-v-2e633000" bindtap="{{n}}">缩小</button></view></view>
|
|
@ -1,37 +0,0 @@
|
|||||||
|
|
||||||
.container.data-v-2e633000 {
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
.gesture-container.data-v-2e633000 {
|
|
||||||
position: relative;
|
|
||||||
width: 300px;
|
|
||||||
height: 300px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
.debug-info.data-v-2e633000 {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
background: rgba(0,0,0,0.6);
|
|
||||||
color: white;
|
|
||||||
padding: 5px;
|
|
||||||
font-size: 12px;
|
|
||||||
z-index: 10;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.controls.data-v-2e633000 {
|
|
||||||
margin-top: 20px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
button.data-v-2e633000 {
|
|
||||||
padding: 8px 16px;
|
|
||||||
background: #4a8cff;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
@ -1,156 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
const common_vendor = require("../../common/vendor.js");
|
|
||||||
class GestureHandler {
|
|
||||||
constructor(context, transformMatrix, { container }) {
|
|
||||||
this.transformMatrix = transformMatrix;
|
|
||||||
this.containerRect = container;
|
|
||||||
this.panThreshold = 5;
|
|
||||||
this.panStarted = false;
|
|
||||||
const atOptions = {
|
|
||||||
getPoint: (touch) => ({
|
|
||||||
x: touch.clientX - this.containerRect.left,
|
|
||||||
y: touch.clientY - this.containerRect.top
|
|
||||||
}),
|
|
||||||
preventDefault: false
|
|
||||||
};
|
|
||||||
if (typeof window === "undefined" || typeof HTMLElement === "undefined") {
|
|
||||||
atOptions._element = {
|
|
||||||
style: {},
|
|
||||||
addEventListener: () => {
|
|
||||||
},
|
|
||||||
removeEventListener: () => {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
this.at = new common_vendor.i(atOptions);
|
|
||||||
} catch (e) {
|
|
||||||
common_vendor.index.__f__("error", "at pages/index/gesture-handler.js:32", "AnyTouch初始化失败:", e);
|
|
||||||
this.handleGesture = this.createSimpleGestureHandler();
|
|
||||||
}
|
|
||||||
if (this.at) {
|
|
||||||
this.setupGestures();
|
|
||||||
common_vendor.index.__f__("log", "at pages/index/gesture-handler.js:39", "AnyTouch手势处理器已初始化");
|
|
||||||
} else {
|
|
||||||
common_vendor.index.__f__("warn", "at pages/index/gesture-handler.js:41", "使用简化手势处理器");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 创建小程序专用的简化手势处理器
|
|
||||||
createSimpleGestureHandler() {
|
|
||||||
return {
|
|
||||||
run: (event) => {
|
|
||||||
const touches = event.touches || [];
|
|
||||||
if (touches.length === 1) {
|
|
||||||
this._handlePan(touches[0]);
|
|
||||||
} else if (touches.length > 1) {
|
|
||||||
this._handlePinch(touches);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
setupGestures() {
|
|
||||||
this.at.on("pan", (event) => {
|
|
||||||
if (event.type === "panstart") {
|
|
||||||
common_vendor.index.__f__("log", "at pages/index/gesture-handler.js:67", "panstart", event);
|
|
||||||
this.panStarted = false;
|
|
||||||
} else if (event.type === "panmove") {
|
|
||||||
const distance = Math.sqrt(event.deltaX ** 2 + event.deltaY ** 2);
|
|
||||||
if (!this.panStarted && distance < this.panThreshold) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!this.panStarted) {
|
|
||||||
common_vendor.index.__f__("log", "at pages/index/gesture-handler.js:80", "拖动手势开始");
|
|
||||||
this.panStarted = true;
|
|
||||||
}
|
|
||||||
const currentScale = Math.sqrt(
|
|
||||||
this.transformMatrix.a * this.transformMatrix.a + this.transformMatrix.b * this.transformMatrix.b
|
|
||||||
);
|
|
||||||
const dx = event.deltaX / currentScale;
|
|
||||||
const dy = event.deltaY / currentScale;
|
|
||||||
common_vendor.index.__f__("log", "at pages/index/gesture-handler.js:95", `平移: deltaX=${event.deltaX}, deltaY=${event.deltaY} | 修正后: dx=${dx}, dy=${dy}`);
|
|
||||||
this.transformMatrix.translate(dx, dy);
|
|
||||||
} else if (event.type === "panend") {
|
|
||||||
this.panStarted = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.at.on("pinch", (event) => {
|
|
||||||
if (event.type === "pinchstart") {
|
|
||||||
this.lastScale = 1;
|
|
||||||
} else if (event.type === "pinchmove") {
|
|
||||||
const scaleChange = event.scale / this.lastScale;
|
|
||||||
this.lastScale = event.scale;
|
|
||||||
const centerX = event.center.x;
|
|
||||||
const centerY = event.center.y;
|
|
||||||
common_vendor.index.__f__("log", "at pages/index/gesture-handler.js:117", `缩放: scale=${event.scale} | 变化=${scaleChange} | 中心点: (${centerX}, ${centerY})`);
|
|
||||||
this.transformMatrix.scale(scaleChange, scaleChange, centerX, centerY);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.at.on("tap", (event) => {
|
|
||||||
common_vendor.index.__f__("log", "at pages/index/gesture-handler.js:126", "点击事件", event);
|
|
||||||
});
|
|
||||||
this.at.on("press", (event) => {
|
|
||||||
common_vendor.index.__f__("log", "at pages/index/gesture-handler.js:131", "长按事件", event);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// 公共接口保持不变
|
|
||||||
catchEvent(event) {
|
|
||||||
try {
|
|
||||||
if (this.at) {
|
|
||||||
this.at.run(event);
|
|
||||||
} else {
|
|
||||||
const touches = event.touches || [];
|
|
||||||
const eventType = event.type;
|
|
||||||
if (eventType === "touchstart") {
|
|
||||||
this.lastPoint = null;
|
|
||||||
this.lastPoints = null;
|
|
||||||
this.lastDistance = null;
|
|
||||||
}
|
|
||||||
if (touches.length === 1) {
|
|
||||||
this._handlePan(touches[0]);
|
|
||||||
} else if (touches.length > 1) {
|
|
||||||
this._handlePinch(touches);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
common_vendor.index.__f__("error", "at pages/index/gesture-handler.js:161", "手势处理错误:", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 基础平移手势处理
|
|
||||||
_handlePan(touch) {
|
|
||||||
const pointX = touch.x || touch.clientX - this.containerRect.left;
|
|
||||||
const pointY = touch.y || touch.clientY - this.containerRect.top;
|
|
||||||
if (this.lastPoint) {
|
|
||||||
const dx = pointX - this.lastPoint.x;
|
|
||||||
const dy = pointY - this.lastPoint.y;
|
|
||||||
this.transformMatrix.translate(dx, dy);
|
|
||||||
}
|
|
||||||
this.lastPoint = { x: pointX, y: pointY };
|
|
||||||
}
|
|
||||||
// 基础缩放手势处理
|
|
||||||
_handlePinch(touches) {
|
|
||||||
const point1 = touches[0];
|
|
||||||
const point2 = touches[1];
|
|
||||||
const getPoint = (touch) => ({
|
|
||||||
x: touch.x || touch.clientX - this.containerRect.left,
|
|
||||||
y: touch.y || touch.clientY - this.containerRect.top
|
|
||||||
});
|
|
||||||
const currentPoints = [getPoint(point1), getPoint(point2)];
|
|
||||||
if (this.lastPoints) {
|
|
||||||
const currentDistance = Math.sqrt(
|
|
||||||
Math.pow(currentPoints[1].x - currentPoints[0].x, 2) + Math.pow(currentPoints[1].y - currentPoints[0].y, 2)
|
|
||||||
);
|
|
||||||
const prevDistance = Math.sqrt(
|
|
||||||
Math.pow(this.lastPoints[1].x - this.lastPoints[0].x, 2) + Math.pow(this.lastPoints[1].y - this.lastPoints[0].y, 2)
|
|
||||||
);
|
|
||||||
if (prevDistance > 0 && currentDistance > 0) {
|
|
||||||
const scaleChange = currentDistance / prevDistance;
|
|
||||||
const centerX = (currentPoints[0].x + currentPoints[1].x) / 2;
|
|
||||||
const centerY = (currentPoints[0].y + currentPoints[1].y) / 2;
|
|
||||||
this.transformMatrix.scale(scaleChange, scaleChange, centerX, centerY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.lastPoints = currentPoints;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exports.GestureHandler = GestureHandler;
|
|
||||||
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/index/gesture-handler.js.map
|
|
48
unpackage/dist/dev/mp-weixin/pages/index/index.js
vendored
Normal file
48
unpackage/dist/dev/mp-weixin/pages/index/index.js
vendored
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
"use strict";
|
||||||
|
const common_vendor = require("../../common/vendor.js");
|
||||||
|
const _sfc_main = {
|
||||||
|
onReady() {
|
||||||
|
this.drawImageOnCanvas();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
drawImageOnCanvas() {
|
||||||
|
const query = common_vendor.index.createSelectorQuery().in(this);
|
||||||
|
query.select("#myCanvas").fields({ node: true, size: true }).exec(async (res) => {
|
||||||
|
if (!res[0])
|
||||||
|
return;
|
||||||
|
const canvas = res[0].node;
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
const dpr = common_vendor.index.getSystemInfoSync().pixelRatio;
|
||||||
|
this.canvas = canvas;
|
||||||
|
canvas.width = res[0].width * dpr;
|
||||||
|
canvas.height = res[0].height * dpr;
|
||||||
|
ctx.scale(dpr, dpr);
|
||||||
|
try {
|
||||||
|
const image = await this.loadImage("https://assets.tech.troyrc.com/sx25/images/background/odertupian.png");
|
||||||
|
ctx.drawImage(image, 0, 0, 300, 200);
|
||||||
|
} catch (e) {
|
||||||
|
common_vendor.index.__f__("error", "at pages/index/index.vue:40", "图片加载失败:", e);
|
||||||
|
ctx.fillStyle = "#f0f0f0";
|
||||||
|
ctx.fillRect(0, 0, 300, 200);
|
||||||
|
ctx.fillStyle = "#999";
|
||||||
|
ctx.textAlign = "center";
|
||||||
|
ctx.fillText("图片加载失败", 150, 100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
loadImage(src) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const image = this.canvas.createImage();
|
||||||
|
image.onload = () => resolve(image);
|
||||||
|
image.onerror = reject;
|
||||||
|
image.src = src;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
|
||||||
|
wx.createPage(MiniProgramPage);
|
||||||
|
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/index/index.js.map
|
4
unpackage/dist/dev/mp-weixin/pages/index/index.json
vendored
Normal file
4
unpackage/dist/dev/mp-weixin/pages/index/index.json
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"navigationBarTitleText": "",
|
||||||
|
"usingComponents": {}
|
||||||
|
}
|
1
unpackage/dist/dev/mp-weixin/pages/index/index.wxml
vendored
Normal file
1
unpackage/dist/dev/mp-weixin/pages/index/index.wxml
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
<view><canvas id="myCanvas" canvas-id="myCanvas" type="2d" style="width:300px;height:200px;border:1px solid #eee"></canvas></view>
|
@ -1,121 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
const common_vendor = require("../../common/vendor.js");
|
|
||||||
const _sfc_main = {
|
|
||||||
props: {
|
|
||||||
width: Number,
|
|
||||||
height: Number,
|
|
||||||
matrix: Object
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
ctx: null,
|
|
||||||
canvasWidth: this.width,
|
|
||||||
canvasHeight: this.height
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.initCanvas();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
initCanvas() {
|
|
||||||
this.ctx = common_vendor.index.createCanvasContext("gestureCanvas", this);
|
|
||||||
const pixelRatio = common_vendor.index.getSystemInfoSync().pixelRatio;
|
|
||||||
this.ctx.scale(pixelRatio, pixelRatio);
|
|
||||||
},
|
|
||||||
applyTransform(matrix) {
|
|
||||||
if (!matrix)
|
|
||||||
return;
|
|
||||||
const pixelRatio = common_vendor.index.getSystemInfoSync().pixelRatio;
|
|
||||||
const tx = matrix.tx * pixelRatio;
|
|
||||||
const ty = matrix.ty * pixelRatio;
|
|
||||||
this.ctx.setTransform(
|
|
||||||
matrix.a,
|
|
||||||
matrix.b,
|
|
||||||
matrix.c,
|
|
||||||
matrix.d,
|
|
||||||
tx,
|
|
||||||
ty
|
|
||||||
);
|
|
||||||
},
|
|
||||||
// 调试网格
|
|
||||||
// drawDebugGrid() {
|
|
||||||
// if (!this.ctx) return;
|
|
||||||
// const pixelRatio = uni.getSystemInfoSync().pixelRatio;
|
|
||||||
// const width = this.canvasWidth * pixelRatio;
|
|
||||||
// const height = this.canvasHeight * pixelRatio;
|
|
||||||
// // 保存当前状态
|
|
||||||
// this.ctx.save();
|
|
||||||
// // 重置变换
|
|
||||||
// this.ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
||||||
// // 绘制网格
|
|
||||||
// this.ctx.strokeStyle = 'rgba(200, 200, 200, 0.3)';
|
|
||||||
// this.ctx.lineWidth = 1;
|
|
||||||
// // 横向线
|
|
||||||
// for (let y = 0; y <= height; y += 50) {
|
|
||||||
// this.ctx.beginPath();
|
|
||||||
// this.ctx.moveTo(0, y);
|
|
||||||
// this.ctx.lineTo(width, y);
|
|
||||||
// this.ctx.stroke();
|
|
||||||
// }
|
|
||||||
// // 纵向线
|
|
||||||
// for (let x = 0; x <= width; x += 50) {
|
|
||||||
// this.ctx.beginPath();
|
|
||||||
// this.ctx.moveTo(x, 0);
|
|
||||||
// this.ctx.lineTo(x, height);
|
|
||||||
// this.ctx.stroke();
|
|
||||||
// }
|
|
||||||
// // 绘制坐标轴
|
|
||||||
// this.ctx.strokeStyle = '#ff0000';
|
|
||||||
// this.ctx.lineWidth = 2;
|
|
||||||
// this.ctx.beginPath();
|
|
||||||
// this.ctx.moveTo(0, 0);
|
|
||||||
// this.ctx.lineTo(width, 0);
|
|
||||||
// this.ctx.stroke();
|
|
||||||
// this.ctx.beginPath();
|
|
||||||
// this.ctx.moveTo(0, 0);
|
|
||||||
// this.ctx.lineTo(0, height);
|
|
||||||
// this.ctx.stroke();
|
|
||||||
// // 恢复之前的状态
|
|
||||||
// this.ctx.restore();
|
|
||||||
// this.ctx.draw();
|
|
||||||
// },
|
|
||||||
// 绘制主内容
|
|
||||||
draw() {
|
|
||||||
if (!this.ctx) {
|
|
||||||
common_vendor.index.__f__("warn", "at pages/index/transform-canvas.vue:110", "Canvas上下文未初始化");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.clearCanvas();
|
|
||||||
this.applyTransform(this.matrix);
|
|
||||||
this.drawContent();
|
|
||||||
},
|
|
||||||
clearCanvas() {
|
|
||||||
const pixelRatio = common_vendor.index.getSystemInfoSync().pixelRatio;
|
|
||||||
const width = this.canvasWidth * pixelRatio;
|
|
||||||
const height = this.canvasHeight * pixelRatio;
|
|
||||||
this.ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
||||||
this.ctx.clearRect(0, 0, width, height);
|
|
||||||
},
|
|
||||||
drawContent() {
|
|
||||||
this.ctx.fillStyle = "#3498db";
|
|
||||||
this.ctx.fillRect(50, 50, 100, 100);
|
|
||||||
this.ctx.fillStyle = "#e74c3c";
|
|
||||||
this.ctx.beginPath();
|
|
||||||
this.ctx.arc(150, 150, 50, 0, 2 * Math.PI);
|
|
||||||
this.ctx.fill();
|
|
||||||
this.ctx.fillStyle = "#2c3e50";
|
|
||||||
this.ctx.font = "20px sans-serif";
|
|
||||||
this.ctx.fillText("手势Canvas", 50, 250);
|
|
||||||
this.ctx.draw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
||||||
return {
|
|
||||||
a: $data.canvasWidth + "px",
|
|
||||||
b: $data.canvasHeight + "px"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const Component = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
|
|
||||||
wx.createComponent(Component);
|
|
||||||
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/index/transform-canvas.js.map
|
|
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"component": true,
|
|
||||||
"usingComponents": {}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
<canvas canvas-id="gestureCanvas" style="{{'width:' + a + ';' + ('height:' + b)}}"/>
|
|
@ -1,47 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
class TransformMatrix {
|
|
||||||
constructor() {
|
|
||||||
this.reset();
|
|
||||||
}
|
|
||||||
reset() {
|
|
||||||
this.a = 1;
|
|
||||||
this.b = 0;
|
|
||||||
this.c = 0;
|
|
||||||
this.d = 1;
|
|
||||||
this.tx = 0;
|
|
||||||
this.ty = 0;
|
|
||||||
this.stack = [];
|
|
||||||
}
|
|
||||||
translate(dx, dy) {
|
|
||||||
this.tx += this.a * dx + this.c * dy;
|
|
||||||
this.ty += this.b * dx + this.d * dy;
|
|
||||||
}
|
|
||||||
scale(sx, sy, cx = 0, cy = 0) {
|
|
||||||
this.translate(cx, cy);
|
|
||||||
this.a *= sx;
|
|
||||||
this.b *= sx;
|
|
||||||
this.c *= sy;
|
|
||||||
this.d *= sy;
|
|
||||||
this.translate(-cx, -cy);
|
|
||||||
}
|
|
||||||
toArray() {
|
|
||||||
return [this.a, this.b, this.c, this.d, this.tx, this.ty];
|
|
||||||
}
|
|
||||||
// 用于调试的字符串表示
|
|
||||||
toString() {
|
|
||||||
return `[${this.a.toFixed(2)}, ${this.b.toFixed(2)}, ${this.c.toFixed(2)}, ${this.d.toFixed(2)}, ${this.tx.toFixed(1)}, ${this.ty.toFixed(1)}]`;
|
|
||||||
}
|
|
||||||
// 克隆方法
|
|
||||||
clone() {
|
|
||||||
const clone = new TransformMatrix();
|
|
||||||
clone.a = this.a;
|
|
||||||
clone.b = this.b;
|
|
||||||
clone.c = this.c;
|
|
||||||
clone.d = this.d;
|
|
||||||
clone.tx = this.tx;
|
|
||||||
clone.ty = this.ty;
|
|
||||||
return clone;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exports.TransformMatrix = TransformMatrix;
|
|
||||||
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/index/transform-matrix.js.map
|
|
Loading…
x
Reference in New Issue
Block a user