您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
支持自由比例裁切+顶部对齐正方形预览
当前为
// ==UserScript== // @name Bangumi 图片上传增强 // @namespace https://bgm.tv/group/topic/431819 // @version 1.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://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://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 // @grant GM_xmlhttpRequest // @grant GM_getResourceText // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/cropper.min.js // @resource cropperCSS https://cdn.jsdelivr.net/npm/[email protected]/dist/cropper.min.css // @license MIT // ==/UserScript== (function() { 'use strict'; // 注入所有CSS样式 const css = ` .bgm-uploader-container { margin-top: 15px; } .bgm-preview-container { display: flex; gap: 15px; margin-bottom: 10px; flex-wrap: wrap; align-items: flex-start; } .bgm-original-preview { max-width: 300px; display: none; } .bgm-square-preview { display: none; width: 100px; } .bgm-preview-image { max-width: 100%; max-height: 200px; border: 1px solid #ddd; } /* 静态正方形预览容器 */ .bgm-square-image-container { width: 100px; height: 100px; overflow: hidden; border: 1px solid #ddd; } /* 静态正方形预览图片 - 顶部对齐 */ .bgm-square-image { width: 100%; height: 100%; object-fit: cover; object-position: top center; /* 关键修改:确保从顶部开始显示 */ } /* 裁切过程中的正方形预览 */ .bgm-cropper-preview { width: 100px; height: 100px; overflow: hidden; display: none; border: 1px solid #ddd; position: relative; } .bgm-cropper-preview-image { position: absolute; left: 0; transform-origin: left top; /* 关键修改:从左上角开始变换 */ } .bgm-preview-text { margin-top: 5px; font-size: 0.8em; color: #666; } .bgm-controls { margin-top: 10px; display: none; } .bgm-hint { color: #666; font-size: 0.9em; margin-bottom: 10px; } `; 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 fileInput = document.querySelector('input[type="file"][name="picfile"]'); if (!fileInput) return; // 检查是否是/upload_photo页面 const isUploadPhotoPage = window.location.pathname.includes('/upload_photo'); // 创建UI容器 const container = document.createElement('div'); container.className = 'bgm-uploader-container'; // 预览容器(使用flex布局) 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'; // 正方形预览(仅在/upload_photo页面显示) let squarePreview = null; let squarePreviewImage = null; // 裁切过程中的强制正方形预览 let cropperPreview = null; let cropperPreviewImage = null; if (isUploadPhotoPage) { // 静态正方形预览 squarePreview = document.createElement('div'); squarePreview.className = 'bgm-square-preview'; const squarePreviewLabel = document.createElement('div'); squarePreviewLabel.textContent = '正方形预览:'; squarePreviewLabel.className = 'bgm-preview-text'; const squareImageContainer = document.createElement('div'); squareImageContainer.className = 'bgm-square-image-container'; squarePreviewImage = document.createElement('img'); squarePreviewImage.className = 'bgm-square-image'; squareImageContainer.appendChild(squarePreviewImage); squarePreview.appendChild(squarePreviewLabel); squarePreview.appendChild(squareImageContainer); // 裁切过程中的正方形预览 cropperPreview = document.createElement('div'); cropperPreview.className = 'bgm-cropper-preview'; const cropperPreviewLabel = document.createElement('div'); cropperPreviewLabel.textContent = '裁切预览:'; cropperPreviewLabel.className = 'bgm-preview-text'; cropperPreviewImage = document.createElement('img'); cropperPreviewImage.className = 'bgm-cropper-preview-image'; cropperPreview.appendChild(cropperPreviewLabel); cropperPreview.appendChild(cropperPreviewImage); } // 控制按钮 const controls = document.createElement('div'); controls.className = 'bgm-controls'; const cropBtn = document.createElement('button'); cropBtn.textContent = '裁切图片'; const confirmBtn = document.createElement('button'); confirmBtn.textContent = '确认裁切'; confirmBtn.style.display = 'none'; const cancelBtn = document.createElement('button'); cancelBtn.textContent = '取消裁切'; cancelBtn.style.display = 'none'; // 组装UI originalPreview.appendChild(previewImage); originalPreview.appendChild(previewText); previewWrapper.appendChild(originalPreview); if (isUploadPhotoPage) { if (squarePreview) previewWrapper.appendChild(squarePreview); if (cropperPreview) previewWrapper.appendChild(cropperPreview); } controls.appendChild(cropBtn); controls.appendChild(confirmBtn); controls.appendChild(cancelBtn); container.appendChild(previewWrapper); container.appendChild(controls); fileInput.parentNode.insertBefore(container, fileInput.nextSibling); // 支持的图片后缀 const imageExtensions = ['jpg', 'png', 'gif']; let currentFile = null; let cropper = null; let isCropping = false; // 检查是否是图片URL function isImageUrl(text) { try { const url = new URL(text); const ext = url.pathname.split('.').pop().toLowerCase(); return imageExtensions.includes(ext); } catch { return false; } } // 从URL获取图片文件 function fetchImageFromUrl(url) { previewText.textContent = '正在下载图片...'; originalPreview.style.display = 'block'; if (squarePreview) squarePreview.style.display = 'none'; if (cropperPreview) cropperPreview.style.display = 'none'; return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: url, responseType: 'blob', onload: function(response) { const blob = response.response; const fileName = url.split('/').pop(); const file = new File([blob], fileName, { type: blob.type }); resolve(file); }, onerror: function(error) { reject(new Error('图片下载失败: ' + error.statusText)); } }); }); } // 更新强制正方形预览(从顶部开始显示) function updateSquarePreview() { if (!cropper || !cropperPreviewImage) return; const canvas = cropper.getCroppedCanvas(); if (!canvas) return; // 计算缩放比例以适应正方形(保持顶部对齐) const scale = 100 / canvas.width; // 基于宽度缩放 // 更新预览图片 cropperPreviewImage.src = canvas.toDataURL(); cropperPreviewImage.style.width = `${canvas.width * scale}px`; cropperPreviewImage.style.height = `${canvas.height * scale}px`; // 顶部对齐,水平居中 cropperPreviewImage.style.top = '0'; cropperPreviewImage.style.left = '50%'; cropperPreviewImage.style.transform = 'translateX(-50%)'; } // 初始化裁切工具 function initCropper() { if (cropper) { cropper.destroy(); } // 显示裁切预览,隐藏静态预览 if (cropperPreview) { cropperPreview.style.display = 'block'; } if (squarePreview) { squarePreview.style.display = 'none'; } cropper = new Cropper(previewImage, { viewMode: 1, autoCropArea: 0.8, responsive: true, movable: true, rotatable: true, scalable: true, zoomable: true, crop: function() { // 裁切时更新强制正方形预览 updateSquarePreview(); } }); // 初始更新一次预览 updateSquarePreview(); } // 开始裁切 function startCropping(e) { e.preventDefault(); if (!currentFile) return; isCropping = true; initCropper(); cropBtn.style.display = 'none'; confirmBtn.style.display = 'inline-block'; cancelBtn.style.display = 'inline-block'; previewText.textContent = '拖动边框调整裁切区域,然后点击"确认裁切"'; } // 确认裁切 function confirmCrop(e) { e.preventDefault(); if (!cropper || !isCropping) return; cropper.getCroppedCanvas({ fillColor: 'transparent' }).toBlob(blob => { const fileName = currentFile.name.replace(/\.[^/.]+$/, '') + '_cropped.png'; currentFile = new File([blob], fileName, { type: 'image/png' }); // 更新文件输入 const dataTransfer = new DataTransfer(); dataTransfer.items.add(currentFile); fileInput.files = dataTransfer.files; // 更新预览 updatePreview(currentFile); endCropping(); // 恢复显示静态正方形预览 if (squarePreview) { squarePreview.style.display = 'block'; } if (cropperPreview) { cropperPreview.style.display = 'none'; } }, 'image/png'); } // 取消裁切 function cancelCrop(e) { e.preventDefault(); if (!isCropping) return; updatePreview(currentFile); endCropping(); // 恢复显示静态正方形预览 if (squarePreview) { squarePreview.style.display = 'block'; } if (cropperPreview) { cropperPreview.style.display = 'none'; } } // 结束裁切模式 function endCropping() { isCropping = false; if (cropper) { cropper.destroy(); cropper = null; } cropBtn.style.display = 'inline-block'; confirmBtn.style.display = 'none'; cancelBtn.style.display = 'none'; } // 更新预览 function updatePreview(file) { if (!file) { originalPreview.style.display = 'none'; controls.style.display = 'none'; if (squarePreview) squarePreview.style.display = 'none'; if (cropperPreview) cropperPreview.style.display = 'none'; return false; } const allowedTypes = ['image/jpeg', 'image/png', 'image/gif']; if (!allowedTypes.includes(file.type)) { previewText.textContent = `错误:不支持的文件格式 (${file.type}),请上传 JPG/PNG/GIF 图片`; previewImage.style.display = 'none'; originalPreview.style.display = 'block'; controls.style.display = 'none'; if (squarePreview) squarePreview.style.display = 'none'; if (cropperPreview) cropperPreview.style.display = 'none'; return false; } const reader = new FileReader(); reader.onload = function(e) { previewImage.src = e.target.result; previewImage.style.display = 'block'; previewText.textContent = `已选择: ${file.name} (${Math.round(file.size/1024)}KB)`; originalPreview.style.display = 'block'; controls.style.display = 'block'; // 更新静态正方形预览(仅在非裁切模式下显示) if (isUploadPhotoPage && squarePreviewImage && !isCropping) { squarePreviewImage.src = e.target.result; squarePreview.style.display = 'block'; if (cropperPreview) cropperPreview.style.display = 'none'; } // 重置裁切状态 endCropping(); }; reader.readAsDataURL(file); currentFile = file; return true; } // 事件监听 cropBtn.addEventListener('click', startCropping); confirmBtn.addEventListener('click', confirmCrop); cancelBtn.addEventListener('click', cancelCrop); // 监听文件输入变化 fileInput.addEventListener('change', function() { if (this.files.length > 0) { updatePreview(this.files[0]); } }); // 监听粘贴事件 document.addEventListener('paste', async function(e) { // 检查剪贴板中的图片 if (e.clipboardData.items && e.clipboardData.items.length > 0) { for (let i = 0; i < e.clipboardData.items.length; i++) { const item = e.clipboardData.items[i]; if (item.type.indexOf('image') !== -1) { const blob = item.getAsFile(); if (updatePreview(blob)) { const dataTransfer = new DataTransfer(); dataTransfer.items.add(blob); fileInput.files = dataTransfer.files; } e.preventDefault(); return; } } } // 检查剪贴板中的文本(图片URL) const pastedText = e.clipboardData.getData('text/plain').trim(); if (isImageUrl(pastedText)) { e.preventDefault(); try { const imageFile = await fetchImageFromUrl(pastedText); if (updatePreview(imageFile)) { const dataTransfer = new DataTransfer(); dataTransfer.items.add(imageFile); fileInput.files = dataTransfer.files; } } catch (error) { previewText.textContent = error.message; previewImage.style.display = 'none'; originalPreview.style.display = 'block'; if (squarePreview) squarePreview.style.display = 'none'; if (cropperPreview) cropperPreview.style.display = 'none'; } } }); // 添加提示信息 const hint = document.createElement('div'); hint.className = 'bgm-hint'; hint.textContent = '提示:可直接粘贴图片或图片链接'; fileInput.parentNode.insertBefore(hint, fileInput.nextSibling); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址