<!
DOCTYPE
html>
<
html
lang
=
"zh"
>
<
head
>
<
meta
charset
=
"UTF-8"
>
<
meta
name
=
"viewport"
content
=
"width=device-width, initial-scale=1.0"
>
<
title
>🌈✨ 可爱身份证水印工具 ✨🌈</
title
>
<
link
href
=
"https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap"
rel
=
"stylesheet"
>
<
style
>
:root {
--primary-color: #FF85A2;
--secondary-color: #FFC2D1;
--accent-color: #7CB9E8;
--text-color: #5E5E5E;
--light-bg: #FFF0F5;
--card-bg: #FFFFFF;
--success-color: #A0E7A0;
--warning-color: #FFD700;
--shadow: 0 8px 20px rgba(255, 133, 162, 0.2);
--border-radius: 16px;
}
* {
box-sizing: border-box;
transition: all 0.3s ease;
}
body {
font-family: 'Nunito', Arial, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
margin: 0;
padding: 20px;
background-color: var(--light-bg);
background-image:
radial-gradient(circle at 10% 20%, rgba(255, 194, 209, 0.3) 0%, transparent 20%),
radial-gradient(circle at 90% 30%, rgba(124, 185, 232, 0.3) 0%, transparent 20%),
radial-gradient(circle at 50% 80%, rgba(160, 231, 160, 0.3) 0%, transparent 20%);
color: var(--text-color);
min-height: 100vh;
}
.container {
background-color: var(--card-bg);
padding: 30px;
border-radius: var(--border-radius);
box-shadow: var(--shadow);
display: flex;
flex-direction: column;
align-items: center;
max-width: 800px;
width: 100%;
margin-top: 20px;
position: relative;
overflow: hidden;
}
.container::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
height: 8px;
background: linear-gradient(90deg, var(--primary-color), var(--accent-color));
border-radius: var(--border-radius) var(--border-radius) 0 0;
}
h1 {
color: var(--primary-color);
margin-bottom: 25px;
text-align: center;
font-weight: 700;
font-size: 2.2rem;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
}
.emoji-title {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
flex-wrap: wrap;
}
.emoji-title .emoji {
font-size: 2.5rem;
animation: bounce 2s infinite alternate;
}
@keyframes bounce {
0% { transform: translateY(0); }
100% { transform: translateY(-10px); }
}
.upload-area {
border: 3px dashed var(--secondary-color);
border-radius: var(--border-radius);
padding: 30px;
text-align: center;
width: 100%;
margin-bottom: 20px;
cursor: pointer;
position: relative;
overflow: hidden;
background-color: rgba(255, 240, 245, 0.5);
}
.upload-area:hover {
border-color: var(--primary-color);
background-color: rgba(255, 240, 245, 0.8);
transform: translateY(-5px);
box-shadow: 0 10px 25px rgba(255, 133, 162, 0.3);
}
.upload-icon {
font-size: 3rem;
margin-bottom: 10px;
color: var(--primary-color);
}
.upload-text {
font-size: 1.2rem;
color: var(--text-color);
margin-bottom: 10px;
}
.file-input {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
cursor: pointer;
}
.input-group {
width: 100%;
margin-bottom: 15px;
position: relative;
}
.cute-input {
width: 100%;
padding: 12px 20px;
border: 2px solid var(--secondary-color);
border-radius: 25px;
font-size: 1rem;
color: var(--text-color);
background-color: #FFFFFF;
outline: none;
font-family: 'Nunito', sans-serif;
}
.cute-input:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(255, 133, 162, 0.2);
}
.input-icon {
position: absolute;
right: 15px;
top: 50%;
transform: translateY(-50%);
color: var(--primary-color);
font-size: 1.2rem;
}
.options-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
width: 100%;
margin-bottom: 20px;
}
.option-group {
display: flex;
flex-direction: column;
background-color: rgba(255, 240, 245, 0.5);
padding: 15px;
border-radius: var(--border-radius);
border: 2px solid var(--secondary-color);
}
.option-group:hover {
border-color: var(--primary-color);
transform: translateY(-3px);
box-shadow: 0 5px 15px rgba(255, 133, 162, 0.2);
}
.option-label {
display: flex;
align-items: center;
margin-bottom: 8px;
font-weight: 600;
color: var(--primary-color);
}
.option-label .emoji {
margin-right: 8px;
font-size: 1.2rem;
}
.cute-select, .cute-range, .cute-number {
width: 100%;
padding: 10px 15px;
border: 2px solid var(--secondary-color);
border-radius: 20px;
font-size: 0.9rem;
color: var(--text-color);
background-color: #FFFFFF;
outline: none;
font-family: 'Nunito', sans-serif;
}
.cute-select:focus, .cute-range:focus, .cute-number:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(255, 133, 162, 0.2);
}
.cute-range {
-webkit-appearance: none;
height: 10px;
background: linear-gradient(90deg, var(--secondary-color), var(--primary-color));
border-radius: 5px;
outline: none;
padding: 0;
}
.cute-range::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--primary-color);
cursor: pointer;
border: 2px solid white;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
}
.cute-range::-moz-range-thumb {
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--primary-color);
cursor: pointer;
border: 2px solid white;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
}
.cute-button {
padding: 12px 25px;
background: linear-gradient(45deg, var(--primary-color), var(--accent-color));
color: white;
border: none;
border-radius: 25px;
cursor: pointer;
font-size: 1.1rem;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
box-shadow: 0 4px 15px rgba(255, 133, 162, 0.3);
margin: 10px;
transition: all 0.3s ease;
font-family: 'Nunito', sans-serif;
}
.cute-button:hover {
transform: translateY(-3px);
box-shadow: 0 6px 20px rgba(255, 133, 162, 0.4);
}
.cute-button:active {
transform: translateY(1px);
box-shadow: 0 2px 10px rgba(255, 133, 162, 0.3);
}
.button-container {
display: flex;
justify-content: center;
margin: 20px 0;
flex-wrap: wrap;
gap: 10px;
}
.canvas-container {
position: relative;
margin: 20px 0;
border-radius: var(--border-radius);
overflow: hidden;
box-shadow: var(--shadow);
background-color: #f0f0f0;
background-image:
linear-gradient(45deg, #e0e0e0 25%, transparent 25%),
linear-gradient(-45deg, #e0e0e0 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, #e0e0e0 75%),
linear-gradient(-45deg, transparent 75%, #e0e0e0 75%);
background-size: 20px 20px;
background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
width: 100%;
max-width: 600px;
}
#canvas {
display: block;
max-width: 100%;
height: auto;
}
.security-notice {
background-color: var(--success-color);
color: #2E7D32;
padding: 15px;
border-radius: var(--border-radius);
margin-top: 20px;
text-align: center;
font-size: 0.95rem;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
width: 100%;
box-shadow: 0 4px 10px rgba(160, 231, 160, 0.3);
flex-wrap: wrap;
}
.tooltip {
position: relative;
display: inline-block;
margin-left: 5px;
cursor: help;
}
.tooltip .tooltip-text {
visibility: hidden;
width: 220px;
background-color: #333;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 8px;
position: absolute;
z-index: 1;
bottom: 125%;
left: 50%;
transform: translateX(-50%);
opacity: 0;
transition: opacity 0.3s;
font-size: 0.85rem;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
}
.tooltip:hover .tooltip-text {
visibility: visible;
opacity: 1;
}
.loading {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.8);
display: flex;
justify-content: center;
align-items: center;
z-index: 10;
border-radius: var(--border-radius);
display: none;
}
.loading-spinner {
width: 50px;
height: 50px;
border: 5px solid var(--secondary-color);
border-top: 5px solid var(--primary-color);
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.footer {
margin-top: 30px;
text-align: center;
color: var(--text-color);
font-size: 0.9rem;
display: flex;
flex-direction: column;
align-items: center;
gap: 5px;
}
.heart {
color: var(--primary-color);
animation: heartbeat 1.5s infinite;
font-size: 1.2rem;
}
@keyframes heartbeat {
0% { transform: scale(1); }
5% { transform: scale(1.2); }
10% { transform: scale(1); }
15% { transform: scale(1.2); }
20% { transform: scale(1); }
100% { transform: scale(1); }
}
/* 彩虹文字效果 */
.rainbow-text {
background-image: linear-gradient(90deg,
#FF85A2, #FFA07A, #FFD700, #A0E7A0, #7CB9E8, #9370DB, #FF85A2);
background-size: 600% 100%;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
animation: rainbow 6s linear infinite;
}
@keyframes rainbow {
0% { background-position: 0% 50%; }
100% { background-position: 100% 50%; }
}
/* 可爱的复选框 */
.cute-checkbox {
display: flex;
align-items: center;
margin-top: 10px;
}
.cute-checkbox input[type="checkbox"] {
display: none;
}
.cute-checkbox label {
display: flex;
align-items: center;
cursor: pointer;
user-select: none;
color: var(--text-color);
}
.cute-checkbox .checkbox-custom {
width: 24px;
height: 24px;
border: 2px solid var(--secondary-color);
border-radius: 6px;
margin-right: 10px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
background-color: white;
}
.cute-checkbox input[type="checkbox"]:checked + label .checkbox-custom {
background-color: var(--primary-color);
border-color: var(--primary-color);
}
.cute-checkbox input[type="checkbox"]:checked + label .checkbox-custom::after {
content: "✓";
color: white;
font-size: 16px;
font-weight: bold;
}
/* 气泡效果 */
.bubble {
position: absolute;
border-radius: 50%;
background: rgba(255, 255, 255, 0.6);
animation: float 4s infinite ease-in-out;
z-index: -1;
}
@keyframes float {
0% { transform: translateY(0) rotate(0deg); }
50% { transform: translateY(-20px) rotate(180deg); }
100% { transform: translateY(0) rotate(360deg); }
}
/* 颜色选择器样式 */
.color-picker {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-top: 10px;
justify-content: center;
}
.color-option {
width: 30px;
height: 30px;
border-radius: 50%;
cursor: pointer;
border: 2px solid #ccc;
transition: all 0.2s;
}
.color-option.selected {
border: 2px solid var(--primary-color);
transform: scale(1.1);
box-shadow: 0 0 10px rgba(255, 133, 162, 0.4);
}
/* 预设模板选择器 */
.templates {
display: flex;
gap: 15px;
flex-wrap: wrap;
margin-top: 15px;
justify-content: center;
}
.template {
width: 100px;
height: 60px;
border-radius: 10px;
cursor: pointer;
border: 2px solid #ccc;
overflow: hidden;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.8rem;
color: white;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
background-size: cover;
background-position: center;
}
.template:hover {
transform: translateY(-3px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
.template.selected {
border: 2px solid var(--primary-color);
transform: scale(1.05);
box-shadow: 0 5px 15px rgba(255, 133, 162, 0.3);
}
/* 提示气泡 */
.tip-bubble {
position: fixed;
background-color: #FFF9C4;
color: #5D4037;
padding: 10px 15px;
border-radius: 10px;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
font-size: 0.9rem;
max-width: 250px;
z-index: 100;
animation: tipAppear 0.3s forwards;
display: none;
text-align: center;
}
.tip-bubble::after {
content: '';
position: absolute;
bottom: -10px;
left: 50%;
transform: translateX(-50%);
border-width: 10px 10px 0;
border-style: solid;
border-color: #FFF9C4 transparent transparent;
}
@keyframes tipAppear {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* 进度条样式 */
.progress-container {
width: 100%;
height: 10px;
background-color: #f0f0f0;
border-radius: 5px;
margin: 10px 0;
overflow: hidden;
}
.progress-bar {
height: 100%;
width: 0%;
background: linear-gradient(90deg, var(--primary-color), var(--accent-color));
border-radius: 5px;
transition: width 0.3s ease;
}
/* 主题切换按钮 */
.theme-toggle {
position: absolute;
top: 20px;
right: 20px;
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
z-index: 10;
transition: transform 0.3s ease;
}
.theme-toggle:hover {
transform: rotate(30deg);
}
/* 暗色主题 */
body.dark-theme {
--primary-color: #FF85A2;
--secondary-color: #FF5C8D;
--accent-color: #7CB9E8;
--text-color: #E0E0E0;
--light-bg: #2D2D2D;
--card-bg: #3D3D3D;
--success-color: #66BB6A;
--warning-color: #FFCA28;
background-color: var(--light-bg);
}
body.dark-theme .container {
background-color: var(--card-bg);
}
body.dark-theme .option-group {
background-color: rgba(255, 133, 162, 0.1);
}
body.dark-theme .security-notice {
background-color: rgba(102, 187, 106, 0.2);
color: #A5D6A7;
}
/* 动画效果 */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.fade-in {
animation: fadeIn 0.5s forwards;
}
/* 水印预览 */
.watermark-preview {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 5;
display: none;
}
/* 分辨率调整部分 */
.resolution-controls {
display: flex;
flex-direction: column;
width: 100%;
margin-top: 15px;
background-color: rgba(255, 240, 245, 0.5);
padding: 15px;
border-radius: var(--border-radius);
border: 2px solid var(--secondary-color);
}
.resolution-controls:hover {
border-color: var(--primary-color);
transform: translateY(-3px);
box-shadow: 0 5px 15px rgba(255, 133, 162, 0.2);
}
.resolution-title {
display: flex;
align-items: center;
margin-bottom: 10px;
font-weight: 600;
color: var(--primary-color);
}
.resolution-title .emoji {
margin-right: 8px;
font-size: 1.2rem;
}
.resolution-inputs {
display: flex;
flex-wrap: wrap;
gap: 15px;
align-items: center;
justify-content: center;
}
.resolution-input-group {
display: flex;
flex-direction: column;
width: 45%;
min-width: 150px;
}
.resolution-input-group label {
margin-bottom: 5px;
font-size: 0.9rem;
color: var(--text-color);
}
.resolution-info {
margin-top: 10px;
font-size: 0.85rem;
color: var(--text-color);
text-align: center;
}
.resolution-badge {
display: inline-block;
background-color: var(--accent-color);
color: white;
padding: 3px 8px;
border-radius: 12px;
font-size: 0.8rem;
margin-left: 5px;
font-weight: bold;
}
/* 响应式设计增强 */
@media (max-width: 768px) {
.container {
padding: 20px 15px;
margin-top: 10px;
border-radius: 12px;
}
h1 {
font-size: 1.6rem;
margin-bottom: 15px;
}
.emoji-title .emoji {
font-size: 2rem;
}
.options-container {
grid-template-columns: 1fr;
gap: 15px;
}
.cute-button {
padding: 10px 15px;
font-size: 0.95rem;
margin: 5px;
}
.upload-area {
padding: 20px 15px;
}
.upload-icon {
font-size: 2.5rem;
}
.upload-text {
font-size: 1rem;
}
.templates {
gap: 10px;
}
.template {
width: 80px;
height: 50px;
font-size: 0.7rem;
}
.security-notice {
padding: 10px;
font-size: 0.85rem;
}
.theme-toggle {
top: 10px;
right: 10px;
font-size: 1.3rem;
}
.color-picker {
gap: 8px;
}
.color-option {
width: 25px;
height: 25px;
}
.cute-checkbox label {
font-size: 0.9rem;
}
.cute-checkbox .checkbox-custom {
width: 20px;
height: 20px;
}
.resolution-inputs {
flex-direction: column;
gap: 10px;
}
.resolution-input-group {
width: 100%;
}
}
@media (max-width: 480px) {
body {
padding: 10px;
}
.container {
padding: 15px 10px;
}
h1 {
font-size: 1.4rem;
}
.emoji-title .emoji {
font-size: 1.8rem;
}
.button-container {
flex-direction: column;
align-items: center;
}
.cute-button {
width: 100%;
max-width: 250px;
}
.template {
width: 70px;
height: 45px;
font-size: 0.65rem;
}
.option-label {
font-size: 0.9rem;
}
.option-label .emoji {
font-size: 1rem;
}
.footer {
font-size: 0.8rem;
}
.bubble {
display: none;
}
}
/* 触摸设备优化 */
@media (hover: none) {
.cute-button:hover {
transform: none;
box-shadow: 0 4px 15px rgba(255, 133, 162, 0.3);
}
.option-group:hover {
transform: none;
border-color: var(--secondary-color);
}
.upload-area:hover {
transform: none;
border-color: var(--secondary-color);
background-color: rgba(255, 240, 245, 0.5);
}
.template:hover {
transform: none;
box-shadow: none;
}
/* 添加触摸反馈 */
.cute-button:active,
.option-group:active,
.upload-area:active,
.template:active,
.color-option:active {
transform: scale(0.98);
}
}
/* 打印样式 */
@media print {
body {
background: none;
padding: 0;
}
.container {
box-shadow: none;
padding: 0;
}
.button-container,
.options-container,
.upload-area,
.theme-toggle,
.bubble,
.footer {
display: none;
}
.canvas-container {
box-shadow: none;
background: none;
}
.security-notice {
border: 1px solid #2E7D32;
background: none;
color: #2E7D32;
}
}
/* 屏幕阅读器辅助 */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
/* 加载动画增强 */
.loading-text {
position: absolute;
bottom: -30px;
font-size: 0.9rem;
color: var(--primary-color);
text-align: center;
width: 100%;
}
/* 安全提示增强 */
.security-badge {
display: inline-flex;
align-items: center;
justify-content: center;
background-color: #2E7D32;
color: white;
padding: 3px 8px;
border-radius: 12px;
font-size: 0.8rem;
margin-left: 5px;
font-weight: bold;
}
</
style
>
</
head
>
<
body
>
<
div
class
=
"sr-only"
>可爱身份证水印工具,用于在身份证照片上添加水印以保护个人信息安全</
div
>
<
div
class
=
"bubble"
style
=
"width: 30px; height: 30px; top: 10%; left: 10%; animation-delay: 0s;"
></
div
>
<
div
class
=
"bubble"
style
=
"width: 20px; height: 20px; top: 20%; left: 80%; animation-delay: 1s;"
></
div
>
<
div
class
=
"bubble"
style
=
"width: 40px; height: 40px; top: 70%; left: 15%; animation-delay: 2s;"
></
div
>
<
div
class
=
"bubble"
style
=
"width: 25px; height: 25px; top: 80%; left: 70%; animation-delay: 3s;"
></
div
>
<
div
class
=
"bubble"
style
=
"width: 35px; height: 35px; top: 40%; left: 90%; animation-delay: 1.5s;"
></
div
>
<
button
class
=
"theme-toggle"
id
=
"theme-toggle"
aria-label
=
"切换主题颜色"
>🌙</
button
>
<
div
class
=
"container"
>
<
div
class
=
"emoji-title"
>
<
span
class
=
"emoji"
role
=
"img"
aria-label
=
"彩虹"
>🌈</
span
>
<
h1
><
span
class
=
"rainbow-text"
>可爱身份证水印工具</
span
></
h1
>
<
span
class
=
"emoji"
role
=
"img"
aria-label
=
"闪光"
>✨</
span
>
</
div
>
<
div
class
=
"upload-area"
id
=
"upload-area"
role
=
"button"
aria-label
=
"点击或拖放图片到这里"
>
<
div
class
=
"upload-icon"
role
=
"img"
aria-hidden
=
"true"
>📷</
div
>
<
div
class
=
"upload-text"
>点击或拖放图片到这里</
div
>
<
div
style
=
"font-size: 0.9rem; color: #888;"
>支持 JPG, PNG, GIF 格式</
div
>
<
input
type
=
"file"
id
=
"upload"
accept
=
"image/*"
class
=
"file-input"
aria-label
=
"上传图片"
>
</
div
>
<
div
class
=
"input-group"
>
<
input
type
=
"text"
id
=
"watermark-text"
placeholder
=
"输入水印文字 (例如: 仅用于办理XX业务)"
class
=
"cute-input"
aria-label
=
"水印文字"
>
<
div
class
=
"input-icon"
role
=
"img"
aria-hidden
=
"true"
>✏️</
div
>
</
div
>
<
div
class
=
"option-group"
style
=
"width: 100%;"
>
<
div
class
=
"option-label"
>
<
span
class
=
"emoji"
role
=
"img"
aria-hidden
=
"true"
>📝</
span
> 快速选择常用水印文字
</
div
>
<
div
class
=
"templates"
role
=
"radiogroup"
aria-label
=
"水印文字模板"
>
<
div
class
=
"template"
style
=
"background: linear-gradient(45deg, #FF5555, #FF8888);"
data-text
=
"仅用于XX银行办理业务"
role
=
"radio"
aria-label
=
"银行模板"
>
<
span
>🏦 银行</
span
>
</
div
>
<
div
class
=
"template"
style
=
"background: linear-gradient(45deg, #5555FF, #8888FF);"
data-text
=
"仅用于XX公司入职登记"
role
=
"radio"
aria-label
=
"入职模板"
>
<
span
>💼 入职</
span
>
</
div
>
<
div
class
=
"template"
style
=
"background: linear-gradient(45deg, #55AA55, #88CC88);"
data-text
=
"仅用于XX学校注册登记"
role
=
"radio"
aria-label
=
"学校模板"
>
<
span
>🏫 学校</
span
>
</
div
>
<
div
class
=
"template"
style
=
"background: linear-gradient(45deg, #FFAA55, #FFCC88);"
data-text
=
"仅用于XX部门审核"
role
=
"radio"
aria-label
=
"审核模板"
>
<
span
>📋 审核</
span
>
</
div
>
</
div
>
</
div
>
<
div
class
=
"options-container"
>
<
div
class
=
"option-group"
>
<
div
class
=
"option-label"
>
<
span
class
=
"emoji"
role
=
"img"
aria-hidden
=
"true"
>🔤</
span
> 文字大小
</
div
>
<
select
id
=
"font-size"
class
=
"cute-select"
aria-label
=
"文字大小"
>
<
option
value
=
"12"
>小 (12px)</
option
>
<
option
value
=
"16"
>中 (16px)</
option
>
<
option
value
=
"20"
selected>大 (20px)</
option
>
<
option
value
=
"24"
>超大 (24px)</
option
>
<
option
value
=
"32"
>巨大 (32px)</
option
>
</
select
>
</
div
>
<
div
class
=
"option-group"
>
<
div
class
=
"option-label"
>
<
span
class
=
"emoji"
role
=
"img"
aria-hidden
=
"true"
>🔄</
span
> 旋转角度
</
div
>
<
input
type
=
"number"
id
=
"rotation"
value
=
"-45"
step
=
"5"
min
=
"-90"
max
=
"90"
class
=
"cute-number"
aria-label
=
"旋转角度"
>
<
div
style
=
"display: flex; justify-content: space-between; margin-top: 5px; font-size: 0.8rem;"
>
<
span
>↙️ -90°</
span
>
<
span
>0°</
span
>
<
span
>90° ↗️</
span
>
</
div
>
</
div
>
<
div
class
=
"option-group"
>
<
div
class
=
"option-label"
>
<
span
class
=
"emoji"
role
=
"img"
aria-hidden
=
"true"
>🔤</
span
> 字体样式
</
div
>
<
select
id
=
"font-family"
class
=
"cute-select"
aria-label
=
"字体样式"
>
<
option
value
=
"Arial"
selected>Arial</
option
>
<
option
value
=
"'Nunito', sans-serif"
>Nunito</
option
>
<
option
value
=
"'Comic Sans MS', cursive"
>Comic Sans</
option
>
<
option
value
=
"'Times New Roman', serif"
>Times New Roman</
option
>
<
option
value
=
"'Courier New', monospace"
>Courier New</
option
>
</
select
>
</
div
>
<
div
class
=
"option-group"
>
<
div
class
=
"option-label"
>
<
span
class
=
"emoji"
role
=
"img"
aria-hidden
=
"true"
>🔍</
span
> 透明度
</
div
>
<
input
type
=
"range"
id
=
"opacity"
min
=
"0"
max
=
"100"
value
=
"50"
class
=
"cute-range"
aria-label
=
"透明度"
>
<
div
style
=
"display: flex; justify-content: space-between; margin-top: 5px; font-size: 0.8rem;"
>
<
span
>透明 👻</
span
>
<
span
>不透明 🔍</
span
>
</
div
>
</
div
>
<
div
class
=
"option-group"
>
<
div
class
=
"option-label"
>
<
span
class
=
"emoji"
role
=
"img"
aria-hidden
=
"true"
>↔️</
span
> 水平间距
</
div
>
<
input
type
=
"number"
id
=
"horizontal-spacing"
value
=
"100"
min
=
"10"
max
=
"300"
step
=
"10"
class
=
"cute-number"
aria-label
=
"水平间距"
>
<
div
class
=
"progress-container"
>
<
div
class
=
"progress-bar"
id
=
"horizontal-progress"
style
=
"width: 33%;"
role
=
"progressbar"
aria-valuenow
=
"33"
aria-valuemin
=
"0"
aria-valuemax
=
"100"
></
div
>
</
div
>
</
div
>
<
div
class
=
"option-group"
>
<
div
class
=
"option-label"
>
<
span
class
=
"emoji"
role
=
"img"
aria-hidden
=
"true"
>↕️</
span
> 行间距
</
div
>
<
input
type
=
"number"
id
=
"vertical-spacing"
value
=
"100"
min
=
"10"
max
=
"300"
step
=
"10"
class
=
"cute-number"
aria-label
=
"行间距"
>
<
div
class
=
"progress-container"
>
<
div
class
=
"progress-bar"
id
=
"vertical-progress"
style
=
"width: 33%;"
role
=
"progressbar"
aria-valuenow
=
"33"
aria-valuemin
=
"0"
aria-valuemax
=
"100"
></
div
>
</
div
>
</
div
>
</
div
>
<
div
class
=
"option-group"
style
=
"width: 100%;"
>
<
div
class
=
"option-label"
>
<
span
class
=
"emoji"
role
=
"img"
aria-hidden
=
"true"
>🎨</
span
> 水印颜色
</
div
>
<
div
class
=
"color-picker"
role
=
"radiogroup"
aria-label
=
"水印颜色选择"
>
<
div
class
=
"color-option selected"
style
=
"background-color: white;"
data-color
=
"white"
role
=
"radio"
aria-label
=
"白色"
aria-checked
=
"true"
></
div
>
<
div
class
=
"color-option"
style
=
"background-color: black;"
data-color
=
"black"
role
=
"radio"
aria-label
=
"黑色"
aria-checked
=
"false"
></
div
>
<
div
class
=
"color-option"
style
=
"background-color: #FF5555;"
data-color
=
"#FF5555"
role
=
"radio"
aria-label
=
"红色"
aria-checked
=
"false"
></
div
>
<
div
class
=
"color-option"
style
=
"background-color: #5555FF;"
data-color
=
"#5555FF"
role
=
"radio"
aria-label
=
"蓝色"
aria-checked
=
"false"
></
div
>
<
div
class
=
"color-option"
style
=
"background-color: #55AA55;"
data-color
=
"#55AA55"
role
=
"radio"
aria-label
=
"绿色"
aria-checked
=
"false"
></
div
>
<
div
class
=
"color-option"
style
=
"background-color: #FF55FF;"
data-color
=
"#FF55FF"
role
=
"radio"
aria-label
=
"粉色"
aria-checked
=
"false"
></
div
>
<
div
class
=
"color-option"
style
=
"background-color: #FFAA55;"
data-color
=
"#FFAA55"
role
=
"radio"
aria-label
=
"橙色"
aria-checked
=
"false"
></
div
>
<
div
class
=
"color-option"
style
=
"background-color: #55FFFF;"
data-color
=
"#55FFFF"
role
=
"radio"
aria-label
=
"青色"
aria-checked
=
"false"
></
div
>
</
div
>
</
div
>
<
div
class
=
"resolution-controls"
>
<
div
class
=
"resolution-title"
>
<
span
class
=
"emoji"
role
=
"img"
aria-hidden
=
"true"
>📐</
span
> 图片分辨率调整
</
div
>
<
div
class
=
"resolution-inputs"
>
<
div
class
=
"resolution-input-group"
>
<
label
for
=
"image-width"
>宽度 (像素) <
span
class
=
"emoji"
role
=
"img"
aria-hidden
=
"true"
>↔️</
span
></
label
>
<
input
type
=
"number"
id
=
"image-width"
class
=
"cute-number"
min
=
"100"
max
=
"4000"
step
=
"10"
value
=
"1000"
aria-label
=
"图片宽度"
>
</
div
>
<
div
class
=
"resolution-input-group"
>
<
label
for
=
"image-height"
>高度 (像素) <
span
class
=
"emoji"
role
=
"img"
aria-hidden
=
"true"
>↕️</
span
></
label
>
<
input
type
=
"number"
id
=
"image-height"
class
=
"cute-number"
min
=
"100"
max
=
"4000"
step
=
"10"
value
=
"600"
aria-label
=
"图片高度"
>
</
div
>
</
div
>
<
div
class
=
"cute-checkbox"
>
<
input
type
=
"checkbox"
id
=
"maintain-ratio"
checked>
<
label
for
=
"maintain-ratio"
>
<
div
class
=
"checkbox-custom"
aria-hidden
=
"true"
></
div
>
<
span
>🔒 保持原始比例</
span
>
</
label
>
</
div
>
<
div
class
=
"resolution-info"
>
当前分辨率: <
span
id
=
"current-resolution"
>1000 × 600</
span
>
<
span
class
=
"resolution-badge"
id
=
"resolution-quality"
>高清</
span
>
</
div
>
</
div
>
<
div
class
=
"cute-checkbox"
>
<
input
type
=
"checkbox"
id
=
"add-date"
checked>
<
label
for
=
"add-date"
>
<
div
class
=
"checkbox-custom"
aria-hidden
=
"true"
></
div
>
<
span
>📅 添加日期时间戳</
span
> <
span
style
=
"color: var(--primary-color); margin-left: 5px;"
>(增加安全性)</
span
>
</
label
>
</
div
>
<
div
class
=
"cute-checkbox"
>
<
input
type
=
"checkbox"
id
=
"add-emoji"
checked>
<
label
for
=
"add-emoji"
>
<
div
class
=
"checkbox-custom"
aria-hidden
=
"true"
></
div
>
<
span
>😊 添加可爱表情</
span
> <
span
style
=
"color: var(--primary-color); margin-left: 5px;"
>(增加趣味性)</
span
>
</
label
>
</
div
>
<
div
class
=
"button-container"
>
<
button
id
=
"preview-watermark"
class
=
"cute-button"
style
=
"background: linear-gradient(45deg, #9C27B0, #673AB7);"
aria-label
=
"预览水印效果"
>
<
span
role
=
"img"
aria-hidden
=
"true"
>👁️</
span
> 预览水印
</
button
>
<
button
id
=
"add-watermark"
class
=
"cute-button"
aria-label
=
"添加水印"
>
<
span
role
=
"img"
aria-hidden
=
"true"
>✨</
span
> 添加水印 <
span
role
=
"img"
aria-hidden
=
"true"
>✨</
span
>
</
button
>
<
button
id
=
"download"
class
=
"cute-button"
style
=
"display: none; background: linear-gradient(45deg, var(--success-color), var(--accent-color));"
aria-label
=
"下载图片"
>
<
span
role
=
"img"
aria-hidden
=
"true"
>💾</
span
> 下载图片
</
button
>
<
button
id
=
"reset"
class
=
"cute-button"
style
=
"background: linear-gradient(45deg, #888, #aaa);"
aria-label
=
"重置所有设置"
>
<
span
role
=
"img"
aria-hidden
=
"true"
>🔄</
span
> 重置
</
button
>
</
div
>
<
div
class
=
"canvas-container"
aria-label
=
"图片预览区域"
>
<
div
class
=
"loading"
id
=
"loading"
aria-hidden
=
"true"
>
<
div
class
=
"loading-spinner"
></
div
>
<
div
class
=
"loading-text"
>处理中,请稍候... 🐱</
div
>
</
div
>
<
div
class
=
"watermark-preview"
id
=
"watermark-preview"
aria-hidden
=
"true"
></
div
>
<
canvas
id
=
"canvas"
width
=
"337"
height
=
"213"
aria-label
=
"水印图片画布"
></
canvas
>
</
div
>
<
div
class
=
"security-notice"
role
=
"alert"
>
<
span
style
=
"font-size: 1.5rem;"
role
=
"img"
aria-hidden
=
"true"
>🔒</
span
>
<
div
>
<
strong
>安全提示:</
strong
>所有处理在您的浏览器中完成,图片不会上传到任何服务器
<
span
class
=
"security-badge"
>本地处理</
span
>
<
div
class
=
"tooltip"
>ℹ️
<
span
class
=
"tooltip-text"
>本工具完全在您的设备上运行,不会将您的身份证图片或个人信息发送到互联网上,保护您的隐私安全</
span
>
</
div
>
</
div
>
</
div
>
</
div
>
<
div
class
=
"footer"
role
=
"contentinfo"
>
<
div
>使用 <
span
class
=
"heart"
role
=
"img"
aria-label
=
"爱心"
>❤️</
span
> 制作 | 保护您的个人信息安全</
div
>
<
div
style
=
"font-size: 0.8rem; margin-top: 5px;"
>🛡️ 身份证等敏感证件添加水印可有效防止被盗用</
div
>
</
div
>
<
div
class
=
"tip-bubble"
id
=
"tip-bubble"
role
=
"status"
aria-live
=
"polite"
></
div
>
<
script
>
let originalImage = null;
let selectedColor = 'white';
let originalWidth = 0;
let originalHeight = 0;
let aspectRatio = 1;
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const loading = document.getElementById('loading');
const watermarkPreview = document.getElementById('watermark-preview');
const tipBubble = document.getElementById('tip-bubble');
const imageWidthInput = document.getElementById('image-width');
const imageHeightInput = document.getElementById('image-height');
const maintainRatioCheckbox = document.getElementById('maintain-ratio');
const currentResolutionSpan = document.getElementById('current-resolution');
const resolutionQualityBadge = document.getElementById('resolution-quality');
// 检测设备类型
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
if (isMobile) {
document.body.classList.add('mobile-device');
}
// 主题切换
const themeToggle = document.getElementById('theme-toggle');
themeToggle.addEventListener('click', function() {
document.body.classList.toggle('dark-theme');
if (document.body.classList.contains('dark-theme')) {
themeToggle.textContent = '☀️';
themeToggle.setAttribute('aria-label', '切换到亮色主题');
showTip('已切换到暗色主题 🌙', themeToggle);
} else {
themeToggle.textContent = '🌙';
themeToggle.setAttribute('aria-label', '切换到暗色主题');
showTip('已切换到亮色主题 ☀️', themeToggle);
}
});
// 显示提示气泡
function showTip(message, element, duration = 2000) {
const rect = element.getBoundingClientRect();
tipBubble.textContent = message;
tipBubble.style.display = 'block';
// 移动设备上的位置调整
if (isMobile) {
tipBubble.style.top = '50%';
tipBubble.style.left = '50%';
tipBubble.style.transform = 'translate(-50%, -50%)';
} else {
tipBubble.style.top = (rect.top - 50) + 'px';
tipBubble.style.left = (rect.left + rect.width / 2) + 'px';
tipBubble.style.transform = 'translateX(-50%)';
}
setTimeout(() => {
tipBubble.style.display = 'none';
}, duration);
}
// 更新分辨率质量标签
function updateResolutionQuality(width, height) {
const pixels = width * height;
let quality = '';
let color = '';
if (pixels <
300000
) { // 小于0.3MP
quality
=
'低清'
;
color
=
'#FF5555'
;
} else if (pixels < 1000000) { // 小于1MP
quality
=
'标清'
;
color
=
'#FFAA55'
;
} else if (pixels < 2000000) { // 小于2MP
quality
=
'高清'
;
color
=
'#55AA55'
;
} else if (pixels < 4000000) { // 小于4MP
quality
=
'超清'
;
color
=
'#5555FF'
;
} else {
quality
=
'超高清'
;
color
=
'#AA55FF'
;
}
resolutionQualityBadge.textContent
=
quality
;
resolutionQualityBadge.style.backgroundColor
=
color
;
currentResolutionSpan.textContent = `${width} × ${height}`;
}
// 处理分辨率输入变化
imageWidthInput.addEventListener('input', function() {
const
width
=
parseInt
(this.value) || 1000;
if (maintainRatioCheckbox.checked && aspectRatio) {
const
height
=
Math
.round(width / aspectRatio);
imageHeightInput.value
=
height
;
updateResolutionQuality(width, height);
} else {
const
height
=
parseInt
(imageHeightInput.value) || 600;
updateResolutionQuality(width, height);
}
});
imageHeightInput.addEventListener('input', function() {
const
height
=
parseInt
(this.value) || 600;
if (maintainRatioCheckbox.checked && aspectRatio) {
const
width
=
Math
.round(height * aspectRatio);
imageWidthInput.value
=
width
;
updateResolutionQuality(width, height);
} else {
const
width
=
parseInt
(imageWidthInput.value) || 1000;
updateResolutionQuality(width, height);
}
});
// 上传区域拖放功能
const
uploadArea
=
document
.getElementById('upload-area');
uploadArea.addEventListener('dragover', function(e) {
e.preventDefault();
uploadArea.style.borderColor
=
'var(--primary-color)'
;
uploadArea.style.backgroundColor
=
'rgba(255, 240, 245, 0.8)'
;
});
uploadArea.addEventListener('dragleave', function(e) {
e.preventDefault();
uploadArea.style.borderColor
=
'var(--secondary-color)'
;
uploadArea.style.backgroundColor
=
'rgba(255, 240, 245, 0.5)'
;
});
uploadArea.addEventListener('drop', function(e) {
e.preventDefault();
uploadArea.style.borderColor
=
'var(--secondary-color)'
;
uploadArea.style.backgroundColor
=
'rgba(255, 240, 245, 0.5)'
;
if (e.dataTransfer.files.length) {
document.getElementById('upload')
.files
= e.dataTransfer.files;
handleImageUpload(e.dataTransfer.files[0]);
}
});
// 上传图片
document.getElementById('upload').addEventListener('change', function(e) {
const
file
= e.target.files[0];
if (!file) return;
handleImageUpload(file);
});
function handleImageUpload(file) {
if (!file.type.match('image.*')) {
alert('请上传图片文件!');
return;
}
loading.style.display
=
'flex'
;
const
reader
=
new
FileReader();
reader.onload
=
function
(event) {
const
img
=
new
Image();
img.onload
=
function
() {
originalImage
=
img
;
originalWidth
=
img
.width;
originalHeight
=
img
.height;
aspectRatio
=
originalWidth
/ originalHeight;
// 更新分辨率输入框
imageWidthInput.value
=
originalWidth
;
imageHeightInput.value
=
originalHeight
;
updateResolutionQuality(originalWidth, originalHeight);
// 调整canvas大小以适应图片
const
maxWidth
=
600
;
const
maxHeight
=
400
;
let
width
=
img
.width;
let
height
=
img
.height;
if (width > maxWidth) {
height = (maxWidth / width) * height;
width = maxWidth;
}
if (height > maxHeight) {
width = (maxHeight / height) * width;
height = maxHeight;
}
canvas.width = width;
canvas.height = height;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, width, height);
document.getElementById('download').style.display = 'none';
loading.style.display = 'none';
showTip('图片上传成功!👍', uploadArea);
};
img.src = event.target.result;
};
reader.readAsDataURL(file);
}
// 预设模板选择
document.querySelectorAll('.template').forEach(template => {
template.addEventListener('click', function() {
document.querySelectorAll('.template').forEach(t => {
t.classList.remove('selected');
t.setAttribute('aria-checked', 'false');
});
this.classList.add('selected');
this.setAttribute('aria-checked', 'true');
document.getElementById('watermark-text').value = this.getAttribute('data-text');
showTip('已选择模板:' + this.textContent.trim() + ' 📝', this);
});
// 键盘访问
template.setAttribute('tabindex', '0');
template.addEventListener('keydown', function(e) {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
this.click();
}
});
});
// 颜色选择器
document.querySelectorAll('.color-option').forEach(option => {
option.addEventListener('click', function() {
document.querySelectorAll('.color-option').forEach(o => {
o.classList.remove('selected');
o.setAttribute('aria-checked', 'false');
});
this.classList.add('selected');
this.setAttribute('aria-checked', 'true');
selectedColor = this.getAttribute('data-color');
showTip('已选择颜色 🎨', this);
});
// 键盘访问
option.setAttribute('tabindex', '0');
option.addEventListener('keydown', function(e) {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
this.click();
}
});
});
// 进度条更新
document.getElementById('horizontal-spacing').addEventListener('input', function() {
const value = this.value;
const max = 300;
const percentage = (value / max) * 100;
const progressBar = document.getElementById('horizontal-progress');
progressBar.style.width = percentage + '%';
progressBar.setAttribute('aria-valuenow', Math.round(percentage));
});
document.getElementById('vertical-spacing').addEventListener('input', function() {
const value = this.value;
const max = 300;
const percentage = (value / max) * 100;
const progressBar = document.getElementById('vertical-progress');
progressBar.style.width = percentage + '%';
progressBar.setAttribute('aria-valuenow', Math.round(percentage));
});
// 预览水印
document.getElementById('preview-watermark').addEventListener('click', function() {
if (!originalImage) {
alert('请先上传图片!');
return;
}
const text = document.getElementById('watermark-text').value;
if (!text) {
alert('请输入水印文字!');
return;
}
// 创建临时canvas来绘制水印
const tempCanvas = document.createElement('canvas');
tempCanvas.width = canvas.width;
tempCanvas.height = canvas.height;
const tempCtx = tempCanvas.getContext('2d');
// 设置水印参数
const fontSize = document.getElementById('font-size').value;
const rotation = parseFloat(document.getElementById('rotation').value);
const fontFamily = document.getElementById('font-family').value;
const opacity = parseInt(document.getElementById('opacity').value) / 100;
const horizontalSpacing = parseInt(document.getElementById('horizontal-spacing').value);
const verticalSpacing = parseInt(document.getElementById('vertical-spacing').value);
const addDate = document.getElementById('add-date').checked;
const addEmoji = document.getElementById('add-emoji').checked;
// 准备水印文字
let watermarkText = text;
// 添加日期时间戳
if (addDate) {
const now = new Date();
const dateStr = now.toLocaleDateString();
const timeStr = now.toLocaleTimeString();
watermarkText += ` (${dateStr} ${timeStr})`;
}
// 添加表情
if (addEmoji) {
const emojis = ['😊', '👍', '✅', '🔒', '⭐', '💯'];
const randomEmoji = emojis[Math.floor(Math.random() * emojis.length)];
watermarkText = randomEmoji + ' ' + watermarkText + ' ' + randomEmoji;
}
// 设置字体和样式
tempCtx.font = `${fontSize}px ${fontFamily}`;
tempCtx.fillStyle = selectedColor;
tempCtx.globalAlpha = opacity;
tempCtx.textBaseline = 'middle';
// 保存当前状态
tempCtx.save();
// 移动到中心点并旋转
tempCtx.translate(tempCanvas.width / 2, tempCanvas.height / 2);
const angle = rotation * Math.PI / 180;
tempCtx.rotate(angle);
// 计算水印网格
const textWidth = tempCtx.measureText(watermarkText).width;
const diagonal = Math.sqrt(tempCanvas.width * tempCanvas.width + tempCanvas.height * tempCanvas.height);
const cols = Math.ceil(diagonal / (textWidth + horizontalSpacing));
const rows = Math.ceil(diagonal / verticalSpacing);
// 绘制水印网格
for (let i = -rows; i <
rows
; i++) {
for (let j = -cols; j < cols; j++) {
const
x
=
j
* (textWidth + horizontalSpacing);
const
y
=
i
* verticalSpacing;
tempCtx.fillText(watermarkText, x, y);
}
}
// 恢复状态
tempCtx.restore();
// 显示预览
watermarkPreview.style.display
=
'block'
;
watermarkPreview.style.backgroundImage = `url(${tempCanvas.toDataURL()})`;
// 3秒后隐藏预览
setTimeout(() => {
watermarkPreview.style.display = 'none';
}, 3000);
showTip('预览水印效果(3秒后自动关闭)👁️', this);
});
// 添加水印
document.getElementById('add-watermark').addEventListener('click', function() {
if (!originalImage) {
alert('请先上传图片!');
return;
}
const text = document.getElementById('watermark-text').value;
if (!text) {
alert('请输入水印文字!');
return;
}
loading.style.display = 'flex';
setTimeout(() => {
applyWatermark();
loading.style.display = 'none';
showTip('水印添加成功!✅', this);
}, 500);
});
function applyWatermark() {
const text = document.getElementById('watermark-text').value;
const fontSize = document.getElementById('font-size').value;
const rotation = parseFloat(document.getElementById('rotation').value);
const fontFamily = document.getElementById('font-family').value;
const opacity = parseInt(document.getElementById('opacity').value) / 100;
const horizontalSpacing = parseInt(document.getElementById('horizontal-spacing').value);
const verticalSpacing = parseInt(document.getElementById('vertical-spacing').value);
const addDate = document.getElementById('add-date').checked;
const addEmoji = document.getElementById('add-emoji').checked;
// 重绘原始图片
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(originalImage, 0, 0, canvas.width, canvas.height);
// 设置水印文字
let watermarkText = text;
// 添加日期时间戳
if (addDate) {
const now = new Date();
const dateStr = now.toLocaleDateString();
const timeStr = now.toLocaleTimeString();
watermarkText += ` (${dateStr} ${timeStr})`;
}
// 添加表情
if (addEmoji) {
const emojis = ['😊', '👍', '✅', '🔒', '⭐', '💯'];
const randomEmoji = emojis[Math.floor(Math.random() * emojis.length)];
watermarkText = randomEmoji + ' ' + watermarkText + ' ' + randomEmoji;
}
// 设置字体
ctx.font = `${fontSize}px ${fontFamily}`;
ctx.fillStyle = selectedColor;
ctx.globalAlpha = opacity;
ctx.textBaseline = 'middle';
// 保存当前状态
ctx.save();
// 移动到中心点并旋转
ctx.translate(canvas.width / 2, canvas.height / 2);
const angle = rotation * Math.PI / 180;
ctx.rotate(angle);
// 计算水印网格
const textWidth = ctx.measureText(watermarkText).width;
const diagonal = Math.sqrt(canvas.width * canvas.width + canvas.height * canvas.height);
const cols = Math.ceil(diagonal / (textWidth + horizontalSpacing));
const rows = Math.ceil(diagonal / verticalSpacing);
// 绘制水印网格
for (let i = -rows; i <
rows
; i++) {
for (let j = -cols; j < cols; j++) {
const
x
=
j
* (textWidth + horizontalSpacing);
const
y
=
i
* verticalSpacing;
ctx.fillText(watermarkText, x, y);
}
}
// 恢复状态
ctx.restore();
// 重置透明度
ctx.globalAlpha
=
1
.0;
// 显示下载按钮
document.getElementById('download')
.style.display
=
'inline-block'
;
}
// 下载图片
document.getElementById('download').addEventListener('click', function() {
if (!originalImage) {
alert('请先上传图片!');
return;
}
loading.style.display
=
'flex'
;
setTimeout(() => {
// 获取用户设置的分辨率
const targetWidth = parseInt(imageWidthInput.value) || originalWidth;
const targetHeight = parseInt(imageHeightInput.value) || originalHeight;
// 创建一个新的canvas用于导出高分辨率图片
const exportCanvas = document.createElement('canvas');
exportCanvas.width = targetWidth;
exportCanvas.height = targetHeight;
const exportCtx = exportCanvas.getContext('2d');
// 绘制原始图片
exportCtx.drawImage(originalImage, 0, 0, targetWidth, targetHeight);
// 设置水印参数
const text = document.getElementById('watermark-text').value;
const fontSize = parseInt(document.getElementById('font-size').value);
const rotation = parseFloat(document.getElementById('rotation').value);
const fontFamily = document.getElementById('font-family').value;
const opacity = parseInt(document.getElementById('opacity').value) / 100;
const horizontalSpacing = parseInt(document.getElementById('horizontal-spacing').value);
const verticalSpacing = parseInt(document.getElementById('vertical-spacing').value);
const addDate = document.getElementById('add-date').checked;
const addEmoji = document.getElementById('add-emoji').checked;
// 计算字体大小比例
const scaleFactor = Math.max(targetWidth / canvas.width, targetHeight / canvas.height);
const scaledFontSize = Math.round(fontSize * scaleFactor);
// 设置水印文字
let watermarkText = text;
// 添加日期时间戳
if (addDate) {
const now = new Date();
const dateStr = now.toLocaleDateString();
const timeStr = now.toLocaleTimeString();
watermarkText += ` (${dateStr} ${timeStr})`;
}
// 添加表情
if (addEmoji) {
const emojis = ['😊', '👍', '✅', '🔒', '⭐', '💯'];
const randomEmoji = emojis[Math.floor(Math.random() * emojis.length)];
watermarkText = randomEmoji + ' ' + watermarkText + ' ' + randomEmoji;
}
// 设置字体和样式
exportCtx.font = `${scaledFontSize}px ${fontFamily}`;
exportCtx.fillStyle = selectedColor;
exportCtx.globalAlpha = opacity;
exportCtx.textBaseline = 'middle';
// 保存当前状态
exportCtx.save();
// 移动到中心点并旋转
exportCtx.translate(exportCanvas.width / 2, exportCanvas.height / 2);
const angle = rotation * Math.PI / 180;
exportCtx.rotate(angle);
// 计算水印网格
const textWidth = exportCtx.measureText(watermarkText).width;
const diagonal = Math.sqrt(exportCanvas.width * exportCanvas.width + exportCanvas.height * exportCanvas.height);
const scaledHorizontalSpacing = horizontalSpacing * scaleFactor;
const scaledVerticalSpacing = verticalSpacing * scaleFactor;
const cols = Math.ceil(diagonal / (textWidth + scaledHorizontalSpacing));
const rows = Math.ceil(diagonal / scaledVerticalSpacing);
// 绘制水印网格
for (let i = -rows; i <
rows
; i++) {
for (let j = -cols; j < cols; j++) {
const
x
=
j
* (textWidth + scaledHorizontalSpacing);
const
y
=
i
* scaledVerticalSpacing;
exportCtx.fillText(watermarkText, x, y);
}
}
// 恢复状态
exportCtx.restore();
// 创建临时链接
const
link
=
document
.createElement('a');
link.download
=
'安全水印图片_'
+ new Date().getTime() + '.png';
// 将canvas转换为blob
exportCanvas.toBlob(function(blob) {
const
url
=
URL
.createObjectURL(blob);
link.href
=
url
;
link.click();
// 清理
setTimeout(() => {
URL.revokeObjectURL(url);
loading.style.display = 'none';
}, 100);
showTip(`已下载 ${targetWidth}×${targetHeight} 分辨率图片!💾`, document.getElementById('download'));
});
}, 500);
});
// 重置按钮
document.getElementById('reset').addEventListener('click', function() {
if (originalImage) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(originalImage, 0, 0, canvas.width, canvas.height);
document.getElementById('download').style.display = 'none';
// 重置分辨率设置
if (originalWidth && originalHeight) {
imageWidthInput.value = originalWidth;
imageHeightInput.value = originalHeight;
updateResolutionQuality(originalWidth, originalHeight);
}
}
document.getElementById('watermark-text').value = '';
document.getElementById('font-size').value = '20';
document.getElementById('rotation').value = '-45';
document.getElementById('font-family').value = 'Arial';
document.getElementById('opacity').value = '50';
document.getElementById('horizontal-spacing').value = '100';
document.getElementById('vertical-spacing').value = '100';
document.getElementById('add-date').checked = true;
document.getElementById('add-emoji').checked = true;
document.getElementById('maintain-ratio').checked = true;
document.querySelectorAll('.template').forEach(t => {
t.classList.remove('selected');
t.setAttribute('aria-checked', 'false');
});
document.querySelectorAll('.color-option').forEach(o => {
o.classList.remove('selected');
o.setAttribute('aria-checked', 'false');
});
document.querySelector('.color-option[data-color="white"]').classList.add('selected');
document.querySelector('.color-option[data-color="white"]').setAttribute('aria-checked', 'true');
selectedColor = 'white';
document.getElementById('horizontal-progress').style.width = '33%';
document.getElementById('horizontal-progress').setAttribute('aria-valuenow', '33');
document.getElementById('vertical-progress').style.width = '33%';
document.getElementById('vertical-progress').setAttribute('aria-valuenow', '33');
showTip('已重置所有设置!🔄', this);
});
// 初始化进度条
document.getElementById('horizontal-progress').style.width = '33%';
document.getElementById('vertical-progress').style.width = '33%';
// 安全提示动画
const securityNotice = document.querySelector('.security-notice');
setTimeout(() => {
securityNotice.style.backgroundColor = 'var(--warning-color)';
setTimeout(() => {
securityNotice.style.backgroundColor = 'var(--success-color)';
}, 500);
}, 2000);
// 页面加载完成后显示欢迎提示
window.addEventListener('load', function() {
setTimeout(() => {
showTip('欢迎使用可爱身份证水印工具!😊', document.querySelector('.emoji-title'), 3000);
}, 1000);
});
// 检测网络状态
window.addEventListener('online', function() {
showTip('网络已连接 🌐', document.body, 2000);
});
window.addEventListener('offline', function() {
showTip('网络已断开,但不用担心,本工具可以离线使用 🔌', document.body, 3000);
});
// 添加键盘导航支持
document.addEventListener('keydown', function(e) {
// Esc键关闭预览
if (e.key === 'Escape' && watermarkPreview.style.display === 'block') {
watermarkPreview.style.display = 'none';
}
});
// 添加触摸设备的双指缩放支持
let initialPinchDistance = 0;
let initialFontSize = 20;
canvas.addEventListener('touchstart', function(e) {
if (e.touches.length === 2) {
initialPinchDistance = Math.hypot(
e.touches[0].clientX - e.touches[1].clientX,
e.touches[0].clientY - e.touches[1].clientY
);
initialFontSize = parseInt(document.getElementById('font-size').value);
}
});
canvas.addEventListener('touchmove', function(e) {
if (e.touches.length === 2) {
e.preventDefault(); // 防止页面缩放
const currentDistance = Math.hypot(
e.touches[0].clientX - e.touches[1].clientX,
e.touches[0].clientY - e.touches[1].clientY
);
const scaleFactor = currentDistance / initialPinchDistance;
const newFontSize = Math.max(12, Math.min(32, Math.round(initialFontSize * scaleFactor)));
document.getElementById('font-size').value = newFontSize;
}
});
</
script
>
</
body
>
</
html
>