fix:校准画布编辑与图片导出文字水平垂直位置偏移问题,修改屏幕尺寸为960x1361像素

This commit is contained in:
Zhukj
2026-04-20 10:16:25 +08:00
parent 2afee798f8
commit c93dd4c304
3 changed files with 74 additions and 50 deletions

View File

@@ -60,7 +60,7 @@
20. 当选择文字的时候工具栏对应出现文字编辑器,当选择图片的时候工具栏对应出现图片编辑器,点击其他位置时隐藏工具栏,两个编辑栏的宽高保持和文本编辑器工具栏一致
21. 调整图文工具栏布局,解决顶部遮挡问题,图片透明度滑块添加百分比
22. 给背景图添加编辑器,与层叠图编辑器文本编辑器通用一个工具栏,背景图工具栏功能为:背景图可以放大缩小,可以调节透明度,其余功能不受影响,点击层叠图只显示当前编辑器工具栏
23. 校准画布编辑与图片导出文字垂直位置偏移问题,修改屏幕尺寸为960x1361像素

View File

@@ -261,7 +261,7 @@ html, body {
position: relative;
width: 100%;
max-width: 360px;
aspect-ratio: 3 / 5;
aspect-ratio: 960 / 1361;
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
background: #fff;
overflow: hidden;
@@ -372,36 +372,34 @@ html, body {
position: absolute;
min-width: 80px;
min-height: 32px;
padding: 4px 8px;
padding: 0;
font-size: 16px;
color: #000;
font-family: "Microsoft YaHei", "微软雅黑", sans-serif;
background: transparent;
border: 2px solid transparent;
outline: 2px solid transparent;
border-radius: 4px;
outline: none;
cursor: grab;
white-space: nowrap;
line-height: 1.4;
-webkit-user-select: none;
user-select: none;
transition: border-color 0.2s;
transition: outline-color 0.2s;
z-index: 10;
/* 确保输入框内容超出时不影响父容器和其他元素 */
overflow: visible;
max-width: none;
box-sizing: border-box;
}
/* 选中状态(单击/长按) - 仅显示虚线边框 */
/* 选中状态(单击/长按) - 仅显示虚线轮廓 */
.text-input.selected {
border: 2px dashed rgba(74, 144, 226, 0.6);
outline: 2px dashed rgba(74, 144, 226, 0.6);
cursor: grab;
}
/* 编辑状态(双击) - 显示实线边框 */
/* 编辑状态(双击) - 显示实线轮廓 */
.text-input.editing {
border: 2px solid #4a90e2;
outline: 2px solid #4a90e2;
background: rgba(255,255,255,0.1);
cursor: text;
-webkit-user-select: text;

View File

@@ -864,8 +864,10 @@
startX = clientX;
startY = clientY;
startLeft = element.offsetLeft;
startTop = element.offsetTop;
const containerRect = imageContainer.getBoundingClientRect();
const elemRect = element.getBoundingClientRect();
startLeft = elemRect.left - containerRect.left;
startTop = elemRect.top - containerRect.top;
// 延迟判断是否为拖拽
dragTimer = setTimeout(() => {
@@ -1782,31 +1784,27 @@
}
}
// 生成画布(预览与保存样式一致)
// 生成画布(所见即所得 - 使用显示尺寸)
async function generateCanvas() {
const canvas = exportCanvas;
const ctx = canvas.getContext('2d');
const container = imageContainer;
const containerWidth = container.clientWidth;
const containerHeight = container.clientHeight;
const width = container.clientWidth;
const height = container.clientHeight;
canvas.width = containerWidth;
canvas.height = containerHeight;
canvas.width = width;
canvas.height = height;
// 绘制白色背景
ctx.fillStyle = '#fff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillRect(0, 0, width, height);
// 绘制背景图(考虑滑动偏移)
if (currentImage) {
drawImageWithOffset(ctx, currentImage, containerWidth, containerHeight);
drawBgImageOnCanvas(ctx, currentImage, width, height);
}
// 绘制层叠图(等待图片加载完成)
await drawOverlayImages(ctx);
// 绘制文字(动态获取所有文本输入框)
const textInputs = container.querySelectorAll('.preview-input-layer .text-input');
textInputs.forEach(textInput => {
drawTextOnCanvas(ctx, textInput);
@@ -1815,6 +1813,38 @@
return canvas;
}
// 绘制背景图
function drawBgImageOnCanvas(ctx, img, width, height) {
// 使用与编辑时完全相同的计算逻辑
const containerWidth = width;
const containerHeight = height;
const imgRatio = img.naturalWidth / img.naturalHeight;
const containerRatio = containerWidth / containerHeight;
let imgWidth, imgHeight;
if (imgRatio > containerRatio) {
imgHeight = containerHeight;
imgWidth = imgHeight * imgRatio;
} else {
imgWidth = containerWidth;
imgHeight = imgWidth / imgRatio;
}
// 应用缩放
const scaledWidth = imgWidth * bgImageScale;
const scaledHeight = imgHeight * bgImageScale;
// 计算位置(与 updateBgImage 函数完全一致)
const baseLeft = (containerWidth - scaledWidth) / 2;
const drawX = baseLeft + imageOffsetX;
const drawY = (containerHeight - scaledHeight) / 2;
ctx.globalAlpha = bgImageOpacity;
ctx.drawImage(img, drawX, drawY, scaledWidth, scaledHeight);
ctx.globalAlpha = 1;
}
// 绘制层叠图
function drawOverlayImages(ctx) {
return new Promise((resolve) => {
@@ -1832,20 +1862,25 @@
return;
}
// 使用 offsetLeft/offsetTop 获取相对于 overlayLayer 的位置
const left = overlayEl.offsetLeft;
const top = overlayEl.offsetTop;
const width = overlayEl.offsetWidth;
const height = overlayEl.offsetHeight;
ctx.globalAlpha = overlayEl.style.opacity || 1;
const img = new Image();
img.onload = () => {
ctx.drawImage(img, left, top, width, height);
ctx.globalAlpha = 1;
loadedCount++;
if (loadedCount >= overlayImages.length) {
resolve();
}
};
img.onerror = () => {
ctx.globalAlpha = 1;
loadedCount++;
if (loadedCount >= overlayImages.length) {
resolve();
@@ -1856,42 +1891,33 @@
});
}
// 绘制图片(考虑滑动偏移)
function drawImageWithOffset(ctx, img, containerWidth, containerHeight) {
const imgRatio = img.naturalWidth / img.naturalHeight;
const containerRatio = containerWidth / containerHeight;
let imgWidth, imgHeight;
if (imgRatio > containerRatio) {
imgHeight = containerHeight;
imgWidth = imgHeight * imgRatio;
} else {
imgWidth = containerWidth;
imgHeight = imgWidth / imgRatio;
}
const baseLeft = (containerWidth - imgWidth) / 2;
const drawX = baseLeft + imageOffsetX;
ctx.drawImage(img, drawX, 0, imgWidth, imgHeight);
}
// 在画布上绘制文字
function drawTextOnCanvas(ctx, inputElement) {
const text = inputElement.textContent;
if (!text) return;
const deleteBtn = inputElement.querySelector('.text-delete-btn');
let text = inputElement.textContent;
if (deleteBtn) {
text = text.replace(deleteBtn.textContent, '');
}
if (!text.trim()) return;
const style = window.getComputedStyle(inputElement);
// 使用 offsetLeft/offsetTop 获取相对于 preview-input-layer 的位置
const inputLeft = inputElement.offsetLeft;
const inputTop = inputElement.offsetTop;
const style = window.getComputedStyle(inputElement);
const fontSize = parseInt(style.fontSize);
const lineHeight = parseFloat(style.lineHeight);
ctx.font = `${style.fontWeight} ${fontSize}px ${style.fontFamily}`;
ctx.fillStyle = style.color;
ctx.textBaseline = 'top';
ctx.textBaseline = 'alphabetic';
ctx.fillText(text, inputLeft, inputTop);
// 计算文本垂直偏移量,使其与 DOM 渲染一致
// DOM 中 line-height 会影响文本位置,需要计算实际文本基线位置
const actualLineHeight = lineHeight || fontSize * 1.4;
const textOffsetY = (actualLineHeight - fontSize) / 2 + fontSize * 0.85;
ctx.fillText(text, inputLeft, inputTop + textOffsetY);
}
// 保存图片