feat: 背景图新增缩放+透明度编辑,三图文共用统一切换工具栏
This commit is contained in:
@@ -58,6 +58,8 @@
|
|||||||
18. 可添加多个文本框,以及文本框删除功能
|
18. 可添加多个文本框,以及文本框删除功能
|
||||||
19. 添加图片编辑器工具栏,图片编辑器与文本编辑器共用同一位置工具栏,选中对应元素切换对应编辑面板,点击空白处隐藏面板
|
19. 添加图片编辑器工具栏,图片编辑器与文本编辑器共用同一位置工具栏,选中对应元素切换对应编辑面板,点击空白处隐藏面板
|
||||||
20. 当选择文字的时候工具栏对应出现文字编辑器,当选择图片的时候工具栏对应出现图片编辑器,点击其他位置时隐藏工具栏,两个编辑栏的宽高保持和文本编辑器工具栏一致
|
20. 当选择文字的时候工具栏对应出现文字编辑器,当选择图片的时候工具栏对应出现图片编辑器,点击其他位置时隐藏工具栏,两个编辑栏的宽高保持和文本编辑器工具栏一致
|
||||||
|
21. 调整图文工具栏布局,解决顶部遮挡问题,图片透明度滑块添加百分比
|
||||||
|
22. 给背景图添加编辑器,与层叠图编辑器文本编辑器通用一个工具栏,背景图工具栏功能为:背景图可以放大缩小,可以调节透明度,其余功能不受影响,点击层叠图只显示当前编辑器工具栏
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -173,6 +173,14 @@ html, body {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.scale-slider {
|
||||||
|
width: 120px;
|
||||||
|
height: 40px;
|
||||||
|
cursor: pointer;
|
||||||
|
accent-color: #4a90e2;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* 字号滚轮选择器 */
|
/* 字号滚轮选择器 */
|
||||||
.roller-picker {
|
.roller-picker {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
14
index.html
14
index.html
@@ -55,6 +55,20 @@
|
|||||||
<span id="opacityValue">100%</span>
|
<span id="opacityValue">100%</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 背景图编辑工具 -->
|
||||||
|
<div class="toolbar-group bg-tools" id="bgTools">
|
||||||
|
<label class="toolbar-label">
|
||||||
|
<span>缩放</span>
|
||||||
|
<input type="range" id="bgScaleSlider" min="100" max="300" value="100" class="scale-slider">
|
||||||
|
<span id="bgScaleValue">100%</span>
|
||||||
|
</label>
|
||||||
|
<div class="toolbar-divider"></div>
|
||||||
|
<label class="toolbar-label">
|
||||||
|
<span>透明度</span>
|
||||||
|
<input type="range" id="bgOpacitySlider" min="0" max="100" value="100" class="opacity-slider">
|
||||||
|
<span id="bgOpacityValue">100%</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 图片展示区 -->
|
<!-- 图片展示区 -->
|
||||||
|
|||||||
175
js/script.js
175
js/script.js
@@ -17,8 +17,13 @@
|
|||||||
const editorToolbar = document.getElementById('editorToolbar');
|
const editorToolbar = document.getElementById('editorToolbar');
|
||||||
const textTools = document.getElementById('textTools');
|
const textTools = document.getElementById('textTools');
|
||||||
const imageTools = document.getElementById('imageTools');
|
const imageTools = document.getElementById('imageTools');
|
||||||
|
const bgTools = document.getElementById('bgTools');
|
||||||
const opacitySlider = document.getElementById('opacitySlider');
|
const opacitySlider = document.getElementById('opacitySlider');
|
||||||
const opacityValue = document.getElementById('opacityValue');
|
const opacityValue = document.getElementById('opacityValue');
|
||||||
|
const bgScaleSlider = document.getElementById('bgScaleSlider');
|
||||||
|
const bgScaleValue = document.getElementById('bgScaleValue');
|
||||||
|
const bgOpacitySlider = document.getElementById('bgOpacitySlider');
|
||||||
|
const bgOpacityValue = document.getElementById('bgOpacityValue');
|
||||||
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');
|
||||||
@@ -34,6 +39,8 @@
|
|||||||
const guideV = document.getElementById('guideV');
|
const guideV = document.getElementById('guideV');
|
||||||
|
|
||||||
let currentImage = null;
|
let currentImage = null;
|
||||||
|
let bgImageScale = 1;
|
||||||
|
let bgImageOpacity = 1;
|
||||||
let selectedInput = null;
|
let selectedInput = null;
|
||||||
let editingInput = null;
|
let editingInput = null;
|
||||||
let longPressTimer = null;
|
let longPressTimer = null;
|
||||||
@@ -45,6 +52,7 @@
|
|||||||
let imageStartOffsetX = 0;
|
let imageStartOffsetX = 0;
|
||||||
let minOffsetX = 0;
|
let minOffsetX = 0;
|
||||||
let maxOffsetX = 0;
|
let maxOffsetX = 0;
|
||||||
|
let isInteractingWithBgImage = false;
|
||||||
|
|
||||||
// 初始化
|
// 初始化
|
||||||
init();
|
init();
|
||||||
@@ -113,6 +121,20 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 背景图缩放滑块
|
||||||
|
bgScaleSlider.addEventListener('input', (e) => {
|
||||||
|
bgImageScale = e.target.value / 100;
|
||||||
|
bgScaleValue.textContent = e.target.value + '%';
|
||||||
|
updateBgImage();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 背景图透明度滑块
|
||||||
|
bgOpacitySlider.addEventListener('input', (e) => {
|
||||||
|
bgImageOpacity = e.target.value / 100;
|
||||||
|
bgOpacityValue.textContent = e.target.value + '%';
|
||||||
|
bgImage.style.opacity = bgImageOpacity;
|
||||||
|
});
|
||||||
|
|
||||||
// 点击空白区域取消选中
|
// 点击空白区域取消选中
|
||||||
document.addEventListener('click', (e) => {
|
document.addEventListener('click', (e) => {
|
||||||
// 只有点击真正的空白区域(不是输入框,不是图片容器,不是工具栏)才取消选中
|
// 只有点击真正的空白区域(不是输入框,不是图片容器,不是工具栏)才取消选中
|
||||||
@@ -128,6 +150,10 @@
|
|||||||
|
|
||||||
// 点击容器空白处取消选中
|
// 点击容器空白处取消选中
|
||||||
function handleContainerClick(e) {
|
function handleContainerClick(e) {
|
||||||
|
if (isInteractingWithBgImage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const isBackground = e.target === imageContainer ||
|
const isBackground = e.target === imageContainer ||
|
||||||
e.target === bgLayer ||
|
e.target === bgLayer ||
|
||||||
e.target === bgImage ||
|
e.target === bgImage ||
|
||||||
@@ -202,7 +228,15 @@
|
|||||||
calculateImageBounds();
|
calculateImageBounds();
|
||||||
// 背景图选择后默认居中显示
|
// 背景图选择后默认居中显示
|
||||||
imageOffsetX = 0;
|
imageOffsetX = 0;
|
||||||
updateImagePosition();
|
// 重置缩放
|
||||||
|
bgImageScale = 1;
|
||||||
|
bgScaleSlider.value = 100;
|
||||||
|
bgScaleValue.textContent = '100%';
|
||||||
|
// 重置透明度
|
||||||
|
bgImageOpacity = 1;
|
||||||
|
bgOpacitySlider.value = 100;
|
||||||
|
bgOpacityValue.textContent = '100%';
|
||||||
|
updateBgImage();
|
||||||
};
|
};
|
||||||
img.src = e.target.result;
|
img.src = e.target.result;
|
||||||
};
|
};
|
||||||
@@ -523,6 +557,7 @@
|
|||||||
editorToolbar.classList.add('active');
|
editorToolbar.classList.add('active');
|
||||||
textTools.classList.remove('active');
|
textTools.classList.remove('active');
|
||||||
imageTools.classList.add('active');
|
imageTools.classList.add('active');
|
||||||
|
bgTools.classList.remove('active');
|
||||||
|
|
||||||
// 更新透明度滑块为当前层叠图的透明度
|
// 更新透明度滑块为当前层叠图的透明度
|
||||||
const currentOpacity = overlayEl.style.opacity || '1';
|
const currentOpacity = overlayEl.style.opacity || '1';
|
||||||
@@ -569,13 +604,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新图片位置
|
// 更新背景图(缩放和透明度)
|
||||||
function updateImagePosition() {
|
function updateBgImage() {
|
||||||
if (!currentImage) return;
|
if (!currentImage) return;
|
||||||
|
|
||||||
// 限制滑动范围,确保不露出白板
|
|
||||||
imageOffsetX = Math.max(minOffsetX, Math.min(maxOffsetX, imageOffsetX));
|
|
||||||
|
|
||||||
const containerWidth = imageContainer.clientWidth;
|
const containerWidth = imageContainer.clientWidth;
|
||||||
const containerHeight = imageContainer.clientHeight;
|
const containerHeight = imageContainer.clientHeight;
|
||||||
const imgRatio = currentImage.naturalWidth / currentImage.naturalHeight;
|
const imgRatio = currentImage.naturalWidth / currentImage.naturalHeight;
|
||||||
@@ -591,11 +623,71 @@
|
|||||||
imgHeight = imgWidth / imgRatio;
|
imgHeight = imgWidth / imgRatio;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 应用位置
|
// 应用缩放
|
||||||
const baseLeft = (containerWidth - imgWidth) / 2;
|
bgImage.style.width = (imgWidth * bgImageScale) + 'px';
|
||||||
bgImage.style.width = imgWidth + 'px';
|
bgImage.style.height = (imgHeight * bgImageScale) + 'px';
|
||||||
bgImage.style.height = imgHeight + 'px';
|
|
||||||
|
// 应用透明度
|
||||||
|
bgImage.style.opacity = bgImageOpacity;
|
||||||
|
|
||||||
|
// 重新计算边界
|
||||||
|
const scaledWidth = imgWidth * bgImageScale;
|
||||||
|
const scaledHeight = imgHeight * bgImageScale;
|
||||||
|
if (scaledWidth > containerWidth) {
|
||||||
|
minOffsetX = -(scaledWidth - containerWidth) / 2;
|
||||||
|
maxOffsetX = (scaledWidth - containerWidth) / 2;
|
||||||
|
} else {
|
||||||
|
minOffsetX = 0;
|
||||||
|
maxOffsetX = 0;
|
||||||
|
}
|
||||||
|
// 限制当前位置在新范围内
|
||||||
|
imageOffsetX = Math.max(minOffsetX, Math.min(maxOffsetX, imageOffsetX));
|
||||||
|
|
||||||
|
// 重新应用位置(居中 + 偏移)
|
||||||
|
const baseLeft = (containerWidth - scaledWidth) / 2;
|
||||||
bgImage.style.left = (baseLeft + imageOffsetX) + 'px';
|
bgImage.style.left = (baseLeft + imageOffsetX) + 'px';
|
||||||
|
bgImage.style.top = ((containerHeight - scaledHeight) / 2) + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新图片位置
|
||||||
|
function updateImagePosition() {
|
||||||
|
if (!currentImage) return;
|
||||||
|
|
||||||
|
const containerWidth = imageContainer.clientWidth;
|
||||||
|
const containerHeight = imageContainer.clientHeight;
|
||||||
|
const imgRatio = currentImage.naturalWidth / currentImage.naturalHeight;
|
||||||
|
|
||||||
|
let imgWidth, imgHeight;
|
||||||
|
|
||||||
|
if (imgRatio > containerWidth / containerHeight) {
|
||||||
|
imgHeight = containerHeight;
|
||||||
|
imgWidth = imgHeight * imgRatio;
|
||||||
|
} else {
|
||||||
|
imgWidth = containerWidth;
|
||||||
|
imgHeight = imgWidth / imgRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用缩放
|
||||||
|
const scaledWidth = imgWidth * bgImageScale;
|
||||||
|
const scaledHeight = imgHeight * bgImageScale;
|
||||||
|
|
||||||
|
// 限制滑动范围
|
||||||
|
if (scaledWidth > containerWidth) {
|
||||||
|
minOffsetX = -(scaledWidth - containerWidth) / 2;
|
||||||
|
maxOffsetX = (scaledWidth - containerWidth) / 2;
|
||||||
|
} else {
|
||||||
|
minOffsetX = 0;
|
||||||
|
maxOffsetX = 0;
|
||||||
|
}
|
||||||
|
imageOffsetX = Math.max(minOffsetX, Math.min(maxOffsetX, imageOffsetX));
|
||||||
|
|
||||||
|
// 应用位置
|
||||||
|
const baseLeft = (containerWidth - scaledWidth) / 2;
|
||||||
|
bgImage.style.width = scaledWidth + 'px';
|
||||||
|
bgImage.style.height = scaledHeight + 'px';
|
||||||
|
bgImage.style.left = (baseLeft + imageOffsetX) + 'px';
|
||||||
|
bgImage.style.top = ((containerHeight - scaledHeight) / 2) + 'px';
|
||||||
|
bgImage.style.opacity = bgImageOpacity;
|
||||||
bgImage.style.transform = 'none';
|
bgImage.style.transform = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -613,6 +705,53 @@
|
|||||||
document.addEventListener('mouseup', handleImageEnd);
|
document.addEventListener('mouseup', handleImageEnd);
|
||||||
document.addEventListener('touchend', handleImageEnd);
|
document.addEventListener('touchend', handleImageEnd);
|
||||||
|
|
||||||
|
// 点击背景图显示工具栏
|
||||||
|
bgImage.addEventListener('click', (e) => {
|
||||||
|
if (!currentImage) return;
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
isInteractingWithBgImage = true;
|
||||||
|
|
||||||
|
// 显示背景图工具栏
|
||||||
|
editorToolbar.classList.add('active');
|
||||||
|
textTools.classList.remove('active');
|
||||||
|
imageTools.classList.remove('active');
|
||||||
|
bgTools.classList.add('active');
|
||||||
|
|
||||||
|
// 同步滑块值
|
||||||
|
bgScaleSlider.value = bgImageScale * 100;
|
||||||
|
bgScaleValue.textContent = Math.round(bgImageScale * 100) + '%';
|
||||||
|
bgOpacitySlider.value = bgImageOpacity * 100;
|
||||||
|
bgOpacityValue.textContent = Math.round(bgImageOpacity * 100) + '%';
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
isInteractingWithBgImage = false;
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
bgLayer.addEventListener('click', (e) => {
|
||||||
|
if (!currentImage) return;
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
isInteractingWithBgImage = true;
|
||||||
|
|
||||||
|
// 显示背景图工具栏
|
||||||
|
editorToolbar.classList.add('active');
|
||||||
|
textTools.classList.remove('active');
|
||||||
|
imageTools.classList.remove('active');
|
||||||
|
bgTools.classList.add('active');
|
||||||
|
|
||||||
|
// 同步滑块值
|
||||||
|
bgScaleSlider.value = bgImageScale * 100;
|
||||||
|
bgScaleValue.textContent = Math.round(bgImageScale * 100) + '%';
|
||||||
|
bgOpacitySlider.value = bgImageOpacity * 100;
|
||||||
|
bgOpacityValue.textContent = Math.round(bgImageOpacity * 100) + '%';
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
isInteractingWithBgImage = false;
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
|
||||||
function handleImageStart(e) {
|
function handleImageStart(e) {
|
||||||
// 如果点击的是输入框,不处理图片滑动
|
// 如果点击的是输入框,不处理图片滑动
|
||||||
if (e.target.closest('.text-input')) {
|
if (e.target.closest('.text-input')) {
|
||||||
@@ -642,9 +781,22 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
isDraggingImage = true;
|
isDraggingImage = true;
|
||||||
|
isInteractingWithBgImage = true;
|
||||||
imageStartX = clientX;
|
imageStartX = clientX;
|
||||||
imageStartOffsetX = imageOffsetX;
|
imageStartOffsetX = imageOffsetX;
|
||||||
|
|
||||||
|
// 显示背景图工具栏
|
||||||
|
editorToolbar.classList.add('active');
|
||||||
|
textTools.classList.remove('active');
|
||||||
|
imageTools.classList.remove('active');
|
||||||
|
bgTools.classList.add('active');
|
||||||
|
|
||||||
|
// 同步滑块值
|
||||||
|
bgScaleSlider.value = bgImageScale * 100;
|
||||||
|
bgScaleValue.textContent = Math.round(bgImageScale * 100) + '%';
|
||||||
|
bgOpacitySlider.value = bgImageOpacity * 100;
|
||||||
|
bgOpacityValue.textContent = Math.round(bgImageOpacity * 100) + '%';
|
||||||
|
|
||||||
if (isTouch) {
|
if (isTouch) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
@@ -670,6 +822,7 @@
|
|||||||
|
|
||||||
function handleImageEnd() {
|
function handleImageEnd() {
|
||||||
isDraggingImage = false;
|
isDraggingImage = false;
|
||||||
|
isInteractingWithBgImage = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -863,6 +1016,7 @@
|
|||||||
editorToolbar.classList.add('active');
|
editorToolbar.classList.add('active');
|
||||||
textTools.classList.add('active');
|
textTools.classList.add('active');
|
||||||
imageTools.classList.remove('active');
|
imageTools.classList.remove('active');
|
||||||
|
bgTools.classList.remove('active');
|
||||||
|
|
||||||
// 更新工具栏状态
|
// 更新工具栏状态
|
||||||
updateToolbarState(element);
|
updateToolbarState(element);
|
||||||
@@ -885,6 +1039,7 @@
|
|||||||
editorToolbar.classList.add('active');
|
editorToolbar.classList.add('active');
|
||||||
textTools.classList.add('active');
|
textTools.classList.add('active');
|
||||||
imageTools.classList.remove('active');
|
imageTools.classList.remove('active');
|
||||||
|
bgTools.classList.remove('active');
|
||||||
|
|
||||||
// 更新工具栏状态
|
// 更新工具栏状态
|
||||||
updateToolbarState(element);
|
updateToolbarState(element);
|
||||||
|
|||||||
Reference in New Issue
Block a user