图片盖章工具

<!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>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
color: #333;
min-height: 100vh;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
}

.container {
width: 100%;
max-width: 1200px;
background-color: rgba(255, 255, 255, 0.95);
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
overflow: hidden;
margin: 20px auto;
}

header {
background: linear-gradient(90deg, #4b6cb7, #182848);
color: white;
padding: 25px;
text-align: center;
position: relative;
}

h1 {
font-size: 2.5rem;
margin-bottom: 10px;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}

.subtitle {
font-size: 1.1rem;
opacity: 0.9;
max-width: 600px;
margin: 0 auto;
}

.content {
display: flex;
padding: 20px;
flex-wrap: wrap;
}

.image-section {
flex: 1;
min-width: 300px;
padding: 20px;
background-color: #f8f9fa;
border-radius: 10px;
box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.1);
margin: 10px;
}

.controls {
flex: 0 0 300px;
padding: 20px;
background-color: #f8f9fa;
border-radius: 10px;
box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.1);
margin: 10px;
}

.stamp-preview {
width: 100%;
height: 300px;
border: 2px dashed #ccc;
border-radius: 10px;
margin-top: 20px;
display: flex;
align-items: center;
justify-content: center;
background-color: #fff;
overflow: hidden;
position: relative;
}

canvas {
max-width: 100%;
max-height: 100%;
cursor: move;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}

.control-group {
margin-bottom: 25px;
}

h2 {
font-size: 1.5rem;
margin-bottom: 15px;
color: #2c3e50;
border-bottom: 2px solid #3498db;
padding-bottom: 5px;
}

label {
display: block;
margin: 10px 0 5px;
font-weight: 500;
}

input[type="range"],
input[type="color"],
select {
width: 100%;
padding: 8px;
border-radius: 5px;
border: 1px solid #ddd;
background-color: white;
}

.stamp-selector {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 10px;
}

.stamp-option {
width: 60px;
height: 60px;
border: 2px solid #ddd;
border-radius: 50%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
transition: all 0.3s;
}

.stamp-option:hover, .stamp-option.active {
border-color: #3498db;
transform: scale(1.1);
box-shadow: 0 0 10px rgba(52, 152, 219, 0.5);
}

.button-group {
display: flex;
gap: 10px;
margin-top: 20px;
}

button {
flex: 1;
padding: 12px 20px;
border: none;
border-radius: 5px;
font-size: 1rem;
font-weight: bold;
cursor: pointer;
transition: all 0.3s;
}

.btn-primary {
background: linear-gradient(90deg, #3498db, #2980b9);
color: white;
}

.btn-success {
background: linear-gradient(90deg, #2ecc71, #27ae60);
color: white;
}

.btn-danger {
background: linear-gradient(90deg, #e_1bc9c, #d35400);
color: white;
}

button:hover {
transform: translateY(-3px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
}

button:active {
transform: translateY(1px);
}

footer {
text-align: center;
padding: 20px;
color: rgba(255, 255, 255, 0.8);
font-size: 0.9rem;
}

@media (max-width: 768px) {
.content {
flex-direction: column;
}

.controls {
flex: 1;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>图片盖章工具</h1>
<p class="subtitle">上传图片,添加自定义图章,调整位置和样式,导出最终效果</p>
</header>

<div class="content">
<div class="image-section">
<h2>图片预览</h2>
<div class="stamp-preview">
<canvas id="previewCanvas">您的浏览器不支持Canvas</canvas>
</div>

<div class="button-group">
<button id="uploadBtn" class="btn-primary">上传图片</button>
<button id="exportBtn" class="btn-success">导出图片</button>
</div>
</div>

<div class="controls">
<h2>图章设置</h2>

<div class="control-group">
<label for="stampType">图章样式</label>
<div class="stamp-selector">
<div class="stamp-option active" data-type="circle">圆章</div>
<div class="stamp-option" data-type="square">方章</div>
<div class="stamp-option" data-type="triangle">三角章</div>
<div class="stamp-option" data-type="star">星章</div>
</div>
</div>

<div class="control-group">
<label for="stampText">图章文字</label>
<input type="text" id="stampText" value="审核通过" maxlength="10">
</div>

<div class="control-group">
<label for="stampSize">图章大小: <span id="sizeValue">100</span>px</label>
<input type="range" id="stampSize" min="50" max="200" value="100">
</div>

<div class="control-group">
<label for="stampRotation">旋转角度: <span id="rotationValue">0</span>°</label>
<input type="range" id="stampRotation" min="0" max="360" value="0">
</div>

<div class="control-group">
<label for="stampOpacity">透明度: <span id="opacityValue">80</span>%</label>
<input type="range" id="stampOpacity" min="10" max="100" value="80">
</div>

<div class="control-group">
<label for="stampColor">图章颜色</label>
<input type="color" id="stampColor" value="#e74c3c">
</div>

<div class="button-group">
<button id="resetBtn" class="btn-danger">重置图章</button>
<button id="randomBtn" class="btn-primary">随机图章</button>
</div>
</div>
</div>
</div>

<footer>
<p>图片盖章工具 &copy; 2023 | 使用Canvas技术实现</p>
</footer>

<script>
// 获取DOM元素
const canvas = document.getElementById('previewCanvas');
const ctx = canvas.getContext('2d');
const uploadBtn = document.getElementById('uploadBtn');
const exportBtn = document.getElementById('exportBtn');
const resetBtn = document.getElementById('resetBtn');
const randomBtn = document.getElementById('randomBtn');
const stampText = document.getElementById('stampText');
const stampSize = document.getElementById('stampSize');
const stampRotation = document.getElementById('stampRotation');
const stampOpacity = document.getElementById('stampOpacity');
const stampColor = document.getElementById('stampColor');
const sizeValue = document.getElementById('sizeValue');
const rotationValue = document.getElementById('rotationValue');
const opacityValue = document.getElementById('opacityValue');
const stampOptions = document.querySelectorAll('.stamp-option');

// 初始状态
let img = null;
let stampType = 'circle';
let stampX = 200;
let stampY = 150;
let isDragging = false;
let startX, startY;

// 初始化Canvas
function initCanvas() {
canvas.width = 500;
canvas.height = 300;

// 绘制默认背景
ctx.fillStyle = '#f0f0f0';
ctx.fillRect(0, 0, canvas.width, canvas.height);

ctx.font = '20px Arial';
ctx.fillStyle = '#999';
ctx.textAlign = 'center';
ctx.fillText('上传图片或使用默认背景添加图章', canvas.width/2, canvas.height/2);
}

// 绘制图章
function drawStamp() {
if (!img) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#f0f0f0';
ctx.fillRect(0, 0, canvas.width, canvas.height);
} else {
// 绘制图片
ctx.clearRect(0, 0, canvas.width, canvas.height);
const scale = Math.min(canvas.width / img.width, canvas.height / img.height);
const width = img.width * scale;
const height = img.height * scale;
const x = (canvas.width - width) / 2;
const y = (canvas.height - height) / 2;

ctx.drawImage(img, x, y, width, height);
}

// 保存当前状态
ctx.save();

// 移动到图章中心并旋转
ctx.translate(stampX, stampY);
ctx.rotate(parseInt(stampRotation.value) * Math.PI / 180);

const size = parseInt(stampSize.value);
const opacity = parseInt(stampOpacity.value) / 100;
const color = stampColor.value;

// 绘制图章形状
ctx.globalAlpha = opacity;
ctx.fillStyle = color;
ctx.strokeStyle = color;
ctx.lineWidth = 3;

switch(stampType) {
case 'circle':
ctx.beginPath();
ctx.arc(0, 0, size/2, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
break;

case 'square':
ctx.fillRect(-size/2, -size/2, size, size);
ctx.strokeRect(-size/2, -size/2, size, size);
break;

case 'triangle':
ctx.beginPath();
ctx.moveTo(0, -size/2);
ctx.lineTo(-size/2, size/2);
ctx.lineTo(size/2, size/2);
ctx.closePath();
ctx.fill();
ctx.stroke();
break;

case 'star':
drawStar(ctx, 0, 0, 5, size/2, size/4);
ctx.fill();
ctx.stroke();
break;
}

// 绘制文字
const text = stampText.value || '盖章';
ctx.globalAlpha = 1;
ctx.fillStyle = 'white';
ctx.font = `bold ${Math.max(12, size/5)}px Arial`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(text, 0, 0);

// 恢复状态
ctx.restore();
}

// 绘制星形
function drawStar(ctx, cx, cy, spikes, outerRadius, innerRadius) {
let rot = Math.PI / 2 * 3;
let x = cx;
let y = cy;
let step = Math.PI / spikes;

ctx.beginPath();
ctx.moveTo(cx, cy - outerRadius);

for (let i = 0; i < spikes; i++) {
x = cx + Math.cos(rot) * outerRadius;
y = cy + Math.sin(rot) * outerRadius;
ctx.lineTo(x, y);
rot += step;

x = cx + Math.cos(rot) * innerRadius;
y = cy + Math.sin(rot) * innerRadius;
ctx.lineTo(x, y);
rot += step;
}

ctx.lineTo(cx, cy - outerRadius);
ctx.closePath();
}

// 事件监听器
stampOptions.forEach(option => {
option.addEventListener('click', () => {
stampOptions.forEach(opt => opt.classList.remove('active'));
option.classList.add('active');
stampType = option.dataset.type;
drawStamp();
});
});

stampSize.addEventListener('input', () => {
sizeValue.textContent = stampSize.value;
drawStamp();
});

stampRotation.addEventListener('input', () => {
rotationValue.textContent = stampRotation.value;
drawStamp();
});

stampOpacity.addEventListener('input', () => {
opacityValue.textContent = stampOpacity.value;
drawStamp();
});

stampColor.addEventListener('input', drawStamp);
stampText.addEventListener('input', drawStamp);

// 上传图片
uploadBtn.addEventListener('click', () => {
const input = document.createElement('input');
input.type = 'file';
input.accept = 'image/*';

input.onchange = e => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = event => {
img = new Image();
img.onload = () => {
// 设置Canvas尺寸适应图片
const maxWidth = canvas.parentElement.clientWidth;
const maxHeight = canvas.parentElement.clientHeight - 40;
const scale = Math.min(maxWidth / img.width, maxHeight / img.height);

canvas.width = img.width * scale;
canvas.height = img.height * scale;

// 初始图章位置居中
stampX = canvas.width / 2;
stampY = canvas.height / 2;

drawStamp();
};
img.src = event.target.result;
};
reader.readAsDataURL(file);
}
};

input.click();
});

// 导出图片
exportBtn.addEventListener('click', () => {
const link = document.createElement('a');
link.download = 'stamped-image.png';
link.href = canvas.toDataURL('image/png');
link.click();
});

// 重置图章
resetBtn.addEventListener('click', () => {
stampX = canvas.width / 2;
stampY = canvas.height / 2;
stampRotation.value = 0;
stampOpacity.value = 80;
stampSize.value = 100;
stampColor.value = '#e74c3c';
stampText.value = '审核通过';

rotationValue.textContent = '0';
opacityValue.textContent = '80';
sizeValue.textContent = '100';

document.querySelectorAll('.stamp-option').forEach(opt => opt.classList.remove('active'));
document.querySelector('.stamp-option[data-type="circle"]').classList.add('active');
stampType = 'circle';

drawStamp();
});

// 随机图章
randomBtn.addEventListener('click', () => {
const types = ['circle', 'square', 'triangle', 'star'];
const colors = ['#e74c3c', '#3498db', '#2ecc71', '#f39c12', '#9b59b6', '#1abc9c'];
const texts = ['审核通过', '已确认', '合格', '批准', 'VIP', '机密'];

stampType = types[Math.floor(Math.random() * types.length)];
stampText.value = texts[Math.floor(Math.random() * texts.length)];
stampSize.value = Math.floor(Math.random() * 100) + 50;
stampRotation.value = Math.floor(Math.random() * 360);
stampOpacity.value = Math.floor(Math.random() * 60) + 40;
stampColor.value = colors[Math.floor(Math.random() * colors.length)];

// 更新UI
sizeValue.textContent = stampSize.value;
rotationValue.textContent = stampRotation.value;
opacityValue.textContent = stampOpacity.value;

document.querySelectorAll('.stamp-option').forEach(opt => opt.classList.remove('active'));
document.querySelector(`.stamp-option[data-type="${stampType}"]`).classList.add('active');

drawStamp();
});

// 拖拽功能
canvas.addEventListener('mousedown', (e) => {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;

// 检查是否点击在图章上
const size = parseInt(stampSize.value);
const distance = Math.sqrt(Math.pow(x - stampX, 2) + Math.pow(y - stampY, 2));

if (distance <= size/2) {
isDragging = true;
startX = x - stampX;
startY = y - stampY;
}
});

canvas.addEventListener('mousemove', (e) => {
if (isDragging) {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;

stampX = x - startX;
stampY = y - startY;

// 限制在画布范围内
stampX = Math.max(0, Math.min(canvas.width, stampX));
stampY = Math.max(0, Math.min(canvas.height, stampY));

drawStamp();
}
});

canvas.addEventListener('mouseup', () => {
isDragging = false;
});

canvas.addEventListener('mouseleave', () => {
isDragging = false;
});

// 触摸事件支持
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
const rect = canvas.getBoundingClientRect();
const touch = e.touches[0];
const x = touch.clientX - rect.left;
const y = touch.clientY - rect.top;

const size = parseInt(stampSize.value);
const distance = Math.sqrt(Math.pow(x - stampX, 2) + Math.pow(y - stampY, 2));

if (distance <= size/2) {
isDragging = true;
startX = x - stampX;
startY = y - stampY;
}
});

canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
if (isDragging) {
const rect = canvas.getBoundingClientRect();
const touch = e.touches[0];
const x = touch.clientX - rect.left;
const y = touch.clientY - rect.top;

stampX = x - startX;
stampY = y - startY;

stampX = Math.max(0, Math.min(canvas.width, stampX));
stampY = Math.max(0, Math.min(canvas.height, stampY));

drawStamp();
}
});

canvas.addEventListener('touchend', () => {
isDragging = false;
});

// 初始化
initCanvas();
drawStamp();
</script>
</body>
</html>
posted @ 2026-01-14 13:10  外行的小白  阅读(11)  评论(0)    收藏  举报