diff --git a/pages/make/index.vue b/pages/make/index.vue index e9d23c7..4aa2c13 100644 --- a/pages/make/index.vue +++ b/pages/make/index.vue @@ -37,10 +37,10 @@ class="selected-title-img" :src="currentTitle.imageUrl" mode="widthFix" - :style="{ - transform: `translate(${titleOffsetX}rpx, ${titleOffsetY}rpx) scale(${titleScale})`, - top: '40rpx' - }" + :style="titleStyle" + @touchstart.stop="handleTitleTouchStart" + @touchmove.stop="handleTitleTouchMove" + @touchend.stop="handleTitleTouchEnd" /> { + return { + transform: `translate(${titleState.value.offsetX}rpx, ${titleState.value.offsetY}rpx) scale(${titleState.value.scale}) rotate(${titleState.value.rotate}deg)`, + top: '40rpx', + pointerEvents: 'auto', + transition: 'none' + }; +}); + +// 缓存比例转换 +const sysInfo = uni.getSystemInfoSync(); +const pxToRpx = 750 / sysInfo.windowWidth; + +// 标题触摸交互相关 +let startTouches = []; +let initialTitleState = { + offsetX: 0, + offsetY: 0, + scale: 1, + rotate: 0, +}; + +const getDistance = (p1, p2) => { + const x = p2.clientX - p1.clientX; + const y = p2.clientY - p1.clientY; + return Math.sqrt(x * x + y * y); +}; + +const getAngle = (p1, p2) => { + const x = p1.clientX - p2.clientX; + const y = p1.clientY - p2.clientY; + return (Math.atan2(y, x) * 180) / Math.PI; +}; + +const handleTitleTouchStart = (e) => { + if (!currentTitle.value) return; + startTouches = e.touches; + initialTitleState = { ...titleState.value }; +}; + +const handleTitleTouchEnd = () => { + startTouches = []; +}; + +const handleTitleTouchMove = (e) => { + if (!currentTitle.value || !startTouches.length) return; + + if (e.touches.length === 1 && startTouches.length === 1) { + // 单指拖拽 + const moveX = e.touches[0].clientX - startTouches[0].clientX; + const moveY = e.touches[0].clientY - startTouches[0].clientY; + + titleState.value.offsetX = initialTitleState.offsetX + moveX * pxToRpx; + titleState.value.offsetY = initialTitleState.offsetY + moveY * pxToRpx; + } else if (e.touches.length === 2 && startTouches.length === 2) { + // 双指缩放+平移+旋转 + const p1 = e.touches[0]; + const p2 = e.touches[1]; + const startP1 = startTouches[0]; + const startP2 = startTouches[1]; + + // 缩放 + const currentDist = getDistance(p1, p2); + const startDist = getDistance(startP1, startP2); + if (startDist > 0) { + const scale = initialTitleState.scale * (currentDist / startDist); + titleState.value.scale = Math.min(Math.max(scale, 0.2), 3.0); + } + + // 旋转 + const currentAngle = getAngle(p1, p2); + const startAngle = getAngle(startP1, startP2); + titleState.value.rotate = initialTitleState.rotate + (currentAngle - startAngle); + + // 平移 + const currentCenterX = (p1.clientX + p2.clientX) / 2; + const currentCenterY = (p1.clientY + p2.clientY) / 2; + const startCenterX = (startP1.clientX + startP2.clientX) / 2; + const startCenterY = (startP1.clientY + startP2.clientY) / 2; + + const moveX = currentCenterX - startCenterX; + const moveY = currentCenterY - startCenterY; + + titleState.value.offsetX = initialTitleState.offsetX + moveX * pxToRpx; + titleState.value.offsetY = initialTitleState.offsetY + moveY * pxToRpx; + } +}; const targetName = ref("祝您"); const signatureName = ref(userStore?.userInfo?.nickName || "xxx"); @@ -750,6 +841,13 @@ const selectTitle = (title) => { currentTitle.value = null; } else { currentTitle.value = title; + // 切换标题时重置位置和缩放 + titleState.value = { + offsetX: 0, + offsetY: 0, + scale: 1, + rotate: 0, + }; closePanel(); } }; @@ -884,12 +982,26 @@ const saveByCanvas = async (save = true) => { // 3️⃣ 标题图片 if (titleImg) { const previewBaseWidth = 400; // rpx - const drawWidth = r2p(previewBaseWidth) * titleScale.value; + const drawWidth = r2p(previewBaseWidth) * titleState.value.scale; const drawHeight = (titleImg.height / titleImg.width) * drawWidth; - // 预览中是 translate(offsetX, offsetY),top: 40rpx - const titleX = (W - drawWidth) / 2 + r2p(titleOffsetX.value); - const titleY = r2p(40 + titleOffsetY.value); - ctx.drawImage(titleImg, titleX, titleY, drawWidth, drawHeight); + + ctx.save(); + // 计算中心点:容器中点 + 偏移量 + const centerX = W / 2 + r2p(titleState.value.offsetX); + const centerY = r2p(40) + drawHeight / 2 + r2p(titleState.value.offsetY); + + ctx.translate(centerX, centerY); + ctx.rotate((titleState.value.rotate * Math.PI) / 180); + + // 绘制图片,使图片中心位于 translate 后的 (0,0) + ctx.drawImage( + titleImg, + -drawWidth / 2, + -drawHeight / 2, + drawWidth, + drawHeight + ); + ctx.restore(); } // 4️⃣ 祝福语气泡 @@ -1428,7 +1540,8 @@ function drawRoundRect(ctx, x, y, w, h, r, color) { position: absolute; width: 400rpx; z-index: 2; - transition: transform 0.1s; + /* 移除 transition,防止拖拽抖动 */ + transition: none; } .tpl-card.selected { outline: 4rpx solid #ff3b30;