WWW.YOUINFO.SITE
标签聚合 颜色

/tag/颜色

LinuxDo 最新话题 · 2026-06-10 11:36:24+08:00 · tech

证件照换底(支持自定义颜色代码) <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>证件照换底 - 离线隐私处理</title> <style> *{margin:0;padding:0;box-sizing:border-box;} body{ font-family: -apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif; background:#f0f2f5;min-height:100vh;color:#1a1a2e;line-height:1.6; } .header{ background:#fff;border-bottom:1px solid #e0e0e0;padding:14px 0; position:sticky;top:0;z-index:100;box-shadow:0 1px 3px rgba(0,0,0,0.06); } .header-inner{max-width:1200px;margin:0 auto;padding:0 24px;display:flex;align-items:center;justify-content:space-between;} .logo{display:flex;align-items:center;gap:10px;font-size:1.25rem;font-weight:700;} .logo-icon{width:38px;height:38px;border-radius:11px;background:linear-gradient(135deg,#4f6ef7,#7b8ff7);display:flex;align-items:center;justify-content:center;font-size:1.3rem;color:#fff;} .privacy-badge{display:flex;align-items:center;gap:6px;font-size:0.8rem;color:#27ae60;background:#eafaf1;padding:5px 12px;border-radius:20px;font-weight:600;} .privacy-dot{width:7px;height:7px;border-radius:50%;background:#27ae60;animation:pulse 2s infinite;} @keyframes pulse{0%,100%{opacity:1}50%{opacity:0.4}} .main-container{max-width:1200px;margin:0 auto;padding:24px;display:grid;grid-template-columns:1fr 1fr;gap:24px;} @media(max-width:900px){.main-container{grid-template-columns:1fr;max-width:520px;}} .card{background:#fff;border-radius:16px;padding:22px;box-shadow:0 4px 24px rgba(0,0,0,0.08);border:1px solid #e0e0e0;} .card-title{font-size:1rem;font-weight:700;margin-bottom:16px;} .upload-zone{border:2px dashed #d0d5e0;border-radius:10px;padding:36px 20px;text-align:center;cursor:pointer;background:#fafbfc;position:relative;min-height:180px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;transition:0.25s;} .upload-zone:hover,.upload-zone.drag-over{border-color:#4f6ef7;background:#eef1ff;} .upload-zone.has-image{border-style:solid;border-color:#e0e0e0;padding:6px;min-height:auto;} .upload-icon{font-size:2.8rem;opacity:0.5;} .upload-text{font-size:0.9rem;color:#555;} .upload-hint{font-size:0.75rem;color:#999;} .upload-zone input[type="file"]{position:absolute;inset:0;opacity:0;cursor:pointer;} .preview-image{width:100%;max-height:350px;object-fit:contain;display:block;border-radius:5px;} .image-container{position:relative;display:block;width:100%;} .image-actions-overlay{position:absolute;top:8px;right:8px;z-index:5;} .btn-icon-sm{width:32px;height:32px;border-radius:50%;border:none;background:rgba(0,0,0,0.5);color:#fff;cursor:pointer;font-size:0.85rem;display:flex;align-items:center;justify-content:center;} .btn{display:inline-flex;align-items:center;justify-content:center;gap:5px;padding:9px 18px;border-radius:25px;font-size:0.88rem;font-weight:600;cursor:pointer;border:none;transition:0.25s;white-space:nowrap;} .btn:active{transform:scale(0.96);} .btn-primary{background:#4f6ef7;color:#fff;} .btn-primary:hover{background:#3b54db;} .btn-outline{background:#fff;color:#4f6ef7;border:2px solid #4f6ef7;} .btn-outline:hover{background:#eef1ff;} .btn-success{background:#27ae60;color:#fff;} .btn-success:hover{opacity:0.9;} .btn-lg{padding:11px 26px;font-size:0.95rem;border-radius:28px;} .btn:disabled{opacity:0.5;cursor:not-allowed;pointer-events:none;} .btn-block{width:100%;} .section-label{font-size:0.82rem;font-weight:600;margin:14px 0 8px;} .color-presets{display:flex;gap:10px;flex-wrap:wrap;margin-bottom:8px;} .color-preset{width:40px;height:40px;border-radius:50%;cursor:pointer;border:3px solid transparent;transition:0.25s;box-shadow:0 1px 3px rgba(0,0,0,0.1);position:relative;} .color-preset:hover{transform:scale(1.12);} .color-preset.active{border-color:#1a1a2e;box-shadow:0 0 0 4px rgba(0,0,0,0.08);} .color-preset.active::after{content:'✓';position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:#fff;font-weight:bold;font-size:0.9rem;text-shadow:0 1px 2px rgba(0,0,0,0.5);} .custom-color-section{background:#f8f9fb;border-radius:12px;padding:12px 14px;margin-bottom:4px;border:1px solid #e8eaef;} .custom-color-row{display:flex;gap:10px;align-items:center;flex-wrap:wrap;} .custom-color-row input[type="color"]{width:40px;height:40px;border-radius:50%;border:2px solid #e0e0e0;cursor:pointer;padding:2px;flex-shrink:0;} .custom-color-row input[type="color"]::-webkit-color-swatch-wrapper{padding:0;} .custom-color-row input[type="color"]::-webkit-color-swatch{border-radius:50%;border:none;} .hex-input-wrapper{position:relative;flex:1;min-width:110px;} .hex-input-wrapper .hash{position:absolute;left:10px;top:50%;transform:translateY(-50%);color:#999;font-weight:600;font-size:0.85rem;pointer-events:none;} .hex-input{width:100%;padding:8px 10px 8px 24px;border:2px solid #e0e5ec;border-radius:10px;font-size:0.85rem;font-family:monospace;font-weight:600;color:#1a1a2e;background:#fff;outline:none;transition:0.25s;} .hex-input:focus{border-color:#4f6ef7;box-shadow:0 0 0 3px rgba(79,110,247,0.1);} .hex-input.valid{border-color:#27ae60;background:#f8fdf8;} .hex-input.invalid{border-color:#e74c3c;background:#fef8f8;} .color-preview-dot{width:30px;height:30px;border-radius:50%;border:2px solid #ddd;flex-shrink:0;box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);} .control-group{margin-bottom:12px;} .control-label{display:flex;justify-content:space-between;align-items:center;font-size:0.82rem;font-weight:600;margin-bottom:5px;} .control-value{font-size:0.75rem;color:#555;background:#f5f6f8;padding:2px 10px;border-radius:12px;} input[type="range"]{-webkit-appearance:none;width:100%;height:5px;border-radius:3px;background:#e0e5ec;outline:none;cursor:pointer;} input[type="range"]::-webkit-slider-thumb{-webkit-appearance:none;width:20px;height:20px;border-radius:50%;background:#4f6ef7;cursor:pointer;} .size-presets{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:8px;} .size-preset{padding:10px 12px;border-radius:8px;border:2px solid #e0e0e0;cursor:pointer;text-align:center;font-size:0.82rem;font-weight:600;transition:0.25s;background:#fafbfc;} .size-preset:hover{border-color:#4f6ef7;background:#eef1ff;} .size-preset.active{border-color:#4f6ef7;background:#eef1ff;color:#4f6ef7;} .size-preset .size-dims{font-size:0.7rem;color:#555;font-weight:400;} .result-container{text-align:center;} .result-image{max-width:100%;max-height:350px;border-radius:10px;box-shadow:0 4px 24px rgba(0,0,0,0.08);border:1px solid #e0e0e0;} .result-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:220px;color:#bbb;gap:10px;} .result-placeholder .icon{font-size:3.5rem;} .action-row{display:flex;gap:10px;flex-wrap:wrap;margin-top:14px;} .toast{position:fixed;bottom:30px;left:50%;transform:translateX(-50%) translateY(100px);background:#1a1a2e;color:#fff;padding:10px 22px;border-radius:25px;font-size:0.85rem;font-weight:600;z-index:999;opacity:0;transition:all 0.35s;pointer-events:none;box-shadow:0 8px 30px rgba(0,0,0,0.25);} .toast.show{opacity:1;transform:translateX(-50%) translateY(0);} .toast.success{background:#27ae60;} .toast.error{background:#e74c3c;} .spinner{display:inline-block;width:16px;height:16px;border:2px solid rgba(255,255,255,0.3);border-top-color:#fff;border-radius:50%;animation:spin 0.7s linear infinite;} @keyframes spin{to{transform:rotate(360deg);}} .footer{text-align:center;padding:18px;font-size:0.75rem;color:#aaa;} </style> </head> <body> <header class="header"> <div class="header-inner"> <div class="logo"><div class="logo-icon">📸</div><span>证件照换底</span></div> <div class="privacy-badge"><span class="privacy-dot"></span>离线处理 · 隐私安全</div> </div> </header> <div class="main-container"> <!-- ========== 左侧面板 ========== --> <div class="card"> <div class="card-title">⚙️ 上传与设置</div> <div class="upload-zone" id="uploadZone"> <span class="upload-icon">📷</span> <span class="upload-text">点击或拖拽上传证件照</span> <span class="upload-hint">支持 JPG / PNG / WebP</span> <input type="file" id="fileInput" accept="image/*"> </div> <div class="section-label">🎨 背景颜色(实时预览)</div> <div class="color-presets" id="colorPresets"> <div class="color-preset active" data-color="#FF0000" style="background:#FF0000;" title="红色"></div> <div class="color-preset" data-color="#FFFFFF" style="background:#FFFFFF;" title="白色"></div> <div class="color-preset" data-color="#438EDB" style="background:#438EDB;" title="蓝色"></div> <div class="color-preset" data-color="#1AAD19" style="background:#1AAD19;" title="绿色"></div> <div class="color-preset" data-color="#808080" style="background:#808080;" title="灰色"></div> <div class="color-preset" data-color="#003399" style="background:#003399;" title="深蓝"></div> </div> <div class="custom-color-section"> <div style="font-size:0.78rem;font-weight:600;margin-bottom:8px;color:#555;">🎯 自定义颜色</div> <div class="custom-color-row"> <input type="color" id="customColorPicker" value="#FF0000" title="取色器"> <div class="hex-input-wrapper"> <span class="hash">#</span> <input type="text" class="hex-input valid" id="hexInput" value="FF0000" maxlength="6" placeholder="输入6位HEX色值"> </div> <div class="color-preview-dot" id="colorPreviewDot" style="background:#FF0000;" title="当前颜色"></div> </div> </div> <div class="control-group" style="margin-top:14px;"> <div class="control-label"><span>🔍 容差范围</span><span class="control-value" id="toleranceVal">50</span></div> <input type="range" id="toleranceSlider" min="10" max="120" value="50" step="1"> </div> <div class="control-group"> <div class="control-label"><span>✨ 边缘羽化</span><span class="control-value" id="featherVal">1</span></div> <input type="range" id="featherSlider" min="0" max="5" value="1" step="0.5"> </div> <div class="control-group"> <div class="control-label"><span>☀️ 亮度</span><span class="control-value" id="brightnessVal">0</span></div> <input type="range" id="brightnessSlider" min="-30" max="30" value="0" step="1"> </div> <div class="section-label">📐 裁剪尺寸</div> <div class="size-presets" id="sizePresets"> <div class="size-preset active" data-width="295" data-height="413" data-name="一寸"><div>一寸</div><div class="size-dims">25×35mm</div></div> <div class="size-preset" data-width="413" data-height="579" data-name="二寸"><div>二寸</div><div class="size-dims">35×49mm</div></div> <div class="size-preset" data-width="260" data-height="378" data-name="小一寸"><div>小一寸</div><div class="size-dims">22×32mm</div></div> <div class="size-preset" data-width="390" data-height="567" data-name="小二寸"><div>小二寸</div><div class="size-dims">33×48mm</div></div> </div> <div class="action-row"> <button class="btn btn-primary btn-lg" id="processBtn" disabled>🎯 开始换底</button> <button class="btn btn-outline" id="resetBtn">🔄 重置</button> </div> </div> <!-- ========== 右侧面板 ========== --> <div class="card"> <div class="card-title">🖼️ 结果预览</div> <div class="result-container"> <div class="result-placeholder" id="resultPlaceholder"> <span class="icon">🖼️</span> <span>处理后的照片将显示在这里</span> </div> <img class="result-image" id="resultImage" style="display:none;" alt="处理结果"> </div> <div class="action-row"> <button class="btn btn-success btn-block" id="downloadBtn" disabled>💾 下载照片</button> </div> </div> </div> <div class="toast" id="toast"></div> <div class="footer">🔒 所有图片处理均在浏览器本地完成,不会上传到任何服务器</div> <script> (function() { // ==================== DOM引用 ==================== var uploadZone = document.getElementById('uploadZone'); var fileInput = document.getElementById('fileInput'); var processBtn = document.getElementById('processBtn'); var resetBtn = document.getElementById('resetBtn'); var downloadBtn = document.getElementById('downloadBtn'); var resultImage = document.getElementById('resultImage'); var resultPH = document.getElementById('resultPlaceholder'); var toastEl = document.getElementById('toast'); var colorPicker = document.getElementById('customColorPicker'); var hexInput = document.getElementById('hexInput'); var colorDot = document.getElementById('colorPreviewDot'); var tolSlider = document.getElementById('toleranceSlider'); var tolVal = document.getElementById('toleranceVal'); var feaSlider = document.getElementById('featherSlider'); var feaVal = document.getElementById('featherVal'); var briSlider = document.getElementById('brightnessSlider'); var briVal = document.getElementById('brightnessVal'); // ==================== 状态 ==================== var originalImage = null; var resultDataURL = null; var currentColor = '#FF0000'; var currentSize = { w: 295, h: 413, name: '一寸' }; var toastTimer = null; var autoTimer = null; var cachedMask = null; // ==================== Toast ==================== function toast(msg, type) { if (toastTimer) clearTimeout(toastTimer); toastEl.textContent = msg; toastEl.className = 'toast ' + (type || '') + ' show'; toastTimer = setTimeout(function() { toastEl.classList.remove('show'); }, 2000); } // ==================== 颜色工具 ==================== function hex3to6(s) { s = s.toUpperCase(); if (s.length === 3) { return s[0]+s[0]+s[1]+s[1]+s[2]+s[2]; } return s; } function isValidHex(s) { return /^[0-9a-fA-F]{3}$/.test(s) || /^[0-9a-fA-F]{6}$/.test(s); } function hexToRgb(hex) { hex = hex.replace('#', ''); if (hex.length === 3) hex = hex3to6(hex); return { r: parseInt(hex.substr(0,2), 16), g: parseInt(hex.substr(2,2), 16), b: parseInt(hex.substr(4,2), 16) }; } // 同步所有颜色UI function applyColor(hex6, source) { hex6 = hex6.toUpperCase(); if (!/^[0-9a-fA-F]{6}$/.test(hex6)) return; currentColor = '#' + hex6; // 取色器 if (source !== 'picker') { colorPicker.value = currentColor; } // 输入框 if (source !== 'input') { hexInput.value = hex6; hexInput.className = 'hex-input valid'; } // 预设圆点 if (source !== 'preset') { var all = document.querySelectorAll('.color-preset'); for (var i = 0; i < all.length; i++) { all[i].classList.remove('active'); } var m = document.querySelector('.color-preset[data-color="#' + hex6 + '"]'); if (m) m.classList.add('active'); } // 预览圆点 colorDot.style.background = currentColor; // 实时更新结果 scheduleAuto(); } // ==================== 颜色事件 ==================== document.getElementById('colorPresets').addEventListener('click', function(e) { var p = e.target.closest('.color-preset'); if (!p) return; var hex6 = p.getAttribute('data-color').replace('#', ''); var all = document.querySelectorAll('.color-preset'); for (var i = 0; i < all.length; i++) all[i].classList.remove('active'); p.classList.add('active'); applyColor(hex6, 'preset'); }); colorPicker.addEventListener('input', function() { var hex = colorPicker.value.replace('#', ''); if (isValidHex(hex)) { applyColor(hex3to6(hex), 'picker'); } }); hexInput.addEventListener('input', function() { var raw = hexInput.value.replace(/[^0-9a-fA-F]/g, '').slice(0, 6); hexInput.value = raw; if (raw.length === 6 && /^[0-9a-fA-F]{6}$/.test(raw)) { hexInput.className = 'hex-input valid'; applyColor(raw, 'input'); } else if (raw.length === 3 && /^[0-9a-fA-F]{3}$/.test(raw)) { hexInput.className = 'hex-input valid'; applyColor(hex3to6(raw), 'input'); } else if (raw.length === 0) { hexInput.className = 'hex-input'; } else if (/^[0-9a-fA-F]+$/.test(raw)) { hexInput.className = 'hex-input'; } else { hexInput.className = 'hex-input invalid'; } }); hexInput.addEventListener('blur', function() { var raw = hexInput.value; if (isValidHex(raw)) { var h6 = hex3to6(raw); hexInput.value = h6; hexInput.className = 'hex-input valid'; applyColor(h6, 'input'); } }); hexInput.addEventListener('paste', function(e) { e.preventDefault(); var t = (e.clipboardData || window.clipboardData).getData('text'); t = t.replace('#', '').replace(/[^0-9a-fA-F]/g, '').slice(0, 6); hexInput.value = t; if (isValidHex(t)) { hexInput.className = 'hex-input valid'; applyColor(hex3to6(t), 'input'); } }); // ==================== 尺寸 ==================== document.getElementById('sizePresets').addEventListener('click', function(e) { var p = e.target.closest('.size-preset'); if (!p) return; var all = document.querySelectorAll('.size-preset'); for (var i = 0; i < all.length; i++) all[i].classList.remove('active'); p.classList.add('active'); currentSize = { w: parseInt(p.getAttribute('data-width')), h: parseInt(p.getAttribute('data-height')), name: p.getAttribute('data-name') }; scheduleAuto(); }); // ==================== 滑块 ==================== tolSlider.addEventListener('input', function() { tolVal.textContent = tolSlider.value; cachedMask = null; scheduleAuto(); }); feaSlider.addEventListener('input', function() { feaVal.textContent = feaSlider.value; scheduleAuto(); }); briSlider.addEventListener('input', function() { briVal.textContent = briSlider.value; scheduleAuto(); }); // ==================== 防抖 ==================== function scheduleAuto() { if (!originalImage) return; if (autoTimer) clearTimeout(autoTimer); autoTimer = setTimeout(function() { doProcess(true); }, 200); } // ==================== 上传 ==================== uploadZone.addEventListener('click', function(e) { if (e.target.tagName === 'BUTTON' || e.target.tagName === 'INPUT') return; fileInput.click(); }); fileInput.addEventListener('change', function(e) { if (e.target.files && e.target.files[0]) { loadFile(e.target.files[0]); } }); uploadZone.addEventListener('dragover', function(e) { e.preventDefault(); uploadZone.classList.add('drag-over'); }); uploadZone.addEventListener('dragleave', function() { uploadZone.classList.remove('drag-over'); }); uploadZone.addEventListener('drop', function(e) { e.preventDefault(); uploadZone.classList.remove('drag-over'); if (e.dataTransfer.files && e.dataTransfer.files[0]) { loadFile(e.dataTransfer.files[0]); } }); function loadFile(file) { if (!file.type.match(/image\//)) { toast('请上传图片文件', 'error'); return; } var reader = new FileReader(); reader.onload = function(ev) { var img = new Image(); img.onload = function() { originalImage = img; cachedMask = null; showPreview(img); processBtn.disabled = false; toast('图片加载成功', 'success'); doProcess(false); }; img.onerror = function() { toast('图片加载失败', 'error'); }; img.src = ev.target.result; }; reader.readAsDataURL(file); } function showPreview(img) { // 清除旧预览 var old = uploadZone.querySelector('.image-container'); if (old) old.remove(); var iconEl = uploadZone.querySelector('.upload-icon'); var textEl = uploadZone.querySelector('.upload-text'); var hintEl = uploadZone.querySelector('.upload-hint'); uploadZone.classList.add('has-image'); if (iconEl) iconEl.style.display = 'none'; if (textEl) textEl.style.display = 'none'; if (hintEl) hintEl.style.display = 'none'; var container = document.createElement('div'); container.className = 'image-container'; var pImg = document.createElement('img'); pImg.className = 'preview-image'; pImg.src = img.src; pImg.alt = '原始照片'; var overlay = document.createElement('div'); overlay.className = 'image-actions-overlay'; var delBtn = document.createElement('button'); delBtn.className = 'btn-icon-sm'; delBtn.innerHTML = '✕'; delBtn.title = '移除图片'; delBtn.addEventListener('click', function(ev) { ev.stopPropagation(); resetAll(); }); overlay.appendChild(delBtn); container.appendChild(pImg); container.appendChild(overlay); uploadZone.appendChild(container); } // ==================== 核心算法 ==================== function doProcess(silent) { if (!originalImage) return; var tolerance = parseInt(tolSlider.value); var featherR = parseFloat(feaSlider.value); var brightness = parseInt(briSlider.value); var target = hexToRgb(currentColor); var srcW = originalImage.naturalWidth; var srcH = originalImage.naturalHeight; // 画到canvas var canvas = document.createElement('canvas'); canvas.width = srcW; canvas.height = srcH; var ctx = canvas.getContext('2d'); ctx.drawImage(originalImage, 0, 0); var imgData = ctx.getImageData(0, 0, srcW, srcH); var data = imgData.data; var w = srcW; var h = srcH; // 步骤1:建立遮罩(仅首次或容差改变时) if (cachedMask === null) { // 采样边缘 var samples = []; var step = Math.max(1, Math.floor(Math.min(w, h) / 30)); var x, y; for (x = 0; x < w; x += step) { samples.push(getPixel(data, w, x, 0)); samples.push(getPixel(data, w, x, h - 1)); } for (y = 0; y < h; y += step) { samples.push(getPixel(data, w, 0, y)); samples.push(getPixel(data, w, w - 1, y)); } // 中位数 samples.sort(function(a, b) { return (a.r + a.g + a.b) - (b.r + b.g + b.b); }); var mid = samples[Math.floor(samples.length / 2)]; var bgR = mid.r; var bgG = mid.g; var bgB = mid.b; // 颜色距离 var mask = new Uint8Array(w * h); var tolSq = tolerance * tolerance; var i; for (i = 0; i < w * h; i++) { var pi = i * 4; var dr = data[pi] - bgR; var dg = data[pi + 1] - bgG; var db = data[pi + 2] - bgB; if (dr * dr + dg * dg + db * db <= tolSq) { mask[i] = 1; } } // Flood fill 从边缘连通 var visited = new Uint8Array(w * h); var queue = []; var head = 0; var x2, y2; for (x2 = 0; x2 < w; x2++) { if (mask[x2] === 1 && !visited[x2]) { visited[x2] = 1; queue.push(x2); } var bIdx = (h - 1) * w + x2; if (mask[bIdx] === 1 && !visited[bIdx]) { visited[bIdx] = 1; queue.push(bIdx); } } for (y2 = 0; y2 < h; y2++) { var lIdx = y2 * w; if (mask[lIdx] === 1 && !visited[lIdx]) { visited[lIdx] = 1; queue.push(lIdx); } var rIdx = y2 * w + (w - 1); if (mask[rIdx] === 1 && !visited[rIdx]) { visited[rIdx] = 1; queue.push(rIdx); } } while (head < queue.length) { var qIdx = queue[head++]; var qx = qIdx % w; var qy = Math.floor(qIdx / w); var nb = []; if (qx > 0) nb.push(qIdx - 1); if (qx < w - 1) nb.push(qIdx + 1); if (qy > 0) nb.push(qIdx - w); if (qy < h - 1) nb.push(qIdx + w); for (var ni = 0; ni < nb.length; ni++) { var nIdx = nb[ni]; if (mask[nIdx] === 1 && !visited[nIdx]) { visited[nIdx] = 1; queue.push(nIdx); } } } cachedMask = visited; } // 步骤2:从原始图重新着色 var origCanvas = document.createElement('canvas'); origCanvas.width = w; origCanvas.height = h; var origCtx = origCanvas.getContext('2d'); origCtx.drawImage(originalImage, 0, 0); var origData = origCtx.getImageData(0, 0, w, h).data; var work = new Uint8ClampedArray(origData.length); var k; for (k = 0; k < origData.length; k++) { work[k] = origData[k]; } var featherPx = Math.round(featherR); var j; for (j = 0; j < work.length; j += 4) { var idx = j / 4; if (cachedMask[idx] === 1) { var alpha = 1; if (featherPx > 0) { alpha = getFeather(cachedMask, idx, w, h, featherPx); } work[j] = Math.round(work[j] * (1 - alpha) + target.r * alpha); work[j + 1] = Math.round(work[j + 1] * (1 - alpha) + target.g * alpha); work[j + 2] = Math.round(work[j + 2] * (1 - alpha) + target.b * alpha); } } // 亮度 if (brightness !== 0) { var bj; for (bj = 0; bj < work.length; bj += 4) { work[bj] = clamp(work[bj] + brightness, 0, 255); work[bj + 1] = clamp(work[bj + 1] + brightness, 0, 255); work[bj + 2] = clamp(work[bj + 2] + brightness, 0, 255); } } var resultID = new ImageData(work, w, h); var workCanvas = document.createElement('canvas'); workCanvas.width = w; workCanvas.height = h; var workCtx = workCanvas.getContext('2d'); workCtx.putImageData(resultID, 0, 0); // 裁剪 var finalCanvas = cropCanvas(workCanvas, currentSize.w, currentSize.h); resultDataURL = finalCanvas.toDataURL('image/png'); resultImage.src = resultDataURL; resultImage.style.display = 'block'; resultPH.style.display = 'none'; downloadBtn.disabled = false; if (!silent) { toast('换底完成!', 'success'); } } function getPixel(data, w, x, y) { var i = (y * w + x) * 4; return { r: data[i], g: data[i + 1], b: data[i + 2] }; } function getFeather(mask, idx, w, h, radius) { var y = Math.floor(idx / w); var x = idx % w; var fg = 0; var total = 0; var dy, dx; for (dy = -radius; dy <= radius; dy++) { for (dx = -radius; dx <= radius; dx++) { var nx = x + dx; var ny = y + dy; if (nx >= 0 && nx < w && ny >= 0 && ny < h) { total++; if (mask[ny * w + nx] === 0) fg++; } } } if (total === 0) return 1; var ratio = fg / total; if (ratio > 0.6) return 0; if (ratio > 0.3) return 0.5; return 1; } function cropCanvas(srcCanvas, tw, th) { var sw = srcCanvas.width; var sh = srcCanvas.height; var sa = sw / sh; var ta = tw / th; var cw, ch, ox, oy; if (sa > ta) { ch = sh; cw = Math.round(sh * ta); ox = Math.round((sw - cw) / 2); oy = 0; } else { cw = sw; ch = Math.round(sw / ta); ox = 0; oy = Math.round((sh - ch) / 2); } var fc = document.createElement('canvas'); fc.width = tw; fc.height = th; var fctx = fc.getContext('2d'); fctx.imageSmoothingEnabled = true; fctx.imageSmoothingQuality = 'high'; fctx.drawImage(srcCanvas, ox, oy, cw, ch, 0, 0, tw, th); return fc; } function clamp(v, min, max) { return Math.max(min, Math.min(max, v)); } // ==================== 按钮 ==================== processBtn.addEventListener('click', function() { processBtn.disabled = true; processBtn.innerHTML = '<span class="spinner"></span> 处理中...'; cachedMask = null; setTimeout(function() { try { doProcess(false); } catch(err) { console.error(err); toast('处理出错: ' + err.message, 'error'); } processBtn.disabled = false; processBtn.innerHTML = '🎯 开始换底'; }, 30); }); resetBtn.addEventListener('click', resetAll); downloadBtn.addEventListener('click', function() { if (!resultDataURL) return; var a = document.createElement('a'); a.download = '证件照_' + currentSize.name + '_' + Date.now() + '.png'; a.href = resultDataURL; document.body.appendChild(a); a.click(); document.body.removeChild(a); toast('下载中', 'success'); }); function resetAll() { originalImage = null; resultDataURL = null; cachedMask = null; if (autoTimer) clearTimeout(autoTimer); resultImage.style.display = 'none'; resultImage.src = ''; resultPH.style.display = ''; downloadBtn.disabled = true; processBtn.disabled = true; processBtn.innerHTML = '🎯 开始换底'; uploadZone.classList.remove('has-image'); var old = uploadZone.querySelector('.image-container'); if (old) old.remove(); var iconEl = uploadZone.querySelector('.upload-icon'); var textEl = uploadZone.querySelector('.upload-text'); var hintEl = uploadZone.querySelector('.upload-hint'); if (iconEl) iconEl.style.display = ''; if (textEl) textEl.style.display = ''; if (hintEl) hintEl.style.display = ''; fileInput.value = ''; tolSlider.value = 50; tolVal.textContent = '50'; feaSlider.value = 1; feaVal.textContent = '1'; briSlider.value = 0; briVal.textContent = '0'; currentColor = '#FF0000'; colorPicker.value = '#FF0000'; hexInput.value = 'FF0000'; hexInput.className = 'hex-input valid'; colorDot.style.background = '#FF0000'; var all = document.querySelectorAll('.color-preset'); for (var i = 0; i < all.length; i++) all[i].classList.remove('active'); var red = document.querySelector('.color-preset[data-color="#FF0000"]'); if (red) red.classList.add('active'); var allS = document.querySelectorAll('.size-preset'); for (var j = 0; j < allS.length; j++) allS[j].classList.remove('active'); var yc = document.querySelector('.size-preset[data-name="一寸"]'); if (yc) yc.classList.add('active'); currentSize = { w: 295, h: 413, name: '一寸' }; } // ==================== 快捷键 ==================== document.addEventListener('keydown', function(e) { if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { e.preventDefault(); if (!processBtn.disabled) processBtn.click(); } if ((e.ctrlKey || e.metaKey) && e.key === 's') { e.preventDefault(); if (!downloadBtn.disabled) downloadBtn.click(); } }); console.log('证件照换底工具已就绪 - 离线处理 - 实时预览'); })(); </script> </body> </html> 2 个帖子 - 1 位参与者 阅读完整话题

IT之家 · 2026-06-08 16:32:36+08:00 · tech

IT之家 6 月 8 日消息,中兴 U15S 随身 WiFi 今日开售,有黑白两种颜色可选, 售价 179 元 。 京东 中兴(ZTE)U15S 随身 wifi 179 元 直达链接 这款产品搭载中兴自研双核芯片 V3E-A53,内置双卡、支持双网任意切换,支持 Wi-Fi 6;采用“G2 曲面手感设计”,正面配备一块 1.44 英寸触摸屏面板,可显示二维码、流量等信息;重约 237g。 这款产品内置 10000mAh 电芯,支持 18W 快充,支持 QC / PD / PPS 等协议,同时支持电源直供电模式。 IT之家附这款产品详细参数如下: 京东 618 无门槛红包 面额至高 26618 元,每天抽 3 次: 点此抽红包 淘宝 618 无门槛红包 面额至高 26888 元,每天抽 1 次: 点此抽红包

LinuxDo 最新话题 · 2026-06-07 17:59:20+08:00 · tech

就两个问题: 在"轻松农场"里用"精炼室"精炼各种颜色的卡牌,有没有日限额啥的(比如一天限制精炼多少次之类的限制)。 在"集换卡片"里用"重铸工坊"重铸各种颜色的卡牌,有没有日限额啥的(比如一天限制重铸多少次之类的) 有没有有经验的佬懂的, 问题的目的:我在玩里面的"集换卡片"游戏,现在每天650抽满抽,想法是先通过自然满抽,抽多少算多少,前期先不通过这两种方式精炼和重铸,但最后留出比如5天时间,来集中精炼和重铸,很怕到那时突然来个日限啥的,翻车了,那就完蛋蛋了 1 个帖子 - 1 位参与者 阅读完整话题

IT之家 · 2026-06-06 12:22:44+08:00 · tech

IT之家 6 月 6 日消息,消息源 @KaroulSahil 昨日(6 月 5 日)在 X 平台分享了一段视频, 展示了白色和深蓝色(接近黑色)版本苹果 iPhone 折叠手机(上市后预估名为 iPhone Ultra)。 IT之家此前报道,针对 iPhone Fold,苹果公司内部正研发两种颜色,其一是经典的银白色,其二是深靛蓝色,比较接近 iPhone 17 Pro 的深蓝色。 供应链分析师郭明錤曾警告称,苹果早期会面临生产良率和产能爬坡问题,导致这款手机的供不应求状况至少持续到 2026 年底。 他还指出,外界提到的 1500 万-2000 万台销量预测,更可能是 2-3 年产品生命周期的累计需求。

cnBeta全文版 · 2026-06-01 20:05:34+08:00 · tech

一张疑似苹果首款折叠屏 iPhone——“iPhone Ultra”的真机模型照片近日在微博流出,被认为首次清晰展示了这款新机的其中一种配色。据悉,苹果预计将在今年晚些时候正式发布这款折叠屏 iPhone,而其配色策略将明显区别于目前常规 iPhone 产品线。 此次图片由爆料人士冰宇宙发布,画面中设备采用白色机身,被认为是已进入早期量产阶段机型的模型版本。尽管具体细节仍有保留,但另一位爆料者 Instant Digital 此前已表示,白色目前是唯一“可以被确认”的量产配色。 多方消息显示,iPhone Ultra 整体仅会提供两种颜色,另一种尚未曝光的配色被认为接近 iPhone 17 Pro 的“深蓝”调 indigo 版本。来自供应链的说法称,与 iPhone 18 Pro 系列相比,折叠屏 iPhone 的颜色选择会更少,不会出现鲜艳、跳跃的高饱和色调。 彭博社记者 Mark Gurman 的报道同样指向类似判断:苹果计划有意“回避有趣的颜色”,回归更传统的深空灰 / 黑色与银色 / 白色路线。这种做法与当年 2017 年首发的 iPhone X 颇为相似,当时该机型也仅提供银色和深空灰两种选择。 业内分析认为,有限的配色与折叠屏 iPhone 较低的产量预期密切相关。分析师郭明錤此前警告称,制造端的技术与良率挑战,可能会让折叠屏 iPhone 的供应紧张情况持续到至少 2026 年底,增加配色只会进一步提高生产复杂度和成本。 在供应量有限、定价被外界普遍认为将超过 2000 美元的背景下,苹果在首发阶段并不急于通过多样化配色来刺激需求。相关报道指出,这一价位的潜在用户更看重形态、做工和功能等差异,而不会将颜色作为核心购买因素。 按照目前爆料节奏,iPhone Ultra 预计将与 iPhone 18 Pro、iPhone 18 Pro Max 一同在今年 9 月发布。随着量产推进,关于其最终配色和设计细节的更多消息,预计还将陆续浮出水面。 查看评论

IT之家 · 2026-05-30 07:04:12+08:00 · tech

IT之家 5 月 30 日消息,消息源 @SonnyDickson 昨日(5 月 29 日)在 X 平台发布推文,分享了一组机模照片, 展示了苹果 iPhone 18 四种配色,涵盖黑色、银色、樱桃色和浅蓝色。 消息源表示,在苹果 iPhone 17 系列主打星宇橙后,苹果 iPhone 18 系列主打颜色就是樱桃色,在 Pantone 色系中,颜色编号为 6076,是一种非常柔和的颜色,色调类似于酒红色。IT之家附上相关图片如下: 延伸阅读 根据此前报道,苹果 iPhone 18 Pro 系列将接替 iPhone 17 Pro 系列的“星宇橙”配色,主打颜色被命名为“深樱桃色”(Dark Cherry)。 除了樱桃色,消息称苹果还在为 iPhone 18 Pro 系列测试浅蓝、深灰及银色版本。其中浅蓝色被描述为比较接近 iPhone 17 的青雾蓝色,是一款低饱和度、带灰调的雅致色调。 彭博社记者马克 · 古尔曼曾透露苹果正在为 iPhone 18 Pro 系列测试全新“深红色”配色,这得益于 iPhone 17 Pro 系列引入的铝合金一体成型机身提供了更灵活的着色空间。如果深红色最终入选,这将是自 2022 年 iPhone 14 之后,苹果再次推出红色系 iPhone,也是红色首次出现在“Pro”级别机型上。 在硬件设计方面,iPhone 18 Pro 系列将基于现有形态精细化打磨,新机将采用更小的灵动岛设计,有望缩小后置摄像头玻璃与模组凸起之间的间隙,降低玻璃与金属边框间的色差,提升机身背面的整体感。 影像方面,消息称 iPhone 18 Pro 系列背部摄像头布局基本不变,但为了容纳 48MP 可变光圈,后摄模组区域可能略微加厚。

IT之家 · 2026-05-20 16:09:35+08:00 · tech

IT之家 5 月 20 日消息,小米汽车副总裁李肖爽今日晒出了 YU7 GT 外观颜色、内饰风格以及轮毂样式。 IT之家注意到, 小米 YU7 GT 拥有 5 款外观 ,包括车厘子红、火山灰、曜石黑、珍珠白、钛金属色;2 种内饰风格,包括豪华运动和豪华舒适; 全系采用 21 英寸轮毂 ,包括低风阻幻刃轮毂、五辐双层锻造轮毂。 根据官方介绍,小米 YU7 GT 的定位其实是一款适合长途旅行的跑车级 SUV。关于该车为何要上纽北赛道,雷军在昨日的答网友问里解释称,纽北不仅仅是赛道,其实也是全球最佳的高性能车试炼场。GT 跑纽北不仅仅是为了赛道成绩,更多是为了验证 GT 在复杂路况和复杂天气情况下的机械素质,提高操控的稳定性。 小米创办人、董事长兼 CEO 雷军今日发布视频,回应了小米 YU7 GT 相关问题。 他表示,这款车为时代精英设计,价格肯定会有点小贵 。

IT之家 · 2026-05-17 11:02:02+08:00 · tech

IT之家 5 月 17 日消息, 小米 YU7 汽车全新配色「火山灰」官图今天公布 (虽然官方微博称是 YU7 新色,但实际预热海报显示为 YU7 GT),灵感来自晨雾下的火山地貌。 据小米汽车透露,小米 YU7 全新颜色「火山灰」实车已陆续进店。同时, 小米 YU7 还新增了「霞光紫」配色 。据介绍,目前 YU7 GT 仅做静态展示, 内饰体验将在 5 月底上市发布后开放 。 小米汽车官方还表示, 更多颜色也在陆续进店 。需要注意的是,由于运输时间差异,准确到店时间需咨询门店。IT之家附 361 家门店分布情况如下(因门店信息过多导致阅读体验不佳,想了解具体门店信息可 点此前往 小米汽车官方文章查看详情): 北京:共 17 家 上海:共 16 家 天津:共 6 家 重庆:共 7 家 广东:共 52 家 四川:共 16 家 浙江:共 49 家 江苏:共 43 家 湖北:共 15 家 河南:共 14 家 山东:共 17 家 湖南:共 9 家 陕西:共 4 家 安徽:共 15 家 福建:共 14 家 河北:共 11 家 辽宁:共 6 家 吉林:共 4 家 黑龙江:共 2 家 云南:共 6 家 江西:共 9 家 内蒙古:共 2 家 山西:共 3 家 新疆:共 3 家 贵州:共 5 家 广西:共 6 家 海南:共 4 家 宁夏:共 2 家 甘肃:共 3 家 青海:共 1 家

v2ex · 2026-05-15 17:06:47+08:00 · tech

标题: 做了一个在线颜色匹配小游戏 Toon Tone ,想听听大家对玩法和 UI 的建议 正文: 最近在做一个很轻量的网页小游戏,名字暂时叫 Toon Tone 。 最开始的想法是做一个“根据卡通角色某个部位颜色进行匹配”的游戏,比如记住角色帽子、衣服、眼睛的颜色,然后用 HSB 滑块复原。但实际开发时发现角色方案比想象中复杂很多: 如果用 SVG 直接画角色,AI / 代码生成出来的角色很容易很丑; 如果用 PNG 分层做角色,需要 base 、mask 、shading 几层严格对齐; 可变色部位如果对不齐,Canvas 叠加效果会很奇怪; 角色素材制作成本远高于游戏逻辑本身。 所以第一版我决定先把玩法简化成 色卡匹配游戏 : 玩家看到一个目标颜色,然后通过: Hue Saturation Brightness 三个滑块去尽量匹配目标色。每局 10 轮,每轮根据颜色接近程度给分。 目前第一版的目标是: 先验证“调色匹配”这个玩法本身是否有趣; 把核心逻辑、计分、移动端交互做好; 如果用户反馈不错,再考虑加每日挑战、分享结果、排行榜,甚至重新尝试角色版本。 技术上暂时没做得很复杂: 纯前端实现; 游戏直接放在首页 hero 区,不单独做 /play 页面; 用 HSB / RGB 转换做实时颜色预览; 用 RGB 距离计算分数; 桌面端左右布局,移动端上下布局; 后续可能再换成更接近人眼感知的色差算法。 我自己感觉这个方向比一开始的角色版更容易落地,也更适合作为 MVP 。 目前想听听大家的建议: 这种颜色匹配小游戏,你会觉得有一点可玩性吗? 目标色一直显示比较好,还是显示几秒后隐藏做成记忆挑战更好? 10 轮一局会不会太长? 计分方式需要更专业吗,比如用 CIEDE2000 ,而不是 RGB 距离? 这种小游戏更适合做成 Daily Challenge ,还是无限练习模式? 如果有做过类似小游戏、颜色工具、Canvas 交互或者前端小游戏的朋友,也欢迎给点建议。 网站地址: https://toontone.work/ 感谢大家~

LinuxDo 最新话题 · 2026-05-13 12:24:31+08:00 · tech

上一篇文章好多人点赞,这里就谢谢大家啦,再发一篇文章讲讲好多人关系的 dns 污染的问题: 一、什么是 DNS? 根据维基百科,域名系统(英语:Domain Name System,缩写:DNS)是互联网的一项服务。它作为将域名和 IP 地址相互映射的一个分布式数据库,能够使人更方便地访问互联网,中文翻译为域名系统,是互联网标准协议的组成部分。 比如,你在地址栏输入 www.baidu.com ,敲一下回车,网页立马就出来了。这看起来理所当然,但你有没有想过,浏览器是咋找到百度服务器的? 要知道,在网络的世界里,计算机之间只能通过 IP 地址来互相识别,比如 202.108.22.5 这样一串枯燥的数字。 计算机是不认识 www.baidu.com 这种字母组合的。 这就出现了一个矛盾:人类擅长记忆有意义的域名,而机器只认数字 IP。为了解决这个矛盾,DNS"域名系统" 应运而生,这就跟我们写信一样,你得写个收信人的地址邮局才能给你发送吧,你给国外写信,你写中文地址邮局不认识,需要这个一个人帮你翻译成英语。这就是 DNS 的作用,所以你的在本地连接里面写 DNS 才可以正常浏览网页,如果不设置 DNS,是无法正常访问网页的。 简单直白地说,DNS 就是互联网的导航员兼翻译官。它的核心职责只有一件事: 把人类能读懂和容易记住的域名,翻译成机器能读懂的 IP 地址。 二、什么是 DNS 劫持? 就像下面这个图片一样(我网络比较干净,于是这种网站我找了好久,好不容易找到一个然后 p 上马赛克 hhh) 平时我们打开网页后,可能一瞬间显示出了网页的原来样子,或者还没加载出来,下一秒就跳转到一个奇怪的网站去了,或者网址栏变成了奇奇怪怪的地址链接一堆英文数字,然后才变成正常网页,明明我输入的是某个官方网站短域名啊?咋会这样呢? 这时候你就要注意了,你可能遭到了 DNS 劫持的攻击,而 DNS 被劫持指的是 DNS 查询的结果被篡改或替换,导致用户访问的网站被重定向到恶意网站或者伪造的网站。 三、什么是运营商级别 DNS 劫持? 一、这种 DNS 劫持,就是运营商在你上网时,拦截你输入网址后的 “地址查询”(DNS 解析),偷偷把正确 IP 换成假 IP,让你跳转到广告页、推广页或拦截页或者反诈中心。 正常流程:你 → 运营商 DNS → 正确 IP → 目标网站 劫持流程:你 → 运营商 DNS → 假 IP(广告 / 拦截页) → 无法直达目标网站 可能会表现这样子: 访问正规站先跳广告,几秒后才跳转到你想看的网站 输错域名不显示 “无法访问”,跳到运营商导航页 部分网站打不开,或跳至赌博 / 不良站点/其他站点 四、怎么检测是否被 DNS 劫持呢?这里给佬友写四种方法: 方法一:使用 nslookup 命令,查询两次,对比多平台解析结果 「nslookup」命令可快速查询 DNS 记录,将域名转为 IP 地址,诊断解析问题。输入「nslookup 域名」即可检测网络状态、排查劫持等异常,支持 Windows、Linux 与 macOS 系统,是网络故障排查与安全检测的实用工具。 使用 nslookup 命令查询常用域名,对比结果与公共 DNS(如谷歌 DNS 地址: 8.8.8.8)是不是一样的。如果解析出的 IP 完全不同,极可能遭遇劫持。 步骤 1:无论你用 Windows 还是 Linux/macOS,先打开CMD命令行界面: ・Windows 用户按快捷键 Win+R 输入 cmd 回车,或者右键点击左下角「开始」按钮,选择「终端」或「终端管理员」。・Mac 用户用 Command + 空格搜索「终端」 步骤 2:使用 nslookup 命令查询两次: 第一次查询:接着输入:nslookup 英文空格,后面写上你要查的网站域名, 比如 以 L 站为例 ,输入:nslookup https://linux.do/ ,按回车键 Enter。 第二次查询:指定谷歌公共 DNS(8.8.8.8)来查询,在第一次查询命令后面加上 8.8.8.8, 再输入:nslookup http://linux.do/ 8.8.8.8 ,按回车键 Enter。 步骤 3:对比两次 nslookup 命令查询结果: 如下图所示,两次查询结果一样: (使用 nslookup 命令检测 DNS 劫持,橙色为 nslookup 命令,绿色为查询结果,查询网站可以选其他解析异常的网站) 解读一下上面这个图片,第一次查询使用默认 DNS,第二次查询使用 Google DNS,查询结果都是返回 4 个 IP 地址,两个 IPv4、两个 IPv6,如果显示 结果完全相同,说明没有 DNS 劫持迹象,但是在实践过程中建议大家可以试试看访问量比较大的其他中文网站 。 一般网站只返回一个 IP 地址,这里返回 4 个,可能是因为网站使用了 CDN 优化,可以忽略, 重点是查看是否一致就可以了 ! 方法二:HTTPS 证书验证 浏览器对无效 SSL 证书会是会发出警告的,如果正常网站突然出现证书错误,那就需要警惕 DNS会不会指向了非官方服务器了? 方法三:用专业工具辅助检测 像「DNS Leak Test」或「GRC DNS Benchmark」等工具可以自动分析 DNS 响应轨迹,识别异常解析节点 方法四:从电脑的 “网络和共享中心” 进入查看 -–打开控制面板然后找到 “网络和共享中心”— -–点击 “网络和共享中心” 下的 “查看网络状态和任务” ,点—查看当前连接的网络—那个按钮— -–点击已经连接的网络,点查看当前网络的状态— -–点击 “属性”,来到 WLAN 属性窗口— -–点击 “Internet 协议版本 4(TCP/IPv4)”,再点击 “属性” 即可来到属性配置界面— -–查看 DNS 服务器地址,如果自己之前没有设置过,默认是自动获取的;如果之前设置过电脑 DNS 地址,但这里显示是手动设置的陌生 DNS 地址,则说明电脑 DNS 被劫持了— 五、怎么还原干净的网络环境? 修改 DNS 服务器地址 :如果说是 Windows 系统,可以右键点击网络图标,选择 “网络和 Internet 设置”,进入 “更改适配器选项”,找到当前连接的网络,右键选择 “属性”,在 “网络” 选项卡下找到 “Internet 协议版本 4(TCP/IPv4)”,点击" 属性",选择 “使用下面的 DNS 服务器地址”,手动输入一些公共 DNS,如 Google 的 8.8.8.8 和 8.8.4.4,或阿里云的 223.5.5.5 和 223.6.6.6。 清除浏览器缓存和 Cookies :打开浏览器设置,找到清除缓存和 Cookies 的选项进行操作。这一步是为了清除可能存在的被劫持的解析记录。 检查路由器设置 :如果使用了路由器,建议登录路由器管理页面,检查 DNS 设置是否被篡改,并重置路由器到出厂设置。 如果这些方法都无法解决,就用安全软件扫描修复或者打电话给运营商解决: 杀毒软件一般都具备 DNS 检测和修复功能,这里个人强烈推荐火绒杀毒,小白友好,安静无广;如果佬友是技术宅或者程序员,可以用卡巴斯基,世界第一的杀毒软件,它也有免费版,网上有很多下载教程这里就不多说了,或者打电话联系你们当地的网络运营商。 (另外关于手机流氓软件科普,可以看看之前一篇文章: 业内人士,向佬友揭露一下流氓 APP 是怎么围剿猎杀用户的 如果你觉得这篇文章好的话,可以给新人一个 LDC 打赏: credit.linux.do LINUX DO Credit Linux Do 社区积分服务平台 (其实也是上一期佬友提醒,才知道有这玩意的,似乎可以换东西?) 上网以来,遇到过劫持吗 上网以来,遇到过劫持吗 有,当时还以为是电脑坏了 没有,今天才知道,涨知识了 点击以查看投票。 二更:根据评论区佬友和GPT指导下补充两张图片: 9 个帖子 - 7 位参与者 阅读完整话题