以下是优化后的左右布局图片盖章工具代码,新增了源文件多格式支持、印章大小调节和拖动位置功能:
```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>智能图片盖章工具</title>
<style>
:root {
--primary: #4361ee;
--secondary: #3f37c9;
--light: #f8f9fa;
--dark: #212529;
}
body {
font-family: 'Segoe UI', system-ui, sans-serif;
margin: 0;
padding: 0;
background-color: #f5f7fb;
color: var(--dark);
}
.app-container {
display: flex;
max-width: 1400px;
margin: 20px auto;
background: white;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
overflow: hidden;
height: calc(100vh - 40px);
}
.left-panel {
width: 300px;
padding: 20px;
border-right: 1px solid #eee;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 20px;
}
.right-panel {
flex: 1;
display: flex;
flex-direction: column;
padding: 20px;
}
h1 {
color: var(--primary);
margin-top: 0;
font-size: 1.5rem;
}
.tool-section {
background: #f8f9fa;
border-radius: 8px;
padding: 15px;
}
.tool-section h3 {
margin-top: 0;
color: var(--secondary);
font-size: 1rem;
}
.btn {
background: var(--primary);
color: white;
border: none;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
transition: all 0.2s;
width: 100%;
margin-bottom: 10px;
}
.btn:hover {
background: var(--secondary);
transform: translateY(-1px);
}
.btn-secondary {
background: #6c757d;
}
.canvas-container {
position: relative;
flex: 1;
border: 1px dashed #ddd;
border-radius: 8px;
overflow: hidden;
background-color: #f9f9f9;
display: flex;
justify-content: center;
align-items: center;
}
#mainCanvas {
max-width: 100%;
max-height: 100%;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.stamp-controls {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
margin-top: 20px;
}
.control-group {
display: flex;
flex-direction: column;
gap: 8px;
}
label {
font-size: 14px;
font-weight: 500;
}
input[type="range"] {
width: 100%;
}
select {
padding: 8px;
border-radius: 6px;
border: 1px solid #ddd;
background: white;
}
.file-input {
display: none;
}
.preview-container {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 10px;
}
.preview-stamp {
width: 120px;
height: 120px;
object-fit: contain;
border: 1px solid #eee;
border-radius: 4px;
margin-bottom: 10px;
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
}
.stamp-resize-handle {
position: absolute;
width: 12px;
height: 12px;
background: var(--primary);
border-radius: 50%;
right: -6px;
bottom: -6px;
cursor: nwse-resize;
z-index: 10;
display: none;
}
.active-stamp {
outline: 2px dashed var(--primary);
}
.format-options {
display: flex;
gap: 10px;
margin-top: 10px;
}
.format-btn {
padding: 5px 10px;
border: 1px solid #ddd;
border-radius: 4px;
background: white;
cursor: pointer;
}
.format-btn.active {
background: var(--primary);
color: white;
border-color: var(--primary);
}
</style>
</head>
<body>
<div class="app-container">
<div class="left-panel">
<h1>图片盖章工具</h1>
<div class="tool-section">
<h3>源文件操作</h3>
<button id="uploadImage" class="btn">上传图片</button>
<input type="file" id="imageInput" class="file-input" accept="image/*">
<div class="format-options">
<button class="format-btn" data-format="png">PNG</button>
<button class="format-btn" data-format="jpeg">JPEG</button>
<button class="format-btn" data-format="webp">WEBP</button>
</div>
</div>
<div class="tool-section">
<h3>印章操作</h3>
<button id="uploadStamp" class="btn">上传印章</button>
<input type="file" id="stampInput" class="file-input" accept="image/*">
<div class="preview-container">
<img id="previewStamp" src="" class="preview-stamp" alt="印章预览">
<button id="addRegularStamp" class="btn">添加普通章</button>
<button id="addSeamStamp" class="btn" style="margin-top: 5px;">添加骑缝章</button>
</div>
</div>
<div class="tool-section">
<h3>印章设置</h3>
<div class="control-group">
<label for="size">印章大小</label>
<input type="range" id="size" min="50" max="300" value="100">
</div>
<div class="control-group">
<label for="opacity">透明度</label>
<input type="range" id="opacity" min="0" max="1" step="0.1" value="0.8">
</div>
<div class="control-group">
<label for="blendMode">混合模式</label>
<select id="blendMode">
<option value="source-over">正常</option>
<option value="multiply">正片叠底</option>
<option value="screen">滤色</option>
<option value="overlay">叠加</option>
</select>
</div>
</div>
<div class="tool-section">
<h3>输出操作</h3>
<button id="download" class="btn">下载图片</button>
<button id="reset" class="btn btn-secondary">重置所有</button>
</div>
</div>
<div class="right-panel">
<div class="canvas-container">
<canvas id="mainCanvas"></canvas>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const canvas = document.getElementById('mainCanvas');
const ctx = canvas.getContext('2d');
const imageInput = document.getElementById('imageInput');
const stampInput = document.getElementById('stampInput');
const uploadImageBtn = document.getElementById('uploadImage');
const uploadStampBtn = document.getElementById('uploadStamp');
const addRegularStampBtn = document.getElementById('addRegularStamp');
const addSeamStampBtn = document.getElementById('addSeamStamp');
const downloadBtn = document.getElementById('download');
const resetBtn = document.getElementById('reset');
const opacityControl = document.getElementById('opacity');
const blendModeControl = document.getElementById('blendMode');
const sizeControl = document.getElementById('size');
const previewStamp = document.getElementById('previewStamp');
const formatBtns = document.querySelectorAll('.format-btn');
let currentImage = null;
let currentStamp = null;
let activeStamp = null;
let isDragging = false;
let isResizing = false;
let dragStartX, dragStartY;
let selectedFormat = 'png';
let canvasScale = 1;
// 初始化画布
function initCanvas() {
const container = canvas.parentElement;
canvas.width = container.clientWidth * 0.9;
canvas.height = container.clientHeight * 0.9;
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.textAlign = 'center';
ctx.fillStyle = '#999';
ctx.font = '20px Arial';
ctx.fillText('请上传图片或添加印章', canvas.width/2, canvas.height/2);
}
// 上传图片
uploadImageBtn.addEventListener('click', function() {
imageInput.click();
});
imageInput.addEventListener('change', function(e) {
if (e.target.files.length > 0) {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = function(event) {
const img = new Image();
img.onload = function() {
currentImage = img;
// 自动缩放画布以适应图片
fitCanvasToImage();
drawCanvas();
};
img.src = event.target.result;
};
reader.readAsDataURL(file);
}
});
// 适配画布到图片尺寸
function fitCanvasToImage() {
if (!currentImage) return;
const container = canvas.parentElement;
const containerRatio = container.clientWidth / container.clientHeight;
const imageRatio = currentImage.width / currentImage.height;
if (imageRatio > containerRatio) {
// 宽度受限
canvas.width = container.clientWidth * 0.9;
canvas.height = canvas.width / imageRatio;
canvasScale = canvas.width / currentImage.width;
} else {
// 高度受限
canvas.height = container.clientHeight * 0.9;
canvas.width = canvas.height * imageRatio;
canvasScale = canvas.height / currentImage.height;
}
}
// 上传印章
uploadStampBtn.addEventListener('click', function() {
stampInput.click();
});
stampInput.addEventListener('change', function(e) {
if (e.target.files.length > 0) {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = function(event) {
const img = new Image();
img.onload = function() {
currentStamp = img;
previewStamp.src = event.target.result;
// 设置默认大小为预览图的50%
sizeControl.value = Math.min(img.width, img.height) * 0.5;
};
img.src = event.target.result;
};
reader.readAsDataURL(file);
}
});
// 添加普通印章
addRegularStampBtn.addEventListener('click', function() {
if (!currentStamp) return alert('请先上传印章');
const size = parseInt(sizeControl.value);
const ratio = currentStamp.width / currentStamp.height;
let width, height;
if (ratio > 1) {
width = size;
height = size / ratio;
} else {
height = size;
width = size * ratio;
}
activeStamp = {
type: 'regular',
img: currentStamp,
x: canvas.width/2 - width/2,
y: canvas.height/2 - height/2,
width: width,
height: height,
originalWidth: currentStamp.width,
originalHeight: currentStamp.height
};
drawCanvas();
});
// 添加骑缝章
addSeamStampBtn.addEventListener('click', function() {
if (!currentImage) return alert('请先上传图片');
if (!currentStamp) return alert('请先上传印章');
const size = parseInt(sizeControl.value);
const ratio = currentStamp.width / currentStamp.height;
let width, height;
if (ratio > 1) {
width = size;
height = size / ratio;
} else {
height = size;
width = size * ratio;
}
activeStamp = {
type: 'seam',
img: currentStamp,
x: canvas.width/2 - width/2,
y: canvas.height/2 - height/2,
width: width,
height: height,
originalWidth: currentStamp.width,
originalHeight: currentStamp.height
};
drawCanvas();
});
// 绘制画布
function drawCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 绘制背景图片
if (currentImage) {
ctx.drawImage(currentImage, 0, 0, canvas.width, canvas.height);
// 存储图片位置信息用于骑缝章
if (activeStamp && activeStamp.type === 'seam') {
activeStamp.imageWidth = canvas.width;
activeStamp.imageHeight = canvas.height;
}
} else {
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
// 绘制印章
if (activeStamp) {
ctx.globalAlpha = opacityControl.value;
ctx.globalCompositeOperation = blendModeControl.value;
if (activeStamp.type === 'regular') {
// 普通印章
ctx.drawImage(
activeStamp.img,
activeStamp.x,
activeStamp.y,
activeStamp.width,
activeStamp.height
);
} else if (activeStamp.type === 'seam') {
// 骑缝章 - 绘制两半
const clipX = canvas.width / 2;
// 左半部分
ctx.save();
ctx.beginPath();
ctx.rect(0, 0, clipX, canvas.height);
ctx.clip();
ctx.drawImage(
activeStamp.img,
activeStamp.x,
activeStamp.y,
activeStamp.width,
activeStamp.height
);
ctx.restore();
// 右半部分
ctx.save();
ctx.beginPath();
ctx.rect(clipX, 0, clipX, canvas.height);
ctx.clip();
ctx.drawImage(
activeStamp.img,
activeStamp.x,
activeStamp.y,
activeStamp.width,
activeStamp.height
);
ctx.restore();
}
// 重置混合模式
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = 'source-over';
// 绘制选中框
if (isDragging || isResizing) {
ctx.strokeStyle = 'rgba(67, 97, 238, 0.8)';
ctx.lineWidth = 2;
ctx.setLineDash([5, 5]);
ctx.strokeRect(
activeStamp.x,
activeStamp.y,
activeStamp.width,
activeStamp.height
);
ctx.setLineDash([]);
}
}
}
// 下载图片
downloadBtn.addEventListener('click', function() {
if (!currentImage && !activeStamp) return alert('没有可下载的内容');
// 创建临时画布以确保高质量输出
const tempCanvas = document.createElement('canvas');
const tempCtx = tempCanvas.getContext('2d');
if (currentImage) {
tempCanvas.width = currentImage.width;
tempCanvas.height = currentImage.height;
tempCtx.drawImage(currentImage, 0, 0);
// 如果有印章,需要在原始尺寸上重绘
if (activeStamp) {
const ratio = currentImage.width / canvas.width;
const stampX = activeStamp.x * ratio;
const stampY = activeStamp.y * ratio;
const stampWidth = activeStamp.width * ratio;
const stampHeight = activeStamp.height * ratio;
tempCtx.globalAlpha = opacityControl.value;
tempCtx.globalCompositeOperation = blendModeControl.value;
if (activeStamp.type === 'regular') {
tempCtx.drawImage(
activeStamp.img,
stampX,
stampY,
stampWidth,
stampHeight
);
} else if (activeStamp.type === 'seam') {
const clipX = currentImage.width / 2;
// 左半部分
tempCtx.save();
tempCtx.beginPath();
tempCtx.rect(0, 0, clipX, currentImage.height);
tempCtx.clip();
tempCtx.drawImage(
activeStamp.img,
stampX,
stampY,
stampWidth,
stampHeight
);
tempCtx.restore();
// 右半部分
tempCtx.save();
tempCtx.beginPath();
tempCtx.rect(clipX, 0, clipX, currentImage.height);
tempCtx.clip();
tempCtx.drawImage(
activeStamp.img,
stampX,
stampY,
stampWidth,
stampHeight
);
tempCtx.restore();
}
}
} else {
// 只有印章没有背景图的情况
tempCanvas.width = canvas.width;
tempCanvas.height = canvas.height;
tempCtx.fillStyle = '#ffffff';
tempCtx.fillRect(0, 0, tempCanvas.width, tempCanvas.height);
tempCtx.drawImage(canvas, 0, 0);
}
// 根据选择的格式