亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

JS+HTML實(shí)現(xiàn)在線圖片水印添加工具

 更新時(shí)間:2025年04月16日 14:30:47   作者:創(chuàng)客白澤  
在社交媒體和內(nèi)容創(chuàng)作日益頻繁的今天,如何保護(hù)原創(chuàng)內(nèi)容、展示品牌身份成了一個(gè)不得不面對(duì)的問題,本文將實(shí)現(xiàn)一個(gè)完全基于 HTML + CSS 構(gòu)建的現(xiàn)代化圖片水印在線工具,希望對(duì)大家有所幫助

概述

在社交媒體和內(nèi)容創(chuàng)作日益頻繁的今天,如何保護(hù)原創(chuàng)內(nèi)容、展示品牌身份成了一個(gè)不得不面對(duì)的問題。給圖片添加水印,正是目前主流平臺(tái)和創(chuàng)作者最常用的解決方案之一。

但問題也隨之而來:

  • Photoshop 加水印太麻煩?
  • 腳本工具不好操作?
  • 找不到一個(gè)輕便、美觀、好用的工具?

別急,今天帶大家實(shí)現(xiàn)一個(gè)完全基于 HTML + CSS 構(gòu)建的現(xiàn)代化 「圖片水印在線工具」,無需安裝、純前端交互,支持在線預(yù)覽和個(gè)性化定制,一切只需瀏覽器即可完成!

功能亮點(diǎn)

該工具界面簡潔、響應(yīng)迅速,適合各類用戶在線添加水印。以下是主要功能一覽:

1. 拖拽上傳圖片

  • 支持點(diǎn)擊上傳與拖拽上傳;
  • 自帶上傳動(dòng)畫與視覺反饋;
  • 支持預(yù)覽已上傳的圖片內(nèi)容。

2. 水印自定義設(shè)置

  • 支持文本水印和圖像水?。蓴U(kuò)展);
  • 自定義顏色、字體、大小、透明度、位置;
  • 所有設(shè)置實(shí)時(shí)預(yù)覽,無需刷新。

3. 響應(yīng)式設(shè)計(jì)

  • 使用 CSS Grid 進(jìn)行布局;
  • 自動(dòng)適配移動(dòng)端和桌面端;
  • sticky 面板在大屏上保持固定,操作方便。

4. 高顏值 UI

  • 全局主題色可配置;
  • 光影、圓角、漸變背景一應(yīng)俱全;
  • UI 參考現(xiàn)代 SaaS 工具風(fēng)格。

使用方法

第一步:上傳你的圖片

點(diǎn)擊或拖拽圖片到上傳區(qū)域,支持 JPG、PNG 等常用格式。

<input type="file" accept="image/*" id="imageInput">

第二步:設(shè)置水印參數(shù)

通過設(shè)置面板可以自定義以下選項(xiàng):

設(shè)置項(xiàng)說明
文本內(nèi)容輸入水印文字
字體大小使用 <input type="range"> 調(diào)整
字體顏色使用 <input type="color"> 設(shè)置
透明度調(diào)整水印透明度,范圍 0 ~ 1
位置選擇左上、右上、居中、底部等

所有設(shè)置項(xiàng)變動(dòng)后會(huì)實(shí)時(shí)更新預(yù)覽區(qū)域的 Canvas。

第三步:查看預(yù)覽效果

頁面左側(cè) canvas 區(qū)域顯示當(dāng)前的最終效果。支持高清縮放。

<canvas id="previewCanvas" width="1080" height="2400"></canvas>

你可以隨時(shí)修改設(shè)置項(xiàng)查看實(shí)時(shí)預(yù)覽,真正做到“所見即所得”。

第四步:導(dǎo)出水印圖片(可擴(kuò)展)

可以通過擴(kuò)展 JS 腳本,調(diào)用 canvas.toBlob() 或 toDataURL() 將帶水印的圖像導(dǎo)出為下載鏈接,甚至上傳到云端。

技術(shù)解析

HTML:結(jié)構(gòu)清晰,語義為王

采用語義化標(biāo)簽 section、h1、label 等構(gòu)建主骨架,使結(jié)構(gòu)更清晰易讀,利于 SEO 和可維護(hù)性。

CSS:原子化變量設(shè)計(jì) + 現(xiàn)代布局方案

使用 :root 全局變量控制顏色、陰影、圓角等設(shè)計(jì)語言,一鍵更換主題毫無壓力。

關(guān)鍵點(diǎn):

CSS Grid + Flexbox 實(shí)現(xiàn)自適應(yīng)布局;

sticky 屬性打造固定側(cè)邊欄;

圖層陰影、過渡動(dòng)畫等增強(qiáng)交互體驗(yàn)。

--primary-color: #4361ee;
--shadow: 0 4px 12px rgba(0,0,0,0.08);
.settings {
  position: sticky;
  top: 20px;
}

JavaScript(擴(kuò)展點(diǎn))

雖然本文代碼未包含完整 JS,但預(yù)留了事件鉤子,非常適合后續(xù)開發(fā):

  • 圖片上傳:監(jiān)聽 input 或拖拽事件;
  • 參數(shù)設(shè)置:綁定 input 和 change 事件;
  • Canvas 繪制:結(jié)合 drawImage() 和文本渲染 API;
  • 導(dǎo)出下載:使用 canvas.toBlob() 實(shí)現(xiàn)圖片導(dǎo)出。

延伸思考

除了當(dāng)前展示的功能,我們還可以這樣升級(jí)它:

1.添加圖像型水印

  • 支持上傳一張 PNG 作為水印圖,疊加在主圖上;
  • 調(diào)整透明度、縮放比例和位置。

2.批量處理功能

  • 拖入多張圖片,逐一添加相同水??;
  • 配合 Web Worker 實(shí)現(xiàn)多線程處理。

3.用戶配置本地保存

  • 利用 localStorage 保存用戶上一次的設(shè)置;
  • 實(shí)現(xiàn)個(gè)性化“水印模板”方案。

4.導(dǎo)出為高分辨率圖像

  • 允許用戶自定義導(dǎo)出分辨率;
  • 適配印刷或商用需求。

5.PWA 打包為 App

  • 利用 PWA 特性將其打包為桌面應(yīng)用或手機(jī) App;
  • 實(shí)現(xiàn)“離線水印工具”。

運(yùn)行效果

項(xiàng)目源碼下載

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>圖片水印工具</title>
    <style>
        :root {
            --primary-color: #4361ee;
            --primary-light: #e6e9ff;
            --secondary-color: #3f37c9;
            --text-color: #333;
            --light-text: #666;
            --border-color: #ddd;
            --bg-color: #f8f9fa;
            --card-bg: #fff;
            --shadow: 0 4px 12px rgba(0,0,0,0.08);
        }

        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
        }

        body {
            font-family: 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif;
            line-height: 1.6;
            color: var(--text-color);
            background-color: var(--bg-color);
            padding: 20px;
        }

        h1 {
            color: var(--primary-color);
            text-align: center;
            margin-bottom: 24px;
            font-weight: 600;
        }

        .container {
            display: grid;
            grid-template-columns: 1fr 350px;
            gap: 24px;
            max-width: 1400px;
            margin: 0 auto;
        }

        .preview {
            background: var(--card-bg);
            padding: 20px;
            border-radius: 12px;
            box-shadow: var(--shadow);
            display: flex;
            flex-direction: column;
            height: fit-content;
        }

        .preview-title {
            font-size: 18px;
            font-weight: 500;
            margin-bottom: 16px;
            color: var(--primary-color);
            display: flex;
            align-items: center;
            gap: 8px;
        }

        .preview-title svg {
            width: 20px;
            height: 20px;
        }

        #previewCanvas {
            width: 100%;
            max-width: 100%;
            height: auto;
            border-radius: 8px;
            border: 1px solid var(--border-color);
            background: repeating-conic-gradient(#f5f5f5 0% 25%, white 0% 50%) 50%/20px 20px;
        }

        .settings {
            background: var(--card-bg);
            padding: 24px;
            border-radius: 12px;
            box-shadow: var(--shadow);
            position: sticky;
            top: 20px;
        }

        .settings-title {
            font-size: 18px;
            font-weight: 500;
            margin-bottom: 20px;
            color: var(--primary-color);
            display: flex;
            align-items: center;
            gap: 8px;
        }

        .settings-title svg {
            width: 20px;
            height: 20px;
        }

        .form-group {
            margin-bottom: 20px;
        }

        label {
            display: block;
            margin-bottom: 8px;
            font-weight: 500;
            color: var(--light-text);
            font-size: 14px;
        }

        input[type="text"],
        input[type="number"],
        select {
            width: 100%;
            padding: 10px 12px;
            border: 1px solid var(--border-color);
            border-radius: 6px;
            font-size: 14px;
            transition: border-color 0.3s;
        }

        input[type="text"]:focus,
        input[type="number"]:focus,
        select:focus {
            outline: none;
            border-color: var(--primary-color);
            box-shadow: 0 0 0 2px var(--primary-light);
        }

        input[type="file"] {
            display: none;
        }

        input[type="color"] {
            width: 40px;
            height: 40px;
            border: 1px solid var(--border-color);
            border-radius: 6px;
            padding: 2px;
            background: white;
        }

        .grid-container {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 15px;
        }

        .range-container {
            display: flex;
            align-items: center;
            gap: 10px;
        }

        input[type="range"] {
            flex-grow: 1;
            height: 6px;
            border-radius: 3px;
            background: var(--border-color);
            -webkit-appearance: none;
        }

        input[type="range"]::-webkit-slider-thumb {
            -webkit-appearance: none;
            width: 18px;
            height: 18px;
            border-radius: 50%;
            background: var(--primary-color);
            cursor: pointer;
        }

        .range-value {
            min-width: 50px;
            text-align: right;
            font-size: 14px;
            color: var(--primary-color);
        }

        .watermark-type {
            display: none;
        }

        .watermark-type.active {
            display: block;
        }

        .section {
            margin-top: 24px;
            padding-top: 16px;
            border-top: 1px solid var(--border-color);
        }

        .section-title {
            font-size: 16px;
            font-weight: 500;
            margin-bottom: 16px;
            color: var(--primary-color);
        }

        button {
            width: 100%;
            padding: 12px;
            background-color: var(--primary-color);
            color: white;
            border: none;
            border-radius: 6px;
            font-size: 16px;
            font-weight: 500;
            cursor: pointer;
            transition: background-color 0.3s;
            margin-top: 20px;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 8px;
        }

        button:hover {
            background-color: var(--secondary-color);
        }

        button svg {
            width: 18px;
            height: 18px;
        }

        @media (max-width: 768px) {
            .container {
                grid-template-columns: 1fr;
            }
            
            .settings {
                position: static;
            }
        }

        /* 添加一些圖標(biāo)樣式 */
        .icon {
            width: 20px;
            height: 20px;
            fill: currentColor;
        }

        /* 文件上傳區(qū)域樣式 */
        .file-upload-area {
            border: 2px dashed var(--border-color);
            border-radius: 8px;
            padding: 20px;
            text-align: center;
            cursor: pointer;
            transition: all 0.3s;
            margin-bottom: 15px;
            background-color: var(--bg-color);
        }

        .file-upload-area:hover {
            border-color: var(--primary-color);
            background-color: var(--primary-light);
        }

        .file-upload-area.drag-over {
            border-color: var(--primary-color);
            background-color: var(--primary-light);
        }

        .file-upload-icon {
            font-size: 48px;
            color: var(--primary-color);
            margin-bottom: 10px;
        }

        .file-upload-text {
            font-size: 14px;
            color: var(--light-text);
        }

        .file-upload-button {
            display: inline-block;
            padding: 8px 16px;
            background-color: var(--primary-color);
            color: white;
            border-radius: 4px;
            font-size: 14px;
            margin-top: 10px;
            transition: background-color 0.3s;
        }

        .file-upload-button:hover {
            background-color: var(--secondary-color);
        }

        .file-name {
            font-size: 14px;
            margin-top: 10px;
            color: var(--primary-color);
            word-break: break-all;
        }
    </style>
</head>
<body>
    <h1>圖片水印工具</h1>
    <div class="container">
        <!-- 預(yù)覽區(qū)域 -->
        <div class="preview">
            <h2 class="preview-title">
                <svg class="icon" viewBox="0 0 24 24">
                    <path d="M19,19H5V5H19M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3M13.96,12.29L11.21,15.83L9.25,13.47L6.5,17H17.5L13.96,12.29Z"/>
                </svg>
                預(yù)覽效果
            </h2>
            <canvas id="previewCanvas" width="1080" height="2400"></canvas>
        </div>

        <!-- 設(shè)置面板 -->
        <div class="settings">
            <h2 class="settings-title">
                <svg class="icon" viewBox="0 0 24 24">
                    <path d="M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z"/>
                </svg>
                水印設(shè)置
            </h2>
            
            <div class="form-group">
                <label for="imageInput">上傳圖片</label>
                <div class="file-upload-area" id="imageUploadArea">
                    <div class="file-upload-icon">
                        <svg viewBox="0 0 24 24" width="48" height="48" fill="currentColor">
                            <path d="M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20Z"/>
                        </svg>
                    </div>
                    <div class="file-upload-text">拖放圖片到此處或點(diǎn)擊選擇文件</div>
                    <div class="file-upload-button">選擇文件</div>
                    <div class="file-name" id="imageFileName"></div>
                </div>
                <input type="file" id="imageInput" accept="image/*">
            </div>

            <div class="form-group">
                <label for="watermarkType">水印類型</label>
                <select id="watermarkType">
                    <option value="text">文字水印</option>
                    <option value="image">圖片水印</option>
                </select>
            </div>

            <!-- 文字水印設(shè)置 -->
            <div id="textSettings" class="watermark-type active">
                <div class="form-group">
                    <label for="watermarkText">水印文字</label>
                    <input type="text" id="watermarkText" value="機(jī)密文件" placeholder="輸入水印文字">
                </div>

                <div class="grid-container">
                    <div class="form-group">
                        <label for="fontSize">字體大小</label>
                        <input type="number" id="fontSize" value="48" min="10" max="100" placeholder="字號(hào)">
                    </div>

                    <div class="form-group">
                        <label for="textColor">文字顏色</label>
                        <div class="range-container">
                            <input type="color" id="textColor" value="#cccccc">
                        </div>
                    </div>
                </div>

                <div class="section">
                    <h3 class="section-title">高級(jí)設(shè)置</h3>
                    <div class="form-group">
                        <label>旋轉(zhuǎn)角度 <span class="range-value" id="angleValue">45°</span></label>
                        <div class="range-container">
                            <input type="range" id="angle" min="-180" max="180" value="45">
                        </div>
                    </div>

                    <div class="form-group">
                        <label>水印密度 <span class="range-value" id="densityValue">50%</span></label>
                        <div class="range-container">
                            <input type="range" id="density" min="10" max="100" value="50">
                        </div>
                    </div>

                    <div class="form-group">
                        <label>水平間距 <span class="range-value" id="spacingXValue">150px</span></label>
                        <div class="range-container">
                            <input type="range" id="spacingX" min="50" max="300" value="150">
                        </div>
                    </div>

                    <div class="form-group">
                        <label>垂直間距 <span class="range-value" id="spacingYValue">100px</span></label>
                        <div class="range-container">
                            <input type="range" id="spacingY" min="50" max="300" value="100">
                        </div>
                    </div>
                </div>
            </div>

            <!-- 圖片水印設(shè)置 -->
            <div id="imageSettings" class="watermark-type">
                <div class="form-group">
                    <label for="watermarkImage">上傳水印圖片</label>
                    <div class="file-upload-area" id="watermarkUploadArea">
                        <div class="file-upload-icon">
                            <svg viewBox="0 0 24 24" width="48" height="48" fill="currentColor">
                                <path d="M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20Z"/>
                            </svg>
                        </div>
                        <div class="file-upload-text">拖放水印圖片到此處或點(diǎn)擊選擇文件</div>
                        <div class="file-upload-button">選擇圖片</div>
                        <div class="file-name" id="watermarkFileName"></div>
                    </div>
                    <input type="file" id="watermarkImage" accept="image/*">
                </div>
                <div class="form-group">
                    <label>縮放比例 <span class="range-value" id="scaleValue">30%</span></label>
                    <div class="range-container">
                        <input type="range" id="scale" min="10" max="100" value="30">
                    </div>
                </div>
            </div>

            <div class="form-group">
                <label>透明度 <span class="range-value" id="opacityValue">50%</span></label>
                <div class="range-container">
                    <input type="range" id="opacity" min="0" max="1" step="0.1" value="0.5">
                </div>
            </div>

            <button id="downloadBtn">
                <svg class="icon" viewBox="0 0 24 24">
                    <path d="M5,20H19V18H5M19,9H15V3H9V9H5L12,16L19,9Z"/>
                </svg>
                下載圖片
            </button>
        </div>
    </div>

    <script>
        const canvas = document.getElementById('previewCanvas');
        const ctx = canvas.getContext('2d');
        let originalImage = null;
        let watermarkImage = null;

        // 控件元素
        const controls = {
            imageInput: document.getElementById('imageInput'),
            watermarkType: document.getElementById('watermarkType'),
            watermarkText: document.getElementById('watermarkText'),
            fontSize: document.getElementById('fontSize'),
            textColor: document.getElementById('textColor'),
            angle: document.getElementById('angle'),
            density: document.getElementById('density'),
            spacingX: document.getElementById('spacingX'),
            spacingY: document.getElementById('spacingY'),
            opacity: document.getElementById('opacity'),
            watermarkImageInput: document.getElementById('watermarkImage'),
            scale: document.getElementById('scale'),
            downloadBtn: document.getElementById('downloadBtn'),
            imageUploadArea: document.getElementById('imageUploadArea'),
            watermarkUploadArea: document.getElementById('watermarkUploadArea'),
            imageFileName: document.getElementById('imageFileName'),
            watermarkFileName: document.getElementById('watermarkFileName')
        };

        // 初始化事件監(jiān)聽
        function initEventListeners() {
            // 通用事件
            controls.imageInput.addEventListener('change', handleImageUpload);
            controls.watermarkType.addEventListener('change', toggleWatermarkType);
            controls.opacity.addEventListener('input', updateRangeValue);
            controls.opacity.addEventListener('input', drawWatermark);

            // 文字水印事件
            controls.watermarkText.addEventListener('input', drawWatermark);
            controls.fontSize.addEventListener('input', drawWatermark);
            controls.textColor.addEventListener('input', drawWatermark);
            controls.angle.addEventListener('input', updateRangeValue);
            controls.angle.addEventListener('input', drawWatermark);
            controls.density.addEventListener('input', updateRangeValue);
            controls.density.addEventListener('input', drawWatermark);
            controls.spacingX.addEventListener('input', updateRangeValue);
            controls.spacingX.addEventListener('input', drawWatermark);
            controls.spacingY.addEventListener('input', updateRangeValue);
            controls.spacingY.addEventListener('input', drawWatermark);

            // 圖片水印事件
            controls.watermarkImageInput.addEventListener('change', handleWatermarkImageUpload);
            controls.scale.addEventListener('input', updateRangeValue);
            controls.scale.addEventListener('input', drawWatermark);

            // 下載按鈕
            controls.downloadBtn.addEventListener('click', downloadImage);

            // 文件拖放功能
            setupDragAndDrop(controls.imageUploadArea, controls.imageInput, controls.imageFileName);
            setupDragAndDrop(controls.watermarkUploadArea, controls.watermarkImageInput, controls.watermarkFileName);

            // 點(diǎn)擊上傳區(qū)域觸發(fā)文件選擇
            controls.imageUploadArea.querySelector('.file-upload-button').addEventListener('click', () => controls.imageInput.click());
            controls.watermarkUploadArea.querySelector('.file-upload-button').addEventListener('click', () => controls.watermarkImageInput.click());
        }

        function setupDragAndDrop(dropArea, fileInput, fileNameDisplay) {
            // 阻止默認(rèn)拖放行為
            ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
                dropArea.addEventListener(eventName, preventDefaults, false);
            });

            // 高亮顯示拖放區(qū)域
            ['dragenter', 'dragover'].forEach(eventName => {
                dropArea.addEventListener(eventName, highlight, false);
            });

            ['dragleave', 'drop'].forEach(eventName => {
                dropArea.addEventListener(eventName, unhighlight, false);
            });

            // 處理拖放文件
            dropArea.addEventListener('drop', handleDrop, false);

            function preventDefaults(e) {
                e.preventDefault();
                e.stopPropagation();
            }

            function highlight() {
                dropArea.classList.add('drag-over');
            }

            function unhighlight() {
                dropArea.classList.remove('drag-over');
            }

            function handleDrop(e) {
                const dt = e.dataTransfer;
                const files = dt.files;
                
                if (files.length) {
                    fileInput.files = files;
                    updateFileNameDisplay(files[0].name, fileNameDisplay);
                    
                    if (fileInput === controls.imageInput) {
                        handleImageUpload({ target: fileInput });
                    } else {
                        handleWatermarkImageUpload({ target: fileInput });
                    }
                }
            }
        }

        function updateFileNameDisplay(name, element) {
            element.textContent = name;
        }

        function updateRangeValue(e) {
            const target = e.target;
            switch(target.id) {
                case 'angle':
                    document.getElementById('angleValue').textContent = `${target.value}°`;
                    break;
                case 'density':
                    document.getElementById('densityValue').textContent = `${target.value}%`;
                    break;
                case 'spacingX':
                    document.getElementById('spacingXValue').textContent = `${target.value}px`;
                    break;
                case 'spacingY':
                    document.getElementById('spacingYValue').textContent = `${target.value}px`;
                    break;
                case 'opacity':
                    document.getElementById('opacityValue').textContent = `${Math.round(target.value * 100)}%`;
                    break;
                case 'scale':
                    document.getElementById('scaleValue').textContent = `${target.value}%`;
                    break;
            }
        }

        function toggleWatermarkType() {
            document.querySelectorAll('.watermark-type').forEach(el => {
                el.classList.remove('active');
            });
            document.getElementById(controls.watermarkType.value === 'text' ?
                'textSettings' : 'imageSettings').classList.add('active');
            drawWatermark();
        }

        async function handleImageUpload(e) {
            const file = e.target.files[0];
            if (file) {
                updateFileNameDisplay(file.name, controls.imageFileName);
                originalImage = await loadImage(file);
                canvas.width = originalImage.width;
                canvas.height = originalImage.height;
                drawWatermark();
            }
        }

        async function handleWatermarkImageUpload(e) {
            const file = e.target.files[0];
            if (file) {
                updateFileNameDisplay(file.name, controls.watermarkFileName);
                watermarkImage = await loadImage(file);
                drawWatermark();
            }
        }

        function loadImage(file) {
            return new Promise((resolve) => {
                const reader = new FileReader();
                reader.onload = (e) => {
                    const img = new Image();
                    img.onload = () => resolve(img);
                    img.src = e.target.result;
                };
                reader.readAsDataURL(file);
            });
        }

        function drawWatermark() {
            if (!originalImage) return;

            ctx.clearRect(0, 0, canvas.width, canvas.height);
            ctx.drawImage(originalImage, 0, 0);

            ctx.globalAlpha = controls.opacity.value;

            if (controls.watermarkType.value === 'text') {
                drawTextWatermark();
            } else if (watermarkImage) {
                drawImageWatermark();
            }

            ctx.globalAlpha = 1.0;
        }

        function drawTextWatermark() {
            ctx.save();
            ctx.font = `${controls.fontSize.value}px Arial`;
            ctx.fillStyle = controls.textColor.value;
            ctx.textAlign = 'center';
            ctx.textBaseline = 'middle';

            const rotation = (controls.angle.value * Math.PI) / 180;
            // 根據(jù)密度調(diào)整間距
            const densityFactor = 1 + (100 - controls.density.value) / 50;
            const stepX = parseInt(controls.spacingX.value) * densityFactor;
            const stepY = parseInt(controls.spacingY.value) * densityFactor;

            // 創(chuàng)建平鋪效果
            for (let x = -canvas.width; x < canvas.width * 2; x += stepX) {
                for (let y = -canvas.height; y < canvas.height * 2; y += stepY) {
                    ctx.save();
                    ctx.translate(x, y);
                    ctx.rotate(rotation);
                    ctx.fillText(controls.watermarkText.value, 0, 0);
                    ctx.restore();
                }
            }
            ctx.restore();
        }

        function drawImageWatermark() {
            const scale = controls.scale.value / 100;
            const width = watermarkImage.width * scale;
            const height = watermarkImage.height * scale;

            ctx.save();
            // 右下角位置
            const x = canvas.width - width - 20;
            const y = canvas.height - height - 20;
            ctx.drawImage(watermarkImage, x, y, width, height);
            ctx.restore();
        }

        function downloadImage() {
            if (!originalImage) {
                alert('請(qǐng)先上傳圖片');
                return;
            }
            
            const link = document.createElement('a');
            link.download = `watermarked-${Date.now()}.png`;
            link.href = canvas.toDataURL('image/png');
            link.click();
        }

        // 初始化
        initEventListeners();
        updateRangeValue({ target: controls.opacity });
        updateRangeValue({ target: controls.scale });
        updateRangeValue({ target: controls.density });
        
        // 添加默認(rèn)背景
        ctx.fillStyle = '#f5f5f5';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.fillStyle = '#999';
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.font = '24px Arial';
        ctx.fillText('上傳圖片后預(yù)覽效果將顯示在這里', canvas.width/2, canvas.height/2);
    </script>
</body>
</html>

總結(jié)

這篇文章,我們不僅展示了一個(gè)現(xiàn)代化、UI 優(yōu)雅、功能強(qiáng)大的前端圖片水印工具的開發(fā)案例,更結(jié)合了 HTML5 + CSS3 的最佳實(shí)踐,為大家提供了一個(gè)可以即用、也能深度定制的模板基礎(chǔ)。

不論你是:

想保護(hù)自己原創(chuàng)作品的內(nèi)容創(chuàng)作者,

想為公司運(yùn)營提供內(nèi)容加密的美工同事,

還是正在學(xué)習(xí)前端開發(fā)的工程師,

都能從這款工具中找到價(jià)值,并快速上手開發(fā)屬于自己的版本!

到此這篇關(guān)于JS+HTML實(shí)現(xiàn)在線圖片水印添加工具的文章就介紹到這了,更多相關(guān)JS圖片水印內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論