add 增加手势js 和 变换矩阵js 融合 canvas测试处理

This commit is contained in:
sunmeng 2025-07-22 18:10:42 +08:00
parent 0283e1b651
commit 0c82be5f58
56 changed files with 2362 additions and 322 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View File

@ -1,7 +1,7 @@
{
"pages": [ //pageshttps://uniapp.dcloud.io/collocation/pages
{
"path" : "pages/index/hammerjsTest/hammerjsTest",
"path" : "pages/index/gesture-canvas-page",
"style" :
{
"navigationBarTitleText" : ""

View File

@ -0,0 +1,286 @@
<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>
<button @click="logMatrix">调试</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) => {
//
const touches = event.touches || [];
if (touches.length > 0) {
const point = touches[0];
const x = point.clientX - this.containerRect.left;
const y = point.clientY - this.containerRect.top;
//
this.transformMatrix.tx = x - 50;
this.transformMatrix.ty = y - 50;
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) {
// 1
if (!this.gestureHandler || !this.containerRect) {
console.warn('手势处理器未就绪,尝试重新初始化...');
await this.getContainerPosition();
this.initGestureHandler();
//
if (this.gestureHandler) {
return this.handleTouchEvent(event);
}
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();
},
//
logMatrix() {
console.log('当前变换矩阵:', this.transformMatrix.toArray());
console.log('容器位置:', this.containerRect);
if (this.$refs.canvasRef) {
this.$refs.canvasRef.drawDebugGrid();
}
}
}
};
</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>

View File

@ -0,0 +1,191 @@
import AnyTouch from 'any-touch'
class GestureHandler {
constructor(context, transformMatrix, { container }) {
this.transformMatrix = transformMatrix;
this.containerRect = container;
// 小程序环境兼容处理
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);
}
else if (event.type === 'panmove') {
// 关键修复:基于当前缩放比例修正移动量
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);
}
});
// 缩放手势 - 修复版本
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 if (this.handleGesture) {
this.handleGesture.run(event);
}
} catch (e) {
console.error('手势处理错误:', e);
// 降级处理
const touches = event.touches || [];
if (touches.length > 0) {
this.handlePan(touches[0]);
}
}
}
// 基础平移手势处理
handlePan(touch) {
if (this.lastPoint) {
const dx = touch.clientX - this.lastPoint.x;
const dy = touch.clientY - this.lastPoint.y;
// 根据当前缩放比例修正移动量
const currentScale = Math.sqrt(
this.transformMatrix.a * this.transformMatrix.a +
this.transformMatrix.b * this.transformMatrix.b
);
const correctedDx = dx / currentScale;
const correctedDy = dy / currentScale;
this.transformMatrix.translate(correctedDx, correctedDy);
}
this.lastPoint = {x: touch.clientX, y: touch.clientY};
}
// 基础缩放手势处理
handlePinch(touches) {
const point1 = touches[0];
const point2 = touches[1];
if (this.lastPoints) {
// 计算当前距离
const currentDistance = Math.sqrt(
Math.pow(point2.clientX - point1.clientX, 2) +
Math.pow(point2.clientY - point1.clientY, 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) {
const scaleChange = currentDistance / prevDistance;
// 计算中心点
const centerX = (point1.clientX + point2.clientX) / 2 - this.containerRect.left;
const centerY = (point1.clientY + point2.clientY) / 2 - this.containerRect.top;
this.transformMatrix.scale(scaleChange, scaleChange, centerX, centerY);
}
}
this.lastPoints = [
{x: point1.clientX, y: point1.clientY},
{x: point2.clientX, y: point2.clientY}
];
}
}
export default GestureHandler;

View File

@ -1,69 +0,0 @@
<template>
<any-touch
:pan-options="{ threshold: 10 }"
:pinch-options="{ threshold: 0.1 }"
@tap="handleTap"
@pan="handlePan"
@swipe="handleSwipe"
@pinch="handlePinch"
@press="handlePress"
@rotate="handleRotate"
>
<view class="gesture-box">手势识别区域</view>
</any-touch>
</template>
<script>
export default {
methods: {
//
handleTap(e) {
console.log('Tap:', e)
},
//
handlePan(e) {
const { displacement, deltaX, deltaY } = e
console.log(`Pan: X=${deltaX}, Y=${deltaY}, 位移=${displacement}px`)
},
//
handleSwipe(e) {
const { direction } = e
const dirMap = {
left: '向左',
right: '向右',
up: '向上',
down: '向下'
}
console.log(`Swipe: ${dirMap[direction]}`)
},
//
handlePinch(e) {
console.log(`缩放比例: ${e.scale.toFixed(2)}`)
},
//
handlePress(e) {
console.log(`长按 ${e.duration}ms`)
},
//
handleRotate(e) {
console.log(`旋转角度: ${e.angle.toFixed(1)}°`)
}
}
}
</script>
<style>
.gesture-box {
width: 300px;
height: 300px;
background-color: #f0f9ff;
border: 1px solid #409eff;
display: flex;
justify-content: center;
align-items: center;
margin: 20px auto;
}
</style>

View File

@ -0,0 +1,154 @@
<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>

View File

@ -0,0 +1,57 @@
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;

11
unpackage/dist/dev/.nvue/app.css.js vendored Normal file
View File

@ -0,0 +1,11 @@
var __getOwnPropNames = Object.getOwnPropertyNames;
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var require_app_css = __commonJS({
"app.css.js"(exports) {
const _style_0 = {};
exports.styles = [_style_0];
}
});
export default require_app_css();

2
unpackage/dist/dev/.nvue/app.js vendored Normal file
View File

@ -0,0 +1,2 @@
Promise.resolve("./app.css.js").then(() => {
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"version":3,"file":"GestureRecognizer.js","sources":["pages/index/GestureRecognizer.js"],"sourcesContent":["import MiniProgramAdapter from './MiniProgramAdapter';\n// 独立的手势库组件\nexport default class GestureRecognizer {\n constructor() {\n this.adapter = new MiniProgramAdapter();\n this.touches = [];\n this.gestures = {};\n this.activeGestures = {};\n this.containerRect = null;\n this.callbacks = {};\n }\n\n initContainer(rect) {\n this.containerRect = rect;\n }\n\n // 注册手势回调\n on(event, callback) {\n\t\tif (!this.callbacks[event]) {\n\t\t\tthis.callbacks[event] = [];\n\t\t}\n\t\tthis.callbacks[event].push(callback);\n\t}\n\t\n\t trigger(event, data) {\n\t const handlers = this.callbacks[event] || [];\n\t handlers.forEach(handler => {\n\t handler({\n\t type: event,\n\t originEvent: this.currentEvent,\n\t containerRect: this.containerRect,\n\t ...data\n\t });\n\t });\n\t }\n\t\t\n // 处理触摸事件\n handleTouchEvent(type, event) {\n\t\t// 更新触点信息\n\t\tthis.updateTouches(event);\n\t\t\n\t\t// 根据类型处理\n\t\tswitch(type) {\n\t\t\tcase 'touchstart':\n\t\t\t\treturn this.handleTouchStart(event);\n\t\t\tcase 'touchmove':\n\t\t\t\treturn this.handleTouchMove(event);\n\t\t\tdefault:\n\t\t\t\treturn this.handleTouchEnd(event);\n\t\t}\n\t}\n\n\t// 处理缩放手势\n\t detectPinchMove() {\n\t if (!this.activeGestures.pinch) return;\n\t \n\t const currentDistance = this.calculateFingerDistance();\n\t const deltaDistance = currentDistance - this.activeGestures.pinch.lastDistance;\n\t \n\t // 计算缩放比例\n\t const scaleFactor = currentDistance / this.activeGestures.pinch.startDistance;\n\t \n\t // 触发事件\n\t this.triggerGesture('pinch', {\n\t scale: scaleFactor,\n\t center: this.calculateCenter()\n\t });\n\t\t\t\n\t \n\t // 保存最后距离\n\t this.activeGestures.pinch.lastDistance = currentDistance;\n\t }\n\n // 触发手势事件\n triggerGesture(name, data) {\n const handlers = this.callbacks[name] || [];\n handlers.forEach(handler => {\n handler({\n type: name,\n originEvent: this.currentEvent,\n containerRect: this.containerRect,\n ...data\n });\n });\n }\n }\t\n"],"names":["MiniProgramAdapter"],"mappings":";;AAEe,MAAM,kBAAkB;AAAA,EACrC,cAAc;AACZ,SAAK,UAAU,IAAIA,+BAAAA;AACnB,SAAK,UAAU;AACf,SAAK,WAAW;AAChB,SAAK,iBAAiB;AACtB,SAAK,gBAAgB;AACrB,SAAK,YAAY;EAClB;AAAA,EAED,cAAc,MAAM;AAClB,SAAK,gBAAgB;AAAA,EACtB;AAAA;AAAA,EAGD,GAAG,OAAO,UAAU;AACpB,QAAI,CAAC,KAAK,UAAU,KAAK,GAAG;AAC3B,WAAK,UAAU,KAAK,IAAI;IACxB;AACD,SAAK,UAAU,KAAK,EAAE,KAAK,QAAQ;AAAA,EACnC;AAAA,EAEA,QAAQ,OAAO,MAAM;AAClB,UAAM,WAAW,KAAK,UAAU,KAAK,KAAK,CAAA;AAC1C,aAAS,QAAQ,aAAW;AAC1B,cAAQ;AAAA,QACN,MAAM;AAAA,QACN,aAAa,KAAK;AAAA,QAClB,eAAe,KAAK;AAAA,QACpB,GAAG;AAAA,MACZ,CAAQ;AAAA,IACR,CAAM;AAAA,EACF;AAAA;AAAA,EAGF,iBAAiB,MAAM,OAAO;AAE9B,SAAK,cAAc,KAAK;AAGxB,YAAO,MAAI;AAAA,MACV,KAAK;AACJ,eAAO,KAAK,iBAAiB,KAAK;AAAA,MACnC,KAAK;AACJ,eAAO,KAAK,gBAAgB,KAAK;AAAA,MAClC;AACC,eAAO,KAAK,eAAe,KAAK;AAAA,IACjC;AAAA,EACD;AAAA;AAAA,EAGC,kBAAkB;AAChB,QAAI,CAAC,KAAK,eAAe;AAAO;AAEhC,UAAM,kBAAkB,KAAK;AACP,sBAAkB,KAAK,eAAe,MAAM;AAGlE,UAAM,cAAc,kBAAkB,KAAK,eAAe,MAAM;AAGhE,SAAK,eAAe,SAAS;AAAA,MAC3B,OAAO;AAAA,MACP,QAAQ,KAAK,gBAAiB;AAAA,IACrC,CAAM;AAID,SAAK,eAAe,MAAM,eAAe;AAAA,EAC1C;AAAA;AAAA,EAGF,eAAe,MAAM,MAAM;AACzB,UAAM,WAAW,KAAK,UAAU,IAAI,KAAK,CAAA;AACzC,aAAS,QAAQ,aAAW;AAC1B,cAAQ;AAAA,QACN,MAAM;AAAA,QACN,aAAa,KAAK;AAAA,QAClB,eAAe,KAAK;AAAA,QACpB,GAAG;AAAA,MACX,CAAO;AAAA,IACP,CAAK;AAAA,EACF;AACH;;"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"version":3,"file":"canvasRenderer.js","sources":["pages/index/canvasRenderer.vue?type=page"],"sourcesContent":["import MiniProgramPage from '/Users/sunmeng/Desktop/wx/canvas/pages/index/canvasRenderer.vue'\nwx.createPage(MiniProgramPage)"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,GAAG,WAAW,eAAe;"}

View File

@ -0,0 +1 @@
{"version":3,"file":"gesture-canvas-page.js","sources":["pages/index/gesture-canvas-page.vue?type=page"],"sourcesContent":["import MiniProgramPage from '/Users/sunmeng/Desktop/wx/canvas/pages/index/gesture-canvas-page.vue'\nwx.createPage(MiniProgramPage)"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,GAAG,WAAW,eAAe;"}

File diff suppressed because one or more lines are too long

View File

@ -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":["MiniProgramPage"],"mappings":";;AACA,GAAG,WAAWA,MAAe,eAAA;"}
{"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;"}

View File

@ -0,0 +1 @@
{"version":3,"file":"transform-canvas.js","sources":["/Users/sunmeng/Desktop/wx/canvas/pages/index/transform-canvas.vue?type=component"],"sourcesContent":["import Component from '/Users/sunmeng/Desktop/wx/canvas/pages/index/transform-canvas.vue'\nwx.createComponent(Component)"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,GAAG,gBAAgB,SAAS;"}

View File

@ -0,0 +1 @@
{"version":3,"file":"transform-matrix.js","sources":["pages/index/transform-matrix.js"],"sourcesContent":["class TransformMatrix {\n constructor() {\n this.reset();\n }\n \n reset() {\n this.a = 1; // 水平缩放\n this.b = 0; // 垂直倾斜\n this.c = 0; // 水平倾斜\n this.d = 1; // 垂直缩放\n this.tx = 0; // 水平移动\n this.ty = 0; // 垂直移动\n this.stack = [];\n }\n \n translate(dx, dy) {\n this.tx += this.a * dx + this.c * dy;\n this.ty += this.b * dx + this.d * dy;\n }\n \n scale(sx, sy, cx = 0, cy = 0) {\n // 移动到中心点\n this.translate(cx, cy);\n \n // 应用缩放\n this.a *= sx;\n this.b *= sx;\n this.c *= sy;\n this.d *= sy;\n \n // 移回原位置\n this.translate(-cx, -cy);\n }\n \n toArray() {\n return [this.a, this.b, this.c, this.d, this.tx, this.ty];\n }\n \n // 用于调试的字符串表示\n toString() {\n 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)}]`;\n }\n \n // 克隆方法\n clone() {\n const clone = new TransformMatrix();\n clone.a = this.a;\n clone.b = this.b;\n clone.c = this.c;\n clone.d = this.d;\n clone.tx = this.tx;\n clone.ty = this.ty;\n return clone;\n }\n}\n\nexport default TransformMatrix;"],"names":[],"mappings":";AAAA,MAAM,gBAAgB;AAAA,EACpB,cAAc;AACZ,SAAK,MAAK;AAAA,EACX;AAAA,EAED,QAAQ;AACN,SAAK,IAAI;AACT,SAAK,IAAI;AACT,SAAK,IAAI;AACT,SAAK,IAAI;AACT,SAAK,KAAK;AACV,SAAK,KAAK;AACV,SAAK,QAAQ;EACd;AAAA,EAED,UAAU,IAAI,IAAI;AAChB,SAAK,MAAM,KAAK,IAAI,KAAK,KAAK,IAAI;AAClC,SAAK,MAAM,KAAK,IAAI,KAAK,KAAK,IAAI;AAAA,EACnC;AAAA,EAED,MAAM,IAAI,IAAI,KAAK,GAAG,KAAK,GAAG;AAE5B,SAAK,UAAU,IAAI,EAAE;AAGrB,SAAK,KAAK;AACV,SAAK,KAAK;AACV,SAAK,KAAK;AACV,SAAK,KAAK;AAGV,SAAK,UAAU,CAAC,IAAI,CAAC,EAAE;AAAA,EACxB;AAAA,EAED,UAAU;AACR,WAAO,CAAC,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,IAAI,KAAK,EAAE;AAAA,EACzD;AAAA;AAAA,EAGD,WAAW;AACT,WAAO,IAAI,KAAK,EAAE,QAAQ,CAAC,CAAC,KAAK,KAAK,EAAE,QAAQ,CAAC,CAAC,KAAK,KAAK,EAAE,QAAQ,CAAC,CAAC,KAAK,KAAK,EAAE,QAAQ,CAAC,CAAC,KAAK,KAAK,GAAG,QAAQ,CAAC,CAAC,KAAK,KAAK,GAAG,QAAQ,CAAC,CAAC;AAAA,EAC7I;AAAA;AAAA,EAGD,QAAQ;AACN,UAAM,QAAQ,IAAI;AAClB,UAAM,IAAI,KAAK;AACf,UAAM,IAAI,KAAK;AACf,UAAM,IAAI,KAAK;AACf,UAAM,IAAI,KAAK;AACf,UAAM,KAAK,KAAK;AAChB,UAAM,KAAK,KAAK;AAChB,WAAO;AAAA,EACR;AACH;;"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>View</title>
<link rel="icon" href="data:,">
<link rel="stylesheet" href="app.css" />
<script>var __uniConfig = {"globalStyle":{},"darkmode":false}</script>
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
</head>
<body>
<div id="app"></div>
<script src="uni-app-view.umd.js"></script>
</body>
</html>

View File

@ -0,0 +1,11 @@
;(function(){
let u=void 0,isReady=false,onReadyCallbacks=[],isServiceReady=false,onServiceReadyCallbacks=[];
const __uniConfig = {"pages":[],"globalStyle":{"backgroundColor":"#F8F8F8","navigationBar":{"backgroundColor":"#F8F8F8","titleText":"uni-app","type":"default","titleColor":"#000000"},"isNVue":false},"nvue":{"compiler":"uni-app","styleCompiler":"uni-app","flex-direction":"column"},"renderer":"auto","appname":"canvas","splashscreen":{"alwaysShowBeforeRender":true,"autoclose":true},"compilerVersion":"4.57","entryPagePath":"pages/index/hammerjsTest/hammerjsTest","entryPageQuery":"","realEntryPagePath":"","networkTimeout":{"request":60000,"connectSocket":60000,"uploadFile":60000,"downloadFile":60000},"locales":{},"darkmode":false,"themeConfig":{}};
const __uniRoutes = [{"path":"pages/index/hammerjsTest/hammerjsTest","meta":{"isQuit":true,"isEntry":true,"navigationBar":{"titleText":"","type":"default"},"isNVue":false}}].map(uniRoute=>(uniRoute.meta.route=uniRoute.path,__uniConfig.pages.push(uniRoute.path),uniRoute.path='/'+uniRoute.path,uniRoute));
__uniConfig.styles=[];//styles
__uniConfig.onReady=function(callback){if(__uniConfig.ready){callback()}else{onReadyCallbacks.push(callback)}};Object.defineProperty(__uniConfig,"ready",{get:function(){return isReady},set:function(val){isReady=val;if(!isReady){return}const callbacks=onReadyCallbacks.slice(0);onReadyCallbacks.length=0;callbacks.forEach(function(callback){callback()})}});
__uniConfig.onServiceReady=function(callback){if(__uniConfig.serviceReady){callback()}else{onServiceReadyCallbacks.push(callback)}};Object.defineProperty(__uniConfig,"serviceReady",{get:function(){return isServiceReady},set:function(val){isServiceReady=val;if(!isServiceReady){return}const callbacks=onServiceReadyCallbacks.slice(0);onServiceReadyCallbacks.length=0;callbacks.forEach(function(callback){callback()})}});
service.register("uni-app-config",{create(a,b,c){if(!__uniConfig.viewport){var d=b.weex.config.env.scale,e=b.weex.config.env.deviceWidth,f=Math.ceil(e/d);Object.assign(__uniConfig,{viewport:f,defaultFontSize:16})}return{instance:{__uniConfig:__uniConfig,__uniRoutes:__uniRoutes,global:u,window:u,document:u,frames:u,self:u,location:u,navigator:u,localStorage:u,history:u,Caches:u,screen:u,alert:u,confirm:u,prompt:u,fetch:u,XMLHttpRequest:u,WebSocket:u,webkit:u,print:u}}}});
})();

View File

@ -0,0 +1 @@
(function(){})();

View File

@ -0,0 +1,131 @@
if (typeof Promise !== "undefined" && !Promise.prototype.finally) {
Promise.prototype.finally = function(callback) {
const promise = this.constructor;
return this.then(
(value) => promise.resolve(callback()).then(() => value),
(reason) => promise.resolve(callback()).then(() => {
throw reason;
})
);
};
}
;
if (typeof uni !== "undefined" && uni && uni.requireGlobal) {
const global = uni.requireGlobal();
ArrayBuffer = global.ArrayBuffer;
Int8Array = global.Int8Array;
Uint8Array = global.Uint8Array;
Uint8ClampedArray = global.Uint8ClampedArray;
Int16Array = global.Int16Array;
Uint16Array = global.Uint16Array;
Int32Array = global.Int32Array;
Uint32Array = global.Uint32Array;
Float32Array = global.Float32Array;
Float64Array = global.Float64Array;
BigInt64Array = global.BigInt64Array;
BigUint64Array = global.BigUint64Array;
}
;
if (uni.restoreGlobal) {
uni.restoreGlobal(Vue, weex, plus, setTimeout, clearTimeout, setInterval, clearInterval);
}
(function(vue) {
"use strict";
function formatAppLog(type, filename, ...args) {
if (uni.__log__) {
uni.__log__(type, filename, ...args);
} else {
console[type].apply(console, [...args, filename]);
}
}
const _export_sfc = (sfc, props) => {
const target = sfc.__vccOpts || sfc;
for (const [key, val] of props) {
target[key] = val;
}
return target;
};
const _sfc_main$1 = {
methods: {
// 基本点击
handleTap(e) {
formatAppLog("log", "at pages/index/hammerjsTest/hammerjsTest.vue:20", "Tap:", e);
},
// 平移拖拽
handlePan(e) {
const { displacement, deltaX, deltaY } = e;
formatAppLog("log", "at pages/index/hammerjsTest/hammerjsTest.vue:26", `Pan: X=${deltaX}, Y=${deltaY}, 位移=${displacement}px`);
},
// 快速滑动
handleSwipe(e) {
const { direction } = e;
const dirMap = {
left: "向左",
right: "向右",
up: "向上",
down: "向下"
};
formatAppLog("log", "at pages/index/hammerjsTest/hammerjsTest.vue:38", `Swipe: ${dirMap[direction]}`);
},
// 缩放
handlePinch(e) {
formatAppLog("log", "at pages/index/hammerjsTest/hammerjsTest.vue:43", `缩放比例: ${e.scale.toFixed(2)}`);
},
// 长按
handlePress(e) {
formatAppLog("log", "at pages/index/hammerjsTest/hammerjsTest.vue:48", `长按 ${e.duration}ms`);
},
// 旋转
handleRotate(e) {
formatAppLog("log", "at pages/index/hammerjsTest/hammerjsTest.vue:53", `旋转角度: ${e.angle.toFixed(1)}°`);
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
const _component_any_touch = vue.resolveComponent("any-touch");
return vue.openBlock(), vue.createBlock(_component_any_touch, {
"pan-options": { threshold: 10 },
"pinch-options": { threshold: 0.1 },
onClick: $options.handleTap,
onPan: $options.handlePan,
onSwipe: $options.handleSwipe,
onPinch: $options.handlePinch,
onPress: $options.handlePress,
onRotate: $options.handleRotate
}, {
default: vue.withCtx(() => [
vue.createElementVNode("view", { class: "gesture-box" }, "手势识别区域")
]),
_: 1
/* STABLE */
}, 8, ["onClick", "onPan", "onSwipe", "onPinch", "onPress", "onRotate"]);
}
const PagesIndexHammerjsTestHammerjsTest = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["render", _sfc_render], ["__file", "/Users/sunmeng/Desktop/wx/canvas/pages/index/hammerjsTest/hammerjsTest.vue"]]);
__definePage("pages/index/hammerjsTest/hammerjsTest", PagesIndexHammerjsTestHammerjsTest);
const _sfc_main = {
onLaunch: function() {
formatAppLog("log", "at App.vue:4", "App Launch");
},
onShow: function() {
formatAppLog("log", "at App.vue:7", "App Show");
},
onHide: function() {
formatAppLog("log", "at App.vue:10", "App Hide");
}
};
const App = /* @__PURE__ */ _export_sfc(_sfc_main, [["__file", "/Users/sunmeng/Desktop/wx/canvas/App.vue"]]);
function createApp() {
const app = vue.createVueApp(App);
return {
app
};
}
const { app: __app__, Vuex: __Vuex__, Pinia: __Pinia__ } = createApp();
uni.Vuex = __Vuex__;
uni.Pinia = __Pinia__;
__app__.provide("__globalStyles", __uniConfig.styles);
__app__._component.mpType = "app";
__app__._component.render = () => {
};
__app__.mount("#app");
})(Vue);

4
unpackage/dist/dev/app-plus/app.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,107 @@
{
"@platforms": [
"android",
"iPhone",
"iPad"
],
"id": "__UNI__EBE2302",
"name": "canvas",
"version": {
"name": "1.0.0",
"code": "100"
},
"description": "",
"developer": {
"name": "",
"email": "",
"url": ""
},
"permissions": {
"UniNView": {
"description": "UniNView原生渲染"
}
},
"plus": {
"useragent": {
"value": "uni-app",
"concatenate": true
},
"splashscreen": {
"target": "id:1",
"autoclose": true,
"waiting": true,
"delay": 0
},
"popGesture": "close",
"launchwebview": {
"render": "always",
"id": "1",
"kernel": "WKWebview"
},
"usingComponents": true,
"nvueStyleCompiler": "uni-app",
"compilerVersion": 3,
"distribute": {
"google": {
"permissions": [
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
]
},
"apple": {},
"plugins": {
"audio": {
"mp3": {
"description": "Android平台录音支持MP3格式文件"
}
}
}
},
"statusbar": {
"immersed": "supportedDevice",
"style": "dark",
"background": "#F8F8F8"
},
"uniStatistics": {
"enable": false
},
"allowsInlineMediaPlayback": true,
"uni-app": {
"control": "uni-v3",
"vueVersion": "3",
"compilerVersion": "4.57",
"nvueCompiler": "uni-app",
"renderer": "auto",
"nvue": {
"flex-direction": "column"
},
"nvueLaunchMode": "normal",
"webView": {
"minUserAgentVersion": "49.0"
}
}
},
"app-harmony": {
"useragent": {
"value": "uni-app",
"concatenate": true
},
"uniStatistics": {
"enable": false
}
},
"launch_path": "__uniappview.html"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -2,7 +2,7 @@
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const common_vendor = require("./common/vendor.js");
if (!Math) {
"./pages/index/hammerjsTest/hammerjsTest.js";
"./pages/index/gesture-canvas-page.js";
}
const _sfc_main = {
onLaunch: function() {

View File

@ -1,6 +1,6 @@
{
"pages": [
"pages/index/hammerjsTest/hammerjsTest"
"pages/index/gesture-canvas-page"
],
"window": {
"navigationBarTextStyle": "black",

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,192 @@
"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:77", "手势处理器初始化失败:", e);
this.gestureHandler = {
catchEvent: (event) => {
const touches = event.touches || [];
if (touches.length > 0) {
const point = touches[0];
const x = point.clientX - this.containerRect.left;
const y = point.clientY - this.containerRect.top;
this.transformMatrix.tx = x - 50;
this.transformMatrix.ty = y - 50;
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:119", "成功获取容器位置:", this.containerRect);
return;
}
} catch (e) {
common_vendor.index.__f__("error", "at pages/index/gesture-canvas-page.vue:123", "获取容器位置失败:", e);
}
await new Promise((r) => setTimeout(r, 100));
retryCount++;
}
common_vendor.index.__f__("error", "at pages/index/gesture-canvas-page.vue:131", `容器位置获取失败,已重试${maxRetries}`);
},
// 事件处理(完整修复)
async handleTouchEvent(event) {
if (!this.gestureHandler || !this.containerRect) {
common_vendor.index.__f__("warn", "at pages/index/gesture-canvas-page.vue:137", "手势处理器未就绪,尝试重新初始化...");
await this.getContainerPosition();
this.initGestureHandler();
if (this.gestureHandler) {
return this.handleTouchEvent(event);
}
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:160", `原始坐标: (${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();
},
// 调试输出
logMatrix() {
common_vendor.index.__f__("log", "at pages/index/gesture-canvas-page.vue:235", "当前变换矩阵:", this.transformMatrix.toArray());
common_vendor.index.__f__("log", "at pages/index/gesture-canvas-page.vue:236", "容器位置:", this.containerRect);
if (this.$refs.canvasRef) {
this.$refs.canvasRef.drawDebugGrid();
}
}
}
};
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)),
o: common_vendor.o((...args) => $options.logMatrix && $options.logMatrix(...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

View File

@ -0,0 +1,6 @@
{
"navigationBarTitleText": "",
"usingComponents": {
"transform-canvas": "./transform-canvas"
}
}

View File

@ -0,0 +1 @@
<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><button class="data-v-2e633000" bindtap="{{o}}">调试</button></view></view>

View File

@ -0,0 +1,37 @@
.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;
}

View File

@ -0,0 +1,136 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
class GestureHandler {
constructor(context, transformMatrix, { container }) {
this.transformMatrix = transformMatrix;
this.containerRect = container;
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:30", "AnyTouch初始化失败:", e);
this.handleGesture = this.createSimpleGestureHandler();
}
if (this.at) {
this.setupGestures();
common_vendor.index.__f__("log", "at pages/index/gesture-handler.js:37", "AnyTouch手势处理器已初始化");
} else {
common_vendor.index.__f__("warn", "at pages/index/gesture-handler.js:39", "使用简化手势处理器");
}
}
// 创建小程序专用的简化手势处理器
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:65", "panstart", event);
} else if (event.type === "panmove") {
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:78", `平移: deltaX=${event.deltaX}, deltaY=${event.deltaY} | 修正后: dx=${dx}, dy=${dy}`);
this.transformMatrix.translate(dx, dy);
}
});
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:98", `缩放: 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:107", "点击事件", event);
});
this.at.on("press", (event) => {
common_vendor.index.__f__("log", "at pages/index/gesture-handler.js:112", "长按事件", event);
});
}
catchEvent(event) {
try {
if (this.at) {
this.at.run(event);
} else if (this.handleGesture) {
this.handleGesture.run(event);
}
} catch (e) {
common_vendor.index.__f__("error", "at pages/index/gesture-handler.js:125", "手势处理错误:", e);
const touches = event.touches || [];
if (touches.length > 0) {
this.handlePan(touches[0]);
}
}
}
// 基础平移手势处理
handlePan(touch) {
if (this.lastPoint) {
const dx = touch.clientX - this.lastPoint.x;
const dy = touch.clientY - this.lastPoint.y;
const currentScale = Math.sqrt(
this.transformMatrix.a * this.transformMatrix.a + this.transformMatrix.b * this.transformMatrix.b
);
const correctedDx = dx / currentScale;
const correctedDy = dy / currentScale;
this.transformMatrix.translate(correctedDx, correctedDy);
}
this.lastPoint = { x: touch.clientX, y: touch.clientY };
}
// 基础缩放手势处理
handlePinch(touches) {
const point1 = touches[0];
const point2 = touches[1];
if (this.lastPoints) {
const currentDistance = Math.sqrt(
Math.pow(point2.clientX - point1.clientX, 2) + Math.pow(point2.clientY - point1.clientY, 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) {
const scaleChange = currentDistance / prevDistance;
const centerX = (point1.clientX + point2.clientX) / 2 - this.containerRect.left;
const centerY = (point1.clientY + point2.clientY) / 2 - this.containerRect.top;
this.transformMatrix.scale(scaleChange, scaleChange, centerX, centerY);
}
}
this.lastPoints = [
{ x: point1.clientX, y: point1.clientY },
{ x: point2.clientX, y: point2.clientY }
];
}
}
exports.GestureHandler = GestureHandler;
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/index/gesture-handler.js.map

View File

@ -1,63 +0,0 @@
"use strict";
const common_vendor = require("../../../common/vendor.js");
const _sfc_main = {
methods: {
// 基本点击
handleTap(e) {
common_vendor.index.__f__("log", "at pages/index/hammerjsTest/hammerjsTest.vue:20", "Tap:", e);
},
// 平移拖拽
handlePan(e) {
const { displacement, deltaX, deltaY } = e;
common_vendor.index.__f__("log", "at pages/index/hammerjsTest/hammerjsTest.vue:26", `Pan: X=${deltaX}, Y=${deltaY}, 位移=${displacement}px`);
},
// 快速滑动
handleSwipe(e) {
const { direction } = e;
const dirMap = {
left: "向左",
right: "向右",
up: "向上",
down: "向下"
};
common_vendor.index.__f__("log", "at pages/index/hammerjsTest/hammerjsTest.vue:38", `Swipe: ${dirMap[direction]}`);
},
// 缩放
handlePinch(e) {
common_vendor.index.__f__("log", "at pages/index/hammerjsTest/hammerjsTest.vue:43", `缩放比例: ${e.scale.toFixed(2)}`);
},
// 长按
handlePress(e) {
common_vendor.index.__f__("log", "at pages/index/hammerjsTest/hammerjsTest.vue:48", `长按 ${e.duration}ms`);
},
// 旋转
handleRotate(e) {
common_vendor.index.__f__("log", "at pages/index/hammerjsTest/hammerjsTest.vue:53", `旋转角度: ${e.angle.toFixed(1)}°`);
}
}
};
if (!Array) {
const _component_any_touch = common_vendor.resolveComponent("any-touch");
_component_any_touch();
}
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {
a: common_vendor.o($options.handleTap),
b: common_vendor.o($options.handlePan),
c: common_vendor.o($options.handleSwipe),
d: common_vendor.o($options.handlePinch),
e: common_vendor.o($options.handlePress),
f: common_vendor.o($options.handleRotate),
g: common_vendor.p({
["pan-options"]: {
threshold: 10
},
["pinch-options"]: {
threshold: 0.1
}
})
};
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../../.sourcemap/mp-weixin/pages/index/hammerjsTest/hammerjsTest.js.map

View File

@ -1,4 +0,0 @@
{
"navigationBarTitleText": "",
"usingComponents": {}
}

View File

@ -1 +0,0 @@
<any-touch wx:if="{{g}}" u-s="{{['d']}}" bindtap="{{a}}" bindpan="{{b}}" bindswipe="{{c}}" bindpinch="{{d}}" bindpress="{{e}}" bindrotate="{{f}}" u-i="2f899711-0" bind:__l="__l" u-p="{{g}}"><view class="gesture-box">手势识别区域</view></any-touch>

View File

@ -0,0 +1,115 @@
"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 = common_vendor.index.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

View File

@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

View File

@ -0,0 +1 @@
<canvas canvas-id="gestureCanvas" style="{{'width:' + a + ';' + ('height:' + b)}}"/>

View File

@ -0,0 +1,47 @@
"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

View File

@ -8,11 +8,18 @@
"miniprogram": {
"list": [
{
"name": "pages/index/hammerjsTest/hammerjsTest",
"pathName": "pages/index/hammerjsTest/hammerjsTest",
"name": "pages/index/canvasRenderer",
"pathName": "pages/index/canvasRenderer",
"query": "",
"scene": null,
"launchMode": "default"
},
{
"name": "pages/index/hammerjsTest/hammerjsTest",
"pathName": "pages/index/hammerjsTest/hammerjsTest",
"query": "",
"launchMode": "default",
"scene": null
}
]
}