您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
支持直接粘贴,自动转换图片格式,自动压缩,裁切和马赛克,预览
// ==UserScript== // @name Bangumi wiki 图片上传增强 // @namespace https://bgm.tv/group/topic/431819 // @version 1.4.3 // @description 支持直接粘贴,自动转换图片格式,自动压缩,裁切和马赛克,预览 // @author You // @match https://bangumi.tv/character/*/upload_photo // @match https://bangumi.tv/character/*/upload_img // @match https://bangumi.tv/person/*/upload_photo // @match https://bangumi.tv/person/*/upload_img // @match https://bangumi.tv/subject/*/upload_img // @match https://bangumi.tv/*/new // @match https://bgm.tv/character/*/upload_photo // @match https://bgm.tv/character/*/upload_img // @match https://bgm.tv/person/*/upload_photo // @match https://bgm.tv/person/*/upload_img // @match https://bgm.tv/subject/*/upload_img // @match https://bgm.tv/*/new // @match https://chii.in/character/*/upload_photo // @match https://chii.in/character/*/upload_img // @match https://chii.in/person/*/upload_photo // @match https://chii.in/person/*/upload_img // @match https://chii.in/subject/*/upload_img // @match https://chii.in/*/new // @grant GM_xmlhttpRequest // @grant GM_getResourceText // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/cropper.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/image-compressor.min.js // @resource cropperCSS https://cdn.jsdelivr.net/npm/[email protected]/dist/cropper.min.css // @license MIT // ==/UserScript== (function () { 'use strict'; // #region 样式与DOM元素初始化 // 注入必要的CSS样式 const css = ` .bgm-preview-container { display: flex; gap: 15px; margin-bottom: 10px; flex-wrap: wrap; align-items: flex-start; display: none; } .bgm-original-preview { max-width: 100%; position: relative; min-height: 100px; } .bgm-square-preview { width: 100px; height: 100px; overflow: hidden; border: 1px solid #ddd; position: relative; } .bgm-preview-image { max-width: 100%; max-height: 300px; border: 1px solid #ddd; } .bgm-square-image { position: absolute; left: 0; top: 0; width: 100%; height: 100%; object-fit: cover; object-position: top center; } .bgm-square-image.cropper-mode { transform-origin: left top; } .bgm-preview-text { margin-top: 5px; font-size: 0.8em; } .bgm-controls { margin-top: 10px; display: flex; gap: 8px; flex-wrap: wrap; } .bgm-clipboard-control { margin-bottom: 10px; } .bgm-mosaic-controls { margin-top: 10px; display: none; } .bgm-hint { font-size: 0.9em; margin-block: 5px; } .cropper-modal { opacity: 0 !important; } .cropper-container { border: 1px solid #ddd; } .slider-control { margin: 10px 0; display: flex; align-items: center; gap: 10px; } .slider-control label { font-size: 0.9em; } .slider-control input { flex-grow: 1; max-width: 200px; } .brush-size-info { font-size: 0.8em; margin-top: 5px; } .mosaic-canvas-container { display: inline-block; border: 1px solid #ddd; } /* 粘贴辅助区域 - 完全隐藏 */ #pasteHelper { position: fixed; top: -100px; left: -100px; width: 1px; height: 1px; opacity: 0; outline: none; border: none; z-index: -1; } `; const style = document.createElement('style'); style.textContent = css; document.head.appendChild(style); // 添加Cropper.js CSS const cropperCSS = GM_getResourceText('cropperCSS'); const cropperStyle = document.createElement('style'); cropperStyle.textContent = cropperCSS; document.head.appendChild(cropperStyle); // 创建隐藏的粘贴辅助区域(用于兼容模式) const pasteHelper = document.createElement('textarea'); pasteHelper.id = 'pasteHelper'; pasteHelper.setAttribute('aria-hidden', 'true'); document.body.appendChild(pasteHelper); // 直接获取文件输入框 const fileInput = document.querySelector('input[type="file"][name="picfile"]'); if (!fileInput) return; const isUploadPhotoPage = window.location.pathname.includes('/upload_photo'); const MAX_FILE_SIZE = 2 * 1024 * 1024; // 2MB // 创建UI容器 const container = document.createElement('div'); container.className = 'bgm-uploader-container'; // 剪贴板按钮容器 const clipboardControl = document.createElement('div'); clipboardControl.className = 'bgm-clipboard-control'; // 单一剪贴板按钮 const getClipboardBtn = document.createElement('button'); getClipboardBtn.id = 'getClipboardBtn'; getClipboardBtn.textContent = '获取剪贴板内容'; clipboardControl.appendChild(getClipboardBtn); // 预览区域 const previewWrapper = document.createElement('div'); previewWrapper.className = 'bgm-preview-container'; const originalPreview = document.createElement('div'); originalPreview.className = 'bgm-original-preview'; const previewImage = document.createElement('img'); previewImage.className = 'bgm-preview-image'; const previewText = document.createElement('div'); previewText.className = 'bgm-preview-text'; let squarePreview = null; let squarePreviewImage = null; if (isUploadPhotoPage) { squarePreview = document.createElement('div'); squarePreview.className = 'bgm-square-preview'; squarePreview.style.display = 'none'; squarePreviewImage = document.createElement('img'); squarePreviewImage.className = 'bgm-square-image'; squarePreview.appendChild(squarePreviewImage); } // 其他控制按钮 const controls = document.createElement('div'); controls.className = 'bgm-controls'; const cropBtn = document.createElement('button'); cropBtn.textContent = '裁切图片'; cropBtn.style.display = 'none'; const cropConfirmBtn = document.createElement('button'); cropConfirmBtn.textContent = '确认裁切'; cropConfirmBtn.style.display = 'none'; const cropCancelBtn = document.createElement('button'); cropCancelBtn.textContent = '取消裁切'; cropCancelBtn.style.display = 'none'; const mosaicBtn = document.createElement('button'); mosaicBtn.textContent = '添加马赛克'; mosaicBtn.style.display = 'none'; const mosaicConfirmBtn = document.createElement('button'); mosaicConfirmBtn.textContent = '完成马赛克'; mosaicConfirmBtn.style.display = 'none'; const mosaicCancelBtn = document.createElement('button'); mosaicCancelBtn.textContent = '取消马赛克'; mosaicCancelBtn.style.display = 'none'; // 马赛克大小控制 const mosaicControls = document.createElement('div'); mosaicControls.className = 'bgm-mosaic-controls'; const blockSizeControl = document.createElement('div'); blockSizeControl.className = 'slider-control'; const blockSizeLabel = document.createElement('label'); blockSizeLabel.htmlFor = 'mosaicBlockSize'; blockSizeLabel.textContent = '马赛克比例:'; const blockSizeSlider = document.createElement('input'); blockSizeSlider.type = 'range'; blockSizeSlider.id = 'mosaicBlockSize'; blockSizeSlider.min = '1'; blockSizeSlider.max = '10'; blockSizeSlider.value = '3'; const blockSizeValue = document.createElement('span'); blockSizeValue.id = 'blockSizeValue'; blockSizeValue.textContent = '3%'; const brushSizeInfo = document.createElement('div'); brushSizeInfo.className = 'brush-size-info'; brushSizeInfo.textContent = '实际大小: 计算中...'; blockSizeControl.append(blockSizeLabel, blockSizeSlider, blockSizeValue); mosaicControls.appendChild(blockSizeControl); mosaicControls.appendChild(brushSizeInfo); // 组装UI originalPreview.appendChild(previewImage); previewWrapper.append(originalPreview); if (isUploadPhotoPage && squarePreview) { previewWrapper.appendChild(squarePreview); } controls.append(cropBtn, mosaicBtn, cropConfirmBtn, cropCancelBtn, mosaicConfirmBtn, mosaicCancelBtn); container.append(clipboardControl, previewWrapper, previewText, controls, mosaicControls); fileInput.parentNode.insertBefore(container, fileInput.nextSibling); // #endregion // #region 变量定义 const allowedImageTypes = { 'image/jpeg': 'jpg', 'image/png': 'png', 'image/gif': 'gif' }; const allowedExtensions = Object.values(allowedImageTypes); let currentFile = null; let cropper = null; let isCropping = false; let isMosaicing = false; let mosaicSizePercent = 3; let originalCanvas = null; let tempCanvas = null; let canvasContainer = null; let ctx = null; let isDrawing = false; const MAX_CANVAS_WIDTH = 700; let canvasScale = 1.0; // #endregion // #region 工具函数 // 显示/隐藏编辑按钮 function showEditButtons() { cropBtn.style.display = 'inline-block'; mosaicBtn.style.display = 'inline-block'; } function hideEditButtons() { cropBtn.style.display = 'none'; mosaicBtn.style.display = 'none'; } // 转换图片为JPG格式 function convertToJpg(file) { return new Promise((resolve, reject) => { if (allowedImageTypes[file.type]) { resolve(file); return; } previewText.textContent = `正在转换格式...`; previewWrapper.style.display = 'flex'; const reader = new FileReader(); reader.onload = function (e) { const img = new Image(); img.crossOrigin = 'anonymous'; img.onload = function () { try { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); // 设置Canvas尺寸与图片一致 canvas.width = img.naturalWidth || img.width; canvas.height = img.naturalHeight || img.height; // 白色背景 ctx.fillStyle = '#ffffff'; ctx.fillRect(0, 0, canvas.width, canvas.height); // 绘制图片,保持原始尺寸 ctx.drawImage(img, 0, 0, canvas.width, canvas.height); canvas.toBlob(blob => { if (!blob) { throw new Error('格式转换失败'); } const baseName = file.name.replace(/\.[^/.]+$/, ""); const newFileName = `${baseName}.jpg`; const newFile = new File([blob], newFileName, { type: 'image/jpeg' }); resolve(newFile); }, 'image/jpeg', 1); } catch (error) { reject(new Error(`转换失败: ${error.message}`)); } }; img.onerror = function (e) { console.error('图片加载错误:', e); reject(new Error('无法加载图片')); }; // 修复SVG的MIME类型 let src = e.target.result; if (file.type === 'image/svg+xml' || file.name.toLowerCase().endsWith('.svg')) { src = src.replace('data:image/svg', 'data:image/svg+xml'); } img.src = src; }; reader.onerror = function () { reject(new Error('读取文件失败')); }; reader.readAsDataURL(file); }); } // 压缩图片至2MB以内 function compressImage(file) { return new Promise((resolve, reject) => { if (file.size <= MAX_FILE_SIZE) { resolve(file); return; } previewText.textContent = `正在压缩图片...`; new ImageCompressor(file, { quality: 0.8, maxWidth: 2000, maxHeight: 2000, success(result) { if (result.size > MAX_FILE_SIZE) { new ImageCompressor(result, { quality: 0.5, maxWidth: 1600, maxHeight: 1600, success(secondResult) { if (secondResult.size > MAX_FILE_SIZE) { new ImageCompressor(secondResult, { quality: 0.3, maxWidth: 1200, maxHeight: 1200, success(finalResult) { const compressedFile = new File( [finalResult], file.name, { type: file.type } ); resolve(compressedFile); }, error() { reject(new Error(`压缩失败`)); } }); } else { const compressedFile = new File( [secondResult], file.name, { type: file.type } ); resolve(compressedFile); } }, error() { reject(new Error(`压缩失败`)); } }); } else { const compressedFile = new File( [result], file.name, { type: file.type } ); resolve(compressedFile); } }, error() { reject(new Error(`压缩失败`)); } }); }); } // 计算实际马赛克大小 function calculateMosaicSize() { if (!originalCanvas) return 1; const baseSize = Math.min(originalCanvas.width, originalCanvas.height); return Math.max(2, Math.round(baseSize * mosaicSizePercent / 100)); } // 更新马赛克大小显示 function updateMosaicSizeDisplay() { if (isMosaicing && originalCanvas) { const pixelSize = calculateMosaicSize(); brushSizeInfo.textContent = `实际大小: ${pixelSize}px`; } } // 检查是否是图片URL function isImageUrl(text) { try { if (text.startsWith('//')) text = `https:${text}`; const url = new URL(text); const ext = url.pathname.split('.').pop().toLowerCase(); return allowedExtensions.includes(ext) || ['bmp', 'webp', 'tiff', 'svg', 'jpeg'].includes(ext); } catch { return false; } } // 从URL获取图片文件 function fetchImageFromUrl(url) { previewText.textContent = '正在下载图片...'; previewWrapper.style.display = 'flex'; if (squarePreview) squarePreview.style.display = 'none'; return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: url, responseType: 'blob', overrideMimeType: 'image/*', onload: function (response) { const blob = response.response; let type = 'image/jpeg'; const extension = url.split('.').pop().toLowerCase(); if (extension === 'png') type = 'image/png'; else if (extension === 'gif') type = 'image/gif'; else if (extension === 'webp') type = 'image/webp'; const contentType = response.responseHeaders.match(/Content-Type:\s*(image\/\w+)/i); if (contentType && contentType[1]) { type = contentType[1]; } const fileName = url.split('/').pop(); const correctedBlob = new Blob([blob], { type }); const file = new File([correctedBlob], fileName, { type }); convertToJpg(file) .then(convertedFile => compressImage(convertedFile)) .then(compressedFile => resolve(compressedFile)) .catch(e => { console.error(e); reject(new Error('下载后处理失败')); }); }, onerror: function () { reject(new Error('图片下载失败')); } }); }); } // 更新正方形预览 function updateSquarePreview() { if (!squarePreviewImage) return; if (isCropping && cropper) { const canvas = cropper.getCroppedCanvas(); if (!canvas) return; const scale = 100 / canvas.width; squarePreviewImage.src = canvas.toDataURL(); squarePreviewImage.style.width = `${canvas.width * scale}px`; squarePreviewImage.style.height = `${canvas.height * scale}px`; squarePreviewImage.style.left = '50%'; squarePreviewImage.style.transform = 'translateX(-50%)'; squarePreviewImage.classList.add('cropper-mode'); } else { squarePreviewImage.src = previewImage.src; squarePreviewImage.style.width = '100%'; squarePreviewImage.style.height = '100%'; squarePreviewImage.style.left = '0'; squarePreviewImage.style.transform = 'none'; squarePreviewImage.classList.remove('cropper-mode'); } } // #endregion // #region 裁切功能 function initCropper() { if (cropper) { cropper.destroy(); } isCropping = true; cropper = new Cropper(previewImage, { viewMode: 1, autoCropArea: 1, responsive: true, movable: true, rotatable: true, scalable: true, zoomable: true, background: false, ready: updateSquarePreview, cropend: updateSquarePreview }); updateSquarePreview(); } function startCropping(e) { e.preventDefault(); if (!currentFile || isMosaicing) return; initCropper(); cropBtn.style.display = 'none'; mosaicBtn.style.display = 'none'; cropConfirmBtn.style.display = 'inline-block'; cropCancelBtn.style.display = 'inline-block'; previewText.textContent = '调整裁切区域,点击"确认裁切"'; } function confirmCrop(e) { e.preventDefault(); if (!cropper || !isCropping) return; cropper.getCroppedCanvas().toBlob(blob => { const fileName = currentFile.name.replace(/\.[^/.]+$/, '') + '_cropped.png'; const croppedFile = new File([blob], fileName, { type: 'image/png' }); compressImage(croppedFile).then(compressedFile => { currentFile = compressedFile; const dataTransfer = new DataTransfer(); dataTransfer.items.add(currentFile); fileInput.files = dataTransfer.files; updatePreview(currentFile); endCropping(); }).catch(() => { previewText.textContent = `裁切后压缩失败`; }); }, 'image/png'); } function cancelCrop(e) { e.preventDefault(); if (!isCropping) return; updatePreview(currentFile); endCropping(); } function endCropping() { isCropping = false; if (cropper) { cropper.destroy(); cropper = null; } showEditButtons(); cropConfirmBtn.style.display = 'none'; cropCancelBtn.style.display = 'none'; updateSquarePreview(); } // #endregion // #region 马赛克功能 function initMosaic() { if (canvasContainer) { canvasContainer.remove(); } canvasContainer = document.createElement('div'); canvasContainer.className = 'mosaic-canvas-container'; originalCanvas = document.createElement('canvas'); const oCtx = originalCanvas.getContext('2d'); tempCanvas = document.createElement('canvas'); ctx = tempCanvas.getContext('2d'); originalCanvas.width = previewImage.naturalWidth; originalCanvas.height = previewImage.naturalHeight; tempCanvas.width = previewImage.naturalWidth; tempCanvas.height = previewImage.naturalHeight; oCtx.drawImage(previewImage, 0, 0); ctx.drawImage(previewImage, 0, 0); canvasScale = 1.0; if (originalCanvas.width > MAX_CANVAS_WIDTH) { canvasScale = MAX_CANVAS_WIDTH / originalCanvas.width; } const scaledWidth = originalCanvas.width * canvasScale; const scaledHeight = originalCanvas.height * canvasScale; canvasContainer.style.width = `${scaledWidth}px`; canvasContainer.style.height = `${scaledHeight}px`; tempCanvas.style.width = `${scaledWidth}px`; tempCanvas.style.height = `${scaledHeight}px`; tempCanvas.style.display = 'block'; canvasContainer.appendChild(tempCanvas); const parent = previewImage.parentNode; parent.replaceChild(canvasContainer, previewImage); bindTempCanvasEvents(); updateSquarePreview(); updateMosaicSizeDisplay(); } function bindTempCanvasEvents() { if (!tempCanvas) return; tempCanvas.addEventListener('mousedown', function(e) { if (!isMosaicing) return; if (e.button === 0) { isDrawing = true; drawMosaic(e); } }); tempCanvas.addEventListener('contextmenu', function(e) { if (isMosaicing) { e.preventDefault(); } }); } function startMosaicing(e) { e.preventDefault(); if (!currentFile || isCropping) return; isMosaicing = true; initMosaic(); cropBtn.style.display = 'none'; mosaicBtn.style.display = 'none'; mosaicConfirmBtn.style.display = 'inline-block'; mosaicCancelBtn.style.display = 'inline-block'; mosaicControls.style.display = 'block'; previewText.textContent = '按住鼠标左键添加马赛克'; } function drawMosaic(e) { if (!isDrawing || !ctx || !originalCanvas || !tempCanvas || e.button !== 0) return; const rect = tempCanvas.getBoundingClientRect(); const x = Math.floor((e.clientX - rect.left) / canvasScale); const y = Math.floor((e.clientY - rect.top) / canvasScale); if (x < 0 || x >= originalCanvas.width || y < 0 || y >= originalCanvas.height) { return; } const blockSize = calculateMosaicSize(); const imageData = ctx.getImageData( Math.max(0, x - blockSize/2), Math.max(0, y - blockSize/2), blockSize, blockSize ); const data = imageData.data; let r = 0, g = 0, b = 0; let count = 0; for (let i = 0; i < data.length; i += 4) { r += data[i]; g += data[i + 1]; b += data[i + 2]; count++; } r = Math.floor(r / count); g = Math.floor(g / count); b = Math.floor(b / count); ctx.fillStyle = `rgb(${r}, ${g}, ${b})`; ctx.fillRect( Math.max(0, x - blockSize/2), Math.max(0, y - blockSize/2), blockSize, blockSize ); updateSquarePreview(); } function confirmMosaic(e) { e.preventDefault(); if (!isMosaicing || !tempCanvas || !canvasContainer) return; tempCanvas.toBlob(blob => { const fileName = currentFile.name.replace(/\.[^/.]+$/, '') + '_mosaic.jpg'; const mosaicFile = new File([blob], fileName, { type: 'image/jpeg' }); compressImage(mosaicFile).then(compressedFile => { currentFile = compressedFile; const dataTransfer = new DataTransfer(); dataTransfer.items.add(currentFile); fileInput.files = dataTransfer.files; const parent = canvasContainer.parentNode; previewImage.src = tempCanvas.toDataURL(); previewImage.style.maxWidth = '100%'; previewImage.style.maxHeight = '300px'; parent.replaceChild(previewImage, canvasContainer); originalCanvas = null; tempCanvas = null; canvasContainer = null; ctx = null; endMosaicing(); updatePreview(currentFile); }).catch(() => { previewText.textContent = `马赛克处理失败`; }); }, 'image/jpeg', 0.95); } function cancelMosaic(e) { e.preventDefault(); if (!isMosaicing || !tempCanvas || !canvasContainer) return; const parent = canvasContainer.parentNode; previewImage.style.maxWidth = '100%'; previewImage.style.maxHeight = '300px'; parent.replaceChild(previewImage, canvasContainer); originalCanvas = null; tempCanvas = null; canvasContainer = null; ctx = null; endMosaicing(); updatePreview(currentFile); } function endMosaicing() { isMosaicing = false; isDrawing = false; showEditButtons(); mosaicConfirmBtn.style.display = 'none'; mosaicCancelBtn.style.display = 'none'; mosaicControls.style.display = 'none'; } // #endregion // #region 预览更新 async function updatePreview(file) { try { const convertedFile = await convertToJpg(file); const compressedFile = await compressImage(convertedFile); if (!compressedFile) { previewWrapper.style.display = 'none'; controls.style.display = 'none'; mosaicControls.style.display = 'none'; hideEditButtons(); if (squarePreview) squarePreview.style.display = 'none'; return false; } const reader = new FileReader(); reader.onload = function (e) { previewImage.src = e.target.result; let extraInfo = ''; if (file.name !== convertedFile.name) { extraInfo += ` (已转换格式)`; } if (convertedFile.size > compressedFile.size) { const originalSizeMB = (convertedFile.size / (1024 * 1024)).toFixed(2); const compressedSizeMB = (compressedFile.size / (1024 * 1024)).toFixed(2); extraInfo += ` (已压缩: ${originalSizeMB}MB → ${compressedSizeMB}MB)`; } previewText.textContent = `${compressedFile.name} (${Math.round(compressedFile.size / 1024)}KB)${extraInfo}`; previewWrapper.style.display = 'flex'; controls.style.display = 'flex'; showEditButtons(); if (isUploadPhotoPage && squarePreview) { squarePreview.style.display = 'block'; updateSquarePreview(); } endCropping(); endMosaicing(); }; reader.readAsDataURL(compressedFile); currentFile = compressedFile; return true; } catch (error) { previewText.textContent = `处理失败: ${error.message}`; previewWrapper.style.display = 'flex'; controls.style.display = 'flex'; mosaicControls.style.display = 'none'; hideEditButtons(); if (squarePreview) squarePreview.style.display = 'none'; return false; } } // #endregion // #region 剪贴板处理 async function processClipboardData(clipboardData) { try { if (clipboardData.items) { for (let i = 0; i < clipboardData.items.length; i++) { const item = clipboardData.items[i]; if (item.type.indexOf('image') !== -1) { const blob = item.getAsFile(); if (blob) { await updatePreview(blob); const dataTransfer = new DataTransfer(); dataTransfer.items.add(currentFile || blob); fileInput.files = dataTransfer.files; return true; } } } } let text; if (typeof clipboardData.text === 'function') { text = await clipboardData.text(); } else if (clipboardData.getData) { text = clipboardData.getData('text/plain'); } if (text) { const trimmedText = text.trim(); if (isImageUrl(trimmedText)) { try { const imageFile = await fetchImageFromUrl(trimmedText); if (imageFile) { await updatePreview(imageFile); const dataTransfer = new DataTransfer(); dataTransfer.items.add(currentFile || imageFile); fileInput.files = dataTransfer.files; return true; } } catch (error) { previewText.textContent = error.message; previewWrapper.style.display = 'flex'; if (squarePreview) squarePreview.style.display = 'none'; } } else { previewText.textContent = '未发现图片内容'; previewWrapper.style.display = 'flex'; } } } catch { previewText.textContent = '处理内容失败'; previewWrapper.style.display = 'flex'; } return false; } async function handleClipboard(e) { e.preventDefault(); if (navigator.clipboard && navigator.clipboard.read) { try { previewText.textContent = '正在获取剪贴板...'; previewWrapper.style.display = 'flex'; // 尝试读取图片 const clipboardItems = await navigator.clipboard.read(); for (const item of clipboardItems) { for (const type of item.types) { if (type.startsWith('image/')) { const blob = await item.getType(type); if (blob) { const fileName = `clipboard-${Date.now()}.${type.split('/')[1] || 'jpg'}`; const file = new File([blob], fileName, { type: blob.type }); await updatePreview(file); const dataTransfer = new DataTransfer(); dataTransfer.items.add(currentFile || file); fileInput.files = dataTransfer.files; return; } } } } // 尝试读取文本 const text = await navigator.clipboard.readText(); if (text) { const trimmedText = text.trim(); if (isImageUrl(trimmedText)) { const imageFile = await fetchImageFromUrl(trimmedText); if (imageFile) { await updatePreview(imageFile); const dataTransfer = new DataTransfer(); dataTransfer.items.add(currentFile || imageFile); fileInput.files = dataTransfer.files; return; } } else { previewText.textContent = '未发现图片内容'; } } else { previewText.textContent = '剪贴板为空'; } } catch { startCompatibilityMode(); } } else { startCompatibilityMode(); } } function startCompatibilityMode() { pasteHelper.value = ''; pasteHelper.focus(); previewText.textContent = '请在此粘贴内容'; previewWrapper.style.display = 'flex'; const handlePaste = async function(e) { e.stopPropagation(); e.preventDefault(); await processClipboardData(e.clipboardData); pasteHelper.blur(); document.removeEventListener('paste', handlePaste); document.removeEventListener('click', handleClickOutside); }; // 点击外部取消 const handleClickOutside = function() { pasteHelper.blur(); document.removeEventListener('paste', handlePaste); document.removeEventListener('click', handleClickOutside); if (previewText.textContent === '请在此粘贴内容') { previewText.textContent = '操作已取消'; } }; document.addEventListener('paste', handlePaste); setTimeout(() => { document.addEventListener('click', handleClickOutside); }, 100); } // #endregion // #region 事件监听 // 裁切相关事件 cropBtn.addEventListener('click', startCropping); cropConfirmBtn.addEventListener('click', confirmCrop); cropCancelBtn.addEventListener('click', cancelCrop); // 马赛克相关事件 mosaicBtn.addEventListener('click', startMosaicing); mosaicConfirmBtn.addEventListener('click', confirmMosaic); mosaicCancelBtn.addEventListener('click', cancelMosaic); // 剪贴板按钮事件 getClipboardBtn.addEventListener('click', handleClipboard); // 马赛克比例调整事件 blockSizeSlider.addEventListener('input', function() { mosaicSizePercent = parseInt(this.value); blockSizeValue.textContent = `${mosaicSizePercent}%`; updateMosaicSizeDisplay(); }); // 鼠标绘制事件 document.addEventListener('mousemove', function(e) { if (isMosaicing && isDrawing && tempCanvas && e.target === tempCanvas) { drawMosaic(e); } }); document.addEventListener('mouseup', function() { isDrawing = false; }); document.addEventListener('mouseleave', function() { isDrawing = false; }); // 文件输入事件 fileInput.addEventListener('change', async function () { if (this.files.length > 0) { await updatePreview(this.files[0]); } else { hideEditButtons(); } }); if (fileInput.files.length > 0) { updatePreview(fileInput.files[0]); } // 全局粘贴事件 document.addEventListener('paste', async function (e) { const activeElement = document.activeElement; if (!activeElement || !(activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA' || activeElement.isContentEditable)) { const handled = await processClipboardData(e.clipboardData); if (handled) { e.preventDefault(); } } }); // 添加提示信息 const hint = document.createElement('div'); hint.className = 'bgm-hint'; hint.textContent = '提示:点击按钮获取剪贴板内容,或直接按 Ctrl+V 粘贴图片'; fileInput.parentNode.insertBefore(hint, fileInput.nextSibling); // #endregion })();