feat: 添加图片编辑器工具栏
This commit is contained in:
@@ -55,6 +55,8 @@
|
|||||||
15. 文本输入框添加辅助线,完善辅助线的吸附逻辑更加精准,辅助线颜色为红色
|
15. 文本输入框添加辅助线,完善辅助线的吸附逻辑更加精准,辅助线颜色为红色
|
||||||
16. 顶部工具栏新增层叠图,可添加多张层叠图,单击选中状态可缩放删除自由移动位置,置于背景图之上,输入框与层叠图点击只能同时选中一个
|
16. 顶部工具栏新增层叠图,可添加多张层叠图,单击选中状态可缩放删除自由移动位置,置于背景图之上,输入框与层叠图点击只能同时选中一个
|
||||||
17. 层叠图添加辅助线
|
17. 层叠图添加辅助线
|
||||||
|
18. 可添加多个文本框,以及文本框删除功能
|
||||||
|
19. 添加图片编辑器工具栏,图片编辑器与文本编辑器共用同一位置工具栏,选中对应元素切换对应编辑面板,点击空白出隐藏面板,修复层叠图不可移动问题
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ html, body {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 顶部工具栏 */
|
/* 顶部工具栏 */
|
||||||
@@ -81,6 +82,10 @@ html, body {
|
|||||||
|
|
||||||
/* 文本编辑器工具栏 */
|
/* 文本编辑器工具栏 */
|
||||||
.text-editor-toolbar {
|
.text-editor-toolbar {
|
||||||
|
position: absolute;
|
||||||
|
top: 49px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -92,6 +97,7 @@ html, body {
|
|||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 0.2s;
|
transition: opacity 0.2s;
|
||||||
|
z-index: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-editor-toolbar.active {
|
.text-editor-toolbar.active {
|
||||||
@@ -99,6 +105,31 @@ html, body {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 图片编辑器工具栏 - 与文本编辑器共享位置 */
|
||||||
|
.image-editor-toolbar {
|
||||||
|
position: absolute;
|
||||||
|
top: 49px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px 16px;
|
||||||
|
background: #fff;
|
||||||
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-editor-toolbar.active {
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.toolbar-btn {
|
.toolbar-btn {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
|
|||||||
12
index.html
12
index.html
@@ -46,6 +46,18 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 图片编辑器工具栏 -->
|
||||||
|
<div class="image-editor-toolbar" id="imageEditorToolbar">
|
||||||
|
<label class="toolbar-label">
|
||||||
|
<span>透明度</span>
|
||||||
|
<div class="roller-picker" id="opacityPicker">
|
||||||
|
<div class="roller-arrow roller-up">▲</div>
|
||||||
|
<div class="roller-value">100</div>
|
||||||
|
<div class="roller-arrow roller-down">▼</div>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 图片展示区 -->
|
<!-- 图片展示区 -->
|
||||||
<div class="canvas-area" id="canvasArea">
|
<div class="canvas-area" id="canvasArea">
|
||||||
<div class="preview image-container empty" id="imageContainer">
|
<div class="preview image-container empty" id="imageContainer">
|
||||||
|
|||||||
93
js/script.js
93
js/script.js
@@ -15,11 +15,13 @@
|
|||||||
const longPressTip = document.getElementById('longPressTip');
|
const longPressTip = document.getElementById('longPressTip');
|
||||||
const canvasArea = document.getElementById('canvasArea');
|
const canvasArea = document.getElementById('canvasArea');
|
||||||
const textEditorToolbar = document.getElementById('textEditorToolbar');
|
const textEditorToolbar = document.getElementById('textEditorToolbar');
|
||||||
|
const imageEditorToolbar = document.getElementById('imageEditorToolbar');
|
||||||
const boldBtn = document.getElementById('boldBtn');
|
const boldBtn = document.getElementById('boldBtn');
|
||||||
const italicBtn = document.getElementById('italicBtn');
|
const italicBtn = document.getElementById('italicBtn');
|
||||||
const colorPicker = document.getElementById('colorPicker');
|
const colorPicker = document.getElementById('colorPicker');
|
||||||
const fontPicker = document.getElementById('fontPicker');
|
const fontPicker = document.getElementById('fontPicker');
|
||||||
const fontSizePicker = document.getElementById('fontSizePicker');
|
const fontSizePicker = document.getElementById('fontSizePicker');
|
||||||
|
const opacityPicker = document.getElementById('opacityPicker');
|
||||||
const overlayLayer = document.getElementById('overlayLayer');
|
const overlayLayer = document.getElementById('overlayLayer');
|
||||||
const overlayInput = document.createElement('input');
|
const overlayInput = document.createElement('input');
|
||||||
overlayInput.type = 'file';
|
overlayInput.type = 'file';
|
||||||
@@ -142,8 +144,9 @@
|
|||||||
}
|
}
|
||||||
// 隐藏辅助线
|
// 隐藏辅助线
|
||||||
hideGuides();
|
hideGuides();
|
||||||
// 隐藏文本编辑器工具栏
|
// 隐藏文本编辑器工具栏和图片编辑器工具栏
|
||||||
textEditorToolbar.classList.remove('active');
|
textEditorToolbar.classList.remove('active');
|
||||||
|
imageEditorToolbar.classList.remove('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 点击空白区域(不是层叠图和输入框)
|
// 点击空白区域(不是层叠图和输入框)
|
||||||
@@ -307,12 +310,16 @@
|
|||||||
let isDragging = false;
|
let isDragging = false;
|
||||||
let isResizing = false;
|
let isResizing = false;
|
||||||
let startX, startY, startLeft, startTop, startWidth, startHeight;
|
let startX, startY, startLeft, startTop, startWidth, startHeight;
|
||||||
|
let hasMoved = false;
|
||||||
const minSize = 30;
|
const minSize = 30;
|
||||||
|
|
||||||
// 点击选中(区分点击和拖拽)
|
// 点击选中(区分点击和拖拽)
|
||||||
overlayEl.addEventListener('click', (e) => {
|
overlayEl.addEventListener('click', (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
selectOverlay(overlayEl);
|
if (!hasMoved) {
|
||||||
|
selectOverlay(overlayEl);
|
||||||
|
}
|
||||||
|
hasMoved = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 拖拽移动(不选中也可拖动)
|
// 拖拽移动(不选中也可拖动)
|
||||||
@@ -329,6 +336,7 @@
|
|||||||
const clientY = isTouch ? e.touches[0].clientY : e.clientY;
|
const clientY = isTouch ? e.touches[0].clientY : e.clientY;
|
||||||
|
|
||||||
isDragging = true;
|
isDragging = true;
|
||||||
|
hasMoved = false;
|
||||||
startX = clientX;
|
startX = clientX;
|
||||||
startY = clientY;
|
startY = clientY;
|
||||||
startLeft = overlayEl.offsetLeft;
|
startLeft = overlayEl.offsetLeft;
|
||||||
@@ -336,7 +344,6 @@
|
|||||||
|
|
||||||
overlayEl.classList.add('dragging');
|
overlayEl.classList.add('dragging');
|
||||||
|
|
||||||
// 拖动时设置为选中状态
|
|
||||||
selectOverlay(overlayEl);
|
selectOverlay(overlayEl);
|
||||||
|
|
||||||
document.addEventListener('mousemove', handleDragMove);
|
document.addEventListener('mousemove', handleDragMove);
|
||||||
@@ -357,21 +364,10 @@
|
|||||||
|
|
||||||
const moveDistance = Math.sqrt(dx * dx + dy * dy);
|
const moveDistance = Math.sqrt(dx * dx + dy * dy);
|
||||||
if (moveDistance > 5) {
|
if (moveDistance > 5) {
|
||||||
|
hasMoved = true;
|
||||||
let newLeft = startLeft + dx;
|
let newLeft = startLeft + dx;
|
||||||
let newTop = startTop + dy;
|
let newTop = startTop + dy;
|
||||||
|
|
||||||
// 使用吸附功能
|
|
||||||
const snapped = getSnapPosition(overlayEl, newLeft, newTop);
|
|
||||||
newLeft = snapped.left;
|
|
||||||
newTop = snapped.top;
|
|
||||||
|
|
||||||
// 显示对齐辅助线
|
|
||||||
if (snapped.showGuide) {
|
|
||||||
showAlignmentGuides(overlayEl);
|
|
||||||
} else {
|
|
||||||
hideGuides();
|
|
||||||
}
|
|
||||||
|
|
||||||
overlayEl.style.left = newLeft + 'px';
|
overlayEl.style.left = newLeft + 'px';
|
||||||
overlayEl.style.top = newTop + 'px';
|
overlayEl.style.top = newTop + 'px';
|
||||||
}
|
}
|
||||||
@@ -382,7 +378,6 @@
|
|||||||
function handleDragEnd() {
|
function handleDragEnd() {
|
||||||
isDragging = false;
|
isDragging = false;
|
||||||
overlayEl.classList.remove('dragging');
|
overlayEl.classList.remove('dragging');
|
||||||
hideGuides();
|
|
||||||
|
|
||||||
document.removeEventListener('mousemove', handleDragMove);
|
document.removeEventListener('mousemove', handleDragMove);
|
||||||
document.removeEventListener('mouseup', handleDragEnd);
|
document.removeEventListener('mouseup', handleDragEnd);
|
||||||
@@ -506,6 +501,15 @@
|
|||||||
}
|
}
|
||||||
selectedOverlay = overlayEl;
|
selectedOverlay = overlayEl;
|
||||||
overlayEl.classList.add('selected');
|
overlayEl.classList.add('selected');
|
||||||
|
|
||||||
|
// 显示图片编辑器工具栏,隐藏文本编辑器工具栏
|
||||||
|
imageEditorToolbar.classList.add('active');
|
||||||
|
textEditorToolbar.classList.remove('active');
|
||||||
|
|
||||||
|
// 更新透明度值显示
|
||||||
|
const currentOpacity = Math.round(parseFloat(overlayEl.style.opacity || 1) * 100);
|
||||||
|
opacityValue = currentOpacity;
|
||||||
|
opacityValueEl.textContent = currentOpacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算图片滑动边界
|
// 计算图片滑动边界
|
||||||
@@ -597,6 +601,11 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果点击的是层叠图,不处理图片滑动(让层叠图的拖拽处理)
|
||||||
|
if (e.target.closest('.overlay-image')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 如果正在编辑,先退出编辑状态
|
// 如果正在编辑,先退出编辑状态
|
||||||
if (editingInput) {
|
if (editingInput) {
|
||||||
deselectAll();
|
deselectAll();
|
||||||
@@ -837,8 +846,9 @@
|
|||||||
element.classList.remove('editing');
|
element.classList.remove('editing');
|
||||||
element.contentEditable = 'false';
|
element.contentEditable = 'false';
|
||||||
|
|
||||||
// 显示文本编辑器工具栏
|
// 显示文本编辑器工具栏,隐藏图片编辑器工具栏
|
||||||
textEditorToolbar.classList.add('active');
|
textEditorToolbar.classList.add('active');
|
||||||
|
imageEditorToolbar.classList.remove('active');
|
||||||
|
|
||||||
// 更新工具栏状态
|
// 更新工具栏状态
|
||||||
updateToolbarState(element);
|
updateToolbarState(element);
|
||||||
@@ -857,8 +867,9 @@
|
|||||||
element.classList.remove('selected');
|
element.classList.remove('selected');
|
||||||
element.contentEditable = 'true';
|
element.contentEditable = 'true';
|
||||||
|
|
||||||
// 显示文本编辑器工具栏
|
// 显示文本编辑器工具栏,隐藏图片编辑器工具栏
|
||||||
textEditorToolbar.classList.add('active');
|
textEditorToolbar.classList.add('active');
|
||||||
|
imageEditorToolbar.classList.remove('active');
|
||||||
|
|
||||||
// 更新工具栏状态
|
// 更新工具栏状态
|
||||||
updateToolbarState(element);
|
updateToolbarState(element);
|
||||||
@@ -1314,8 +1325,13 @@
|
|||||||
selectedInput.classList.remove('selected');
|
selectedInput.classList.remove('selected');
|
||||||
selectedInput = null;
|
selectedInput = null;
|
||||||
}
|
}
|
||||||
// 隐藏文本编辑器工具栏
|
if (selectedOverlay) {
|
||||||
|
selectedOverlay.classList.remove('selected');
|
||||||
|
selectedOverlay = null;
|
||||||
|
}
|
||||||
|
// 隐藏所有工具栏
|
||||||
textEditorToolbar.classList.remove('active');
|
textEditorToolbar.classList.remove('active');
|
||||||
|
imageEditorToolbar.classList.remove('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化长按保存(只在3:5预览区域)
|
// 初始化长按保存(只在3:5预览区域)
|
||||||
@@ -1468,6 +1484,45 @@
|
|||||||
updateFontSize(currentSize + delta, true);
|
updateFontSize(currentSize + delta, true);
|
||||||
}
|
}
|
||||||
}, { passive: false });
|
}, { passive: false });
|
||||||
|
|
||||||
|
// 透明度选择器 - 密码锁式滚轮
|
||||||
|
let opacityValue = 100;
|
||||||
|
const opacityRoller = document.getElementById('opacityPicker');
|
||||||
|
const opacityValueEl = opacityRoller.querySelector('.roller-value');
|
||||||
|
const opacityUpBtn = opacityRoller.querySelector('.roller-up');
|
||||||
|
const opacityDownBtn = opacityRoller.querySelector('.roller-down');
|
||||||
|
|
||||||
|
function updateOpacity(newOpacity, forceUpdate = false) {
|
||||||
|
newOpacity = Math.max(0, Math.min(100, newOpacity));
|
||||||
|
if (selectedOverlay) {
|
||||||
|
selectedOverlay.style.opacity = newOpacity / 100;
|
||||||
|
opacityValue = newOpacity;
|
||||||
|
opacityValueEl.textContent = newOpacity;
|
||||||
|
} else {
|
||||||
|
opacityValue = newOpacity;
|
||||||
|
opacityValueEl.textContent = newOpacity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
opacityUpBtn.addEventListener('click', () => {
|
||||||
|
if (selectedOverlay) {
|
||||||
|
updateOpacity(opacityValue + 5, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
opacityDownBtn.addEventListener('click', () => {
|
||||||
|
if (selectedOverlay) {
|
||||||
|
updateOpacity(opacityValue - 5, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
opacityRoller.addEventListener('wheel', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (selectedOverlay) {
|
||||||
|
const delta = e.deltaY > 0 ? -5 : 5;
|
||||||
|
updateOpacity(opacityValue + delta, true);
|
||||||
|
}
|
||||||
|
}, { passive: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 显示右键菜单
|
// 显示右键菜单
|
||||||
|
|||||||
Reference in New Issue
Block a user