// ==UserScript== // @name 三连虚假点击 // @namespace http://tampermonkey.net/ // @version 1.0 // @description 点击切换三连按钮蓝色常亮,按钮流畅拖动、贴边自动隐藏,位置记忆 // @author shelitauhafnri // @match *://*.bilibili.com/* // @exclude *://*.bilibili.com/account/* // @exclude *://passport.bilibili.com/* // @grant GM_addStyle // ==/UserScript== (function() { 'use strict'; const blueStyle = ` body.force-blue-active .video-like svg, body.force-blue-active .video-like span, body.force-blue-active .bpx-player-video-like svg, body.force-blue-active .bpx-player-video-like span, body.force-blue-active .video-coin svg, body.force-blue-active .video-coin span, body.force-blue-active .bpx-player-video-coin svg, body.force-blue-active .bpx-player-video-coin span, body.force-blue-active .video-fav svg, body.force-blue-active .video-fav span, body.force-blue-active .video-collect svg, body.force-blue-active .video-collect span, body.force-blue-active .bpx-player-video-collect svg, body.force-blue-active .bpx-player-video-collect span { color: #00a1d6 !important; fill: #00a1d6 !important; } body.force-blue-active .video-like:hover svg, body.force-blue-active .video-coin:hover svg, body.force-blue-active .video-fav:hover svg { color: #00a1d6 !important; fill: #00a1d6 !important; } body.force-blue-active .video-like:hover, body.force-blue-active .video-coin:hover, body.force-blue-active .video-fav:hover { opacity: 0.85 !important; } `; if (typeof GM_addStyle !== 'undefined') { GM_addStyle(blueStyle); } else { const style = document.createElement('style'); style.textContent = blueStyle; document.head.appendChild(style); } const button = document.createElement('div'); button.id = 'bili-blue-toggle'; button.textContent = '🔵'; button.title = '点击切换蓝色常亮(可拖动)'; Object.assign(button.style, { position: 'fixed', width: '48px', height: '48px', borderRadius: '50%', backgroundColor: '#00a1d6', color: 'white', fontSize: '24px', textAlign: 'center', lineHeight: '48px', cursor: 'grab', zIndex: '9999', boxShadow: '0 2px 10px rgba(0,0,0,0.2)', transition: 'none', // 拖拽时禁用过渡,提升跟手性 userSelect: 'none', willChange: 'transform', // 提示浏览器优化 // 初始位置(右下角) right: '20px', bottom: '80px' }); document.body.appendChild(button); let isBlue = localStorage.getItem('biliBlueEnabled') === 'true'; let isDragging = false; let dragStartX = 0, dragStartY = 0; let startLeft = 0, startTop = 0; let edgeSnap = null; // 'left', 'right', 'top', 'bottom' let edgeHover = false; const EDGE_THRESHOLD = 30; // 触发贴边的距离 const EDGE_OFFSET = -36; // 贴边时偏移量(露出12px) if (isBlue) { document.body.classList.add('force-blue-active'); button.textContent = '🔵✓'; button.style.backgroundColor = '#0080b0'; } // 获取按钮当前相对于视口的 left/top(实时读取) function getButtonPos() { const rect = button.getBoundingClientRect(); return { left: rect.left, top: rect.top }; } // 设置按钮位置(使用 transform 代替 left/top,避免重排) function setButtonPosition(left, top) { // 限制边界 const maxLeft = window.innerWidth - button.offsetWidth; const maxTop = window.innerHeight - button.offsetHeight; const clampedLeft = Math.min(Math.max(left, 0), maxLeft); const clampedTop = Math.min(Math.max(top, 0), maxTop); button.style.transform = `translate(${clampedLeft}px, ${clampedTop}px)`; button.style.right = 'auto'; button.style.bottom = 'auto'; button.style.left = '0'; button.style.top = '0'; // 存储实际坐标(用于后续贴边计算) button.setAttribute('data-left', clampedLeft); button.setAttribute('data-top', clampedTop); } // 获取存储的实际坐标(未贴边时) function getStoredPos() { const left = parseFloat(button.getAttribute('data-left')); const top = parseFloat(button.getAttribute('data-top')); return { left: isNaN(left) ? 0 : left, top: isNaN(top) ? 0 : top }; } // 保存位置到 localStorage(保存实际坐标) function saveNormalPosition() { if (edgeSnap) cancelEdgeSnap(false); // 取消贴边但不重新检查 const pos = getStoredPos(); localStorage.setItem('biliBlueButtonLeft', pos.left); localStorage.setItem('biliBlueButtonTop', pos.top); if (edgeSnap) applyEdgeSnap(edgeSnap); } // 恢复位置 function restorePosition() { const savedLeft = localStorage.getItem('biliBlueButtonLeft'); const savedTop = localStorage.getItem('biliBlueButtonTop'); if (savedLeft !== null && savedTop !== null) { setButtonPosition(parseFloat(savedLeft), parseFloat(savedTop)); } else { // 默认右下角(以 transform 形式设置) const defaultLeft = window.innerWidth - button.offsetWidth - 20; const defaultTop = window.innerHeight - button.offsetHeight - 80; setButtonPosition(defaultLeft, defaultTop); } // 恢复后检查贴边 setTimeout(() => checkEdgeSnap(), 50); } // 获取应该贴边的方向(基于实际坐标) function getEdgeDirection() { const { left, top } = getStoredPos(); const right = window.innerWidth - left - button.offsetWidth; const bottom = window.innerHeight - top - button.offsetHeight; if (left <= EDGE_THRESHOLD) return 'left'; if (right <= EDGE_THRESHOLD) return 'right'; if (top <= EDGE_THRESHOLD) return 'top'; if (bottom <= EDGE_THRESHOLD) return 'bottom'; return null; } // 应用贴边(偏移 transform) function applyEdgeSnap(direction, skipCancel = false) { if (isDragging) return; if (!skipCancel) cancelEdgeSnap(true); // 先取消但保留坐标 const { left, top } = getStoredPos(); let translateX = left, translateY = top; switch (direction) { case 'left': translateX = EDGE_OFFSET; break; case 'right': translateX = window.innerWidth - button.offsetWidth + EDGE_OFFSET; break; case 'top': translateY = EDGE_OFFSET; break; case 'bottom': translateY = window.innerHeight - button.offsetHeight + EDGE_OFFSET; break; } button.style.transform = `translate(${translateX}px, ${translateY}px)`; edgeSnap = direction; } // 取消贴边,恢复实际坐标 function cancelEdgeSnap(skipRecheck = false) { if (!edgeSnap) return; const { left, top } = getStoredPos(); button.style.transform = `translate(${left}px, ${top}px)`; edgeSnap = null; if (!skipRecheck) checkEdgeSnap(); } // 检查并应用贴边 function checkEdgeSnap() { if (isDragging) return; const direction = getEdgeDirection(); if (direction) { if (edgeSnap !== direction) { applyEdgeSnap(direction); } } else { if (edgeSnap) cancelEdgeSnap(true); } } function onMouseDown(e) { e.preventDefault(); if (edgeSnap) cancelEdgeSnap(false); // 取消贴边 isDragging = true; button.style.cursor = 'grabbing'; const startX = e.clientX; const startY = e.clientY; const { left, top } = getStoredPos(); startLeft = left; startTop = top; let rafId = null; let lastLeft = startLeft, lastTop = startTop; function onMouseMove(moveEvent) { if (!isDragging) return; const dx = moveEvent.clientX - startX; const dy = moveEvent.clientY - startY; let newLeft = startLeft + dx; let newTop = startTop + dy; // 边界限制 newLeft = Math.min(Math.max(newLeft, 0), window.innerWidth - button.offsetWidth); newTop = Math.min(Math.max(newTop, 0), window.innerHeight - button.offsetHeight); if (lastLeft === newLeft && lastTop === newTop) return; lastLeft = newLeft; lastTop = newTop; if (rafId) cancelAnimationFrame(rafId); rafId = requestAnimationFrame(() => { setButtonPosition(newLeft, newTop); }); } function onMouseUp() { if (rafId) cancelAnimationFrame(rafId); document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); button.style.cursor = 'grab'; isDragging = false; // 拖拽结束后检查贴边并保存位置 checkEdgeSnap(); saveNormalPosition(); } document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); } function onTouchStart(e) { e.preventDefault(); if (edgeSnap) cancelEdgeSnap(false); isDragging = true; const touch = e.touches[0]; const startX = touch.clientX; const startY = touch.clientY; const { left, top } = getStoredPos(); startLeft = left; startTop = top; let rafId = null; let lastLeft = startLeft, lastTop = startTop; function onTouchMove(moveEvent) { if (!isDragging) return; const moveTouch = moveEvent.touches[0]; const dx = moveTouch.clientX - startX; const dy = moveTouch.clientY - startY; let newLeft = startLeft + dx; let newTop = startTop + dy; newLeft = Math.min(Math.max(newLeft, 0), window.innerWidth - button.offsetWidth); newTop = Math.min(Math.max(newTop, 0), window.innerHeight - button.offsetHeight); if (lastLeft === newLeft && lastTop === newTop) return; lastLeft = newLeft; lastTop = newTop; if (rafId) cancelAnimationFrame(rafId); rafId = requestAnimationFrame(() => { setButtonPosition(newLeft, newTop); }); } function onTouchEnd() { if (rafId) cancelAnimationFrame(rafId); document.removeEventListener('touchmove', onTouchMove); document.removeEventListener('touchend', onTouchEnd); isDragging = false; checkEdgeSnap(); saveNormalPosition(); } document.addEventListener('touchmove', onTouchMove); document.addEventListener('touchend', onTouchEnd); } // 切换蓝色状态 function toggleBlue() { isBlue = !isBlue; if (isBlue) { document.body.classList.add('force-blue-active'); button.textContent = '🔵✓'; button.style.backgroundColor = '#0080b0'; } else { document.body.classList.remove('force-blue-active'); button.textContent = '🔵'; button.style.backgroundColor = '#00a1d6'; } localStorage.setItem('biliBlueEnabled', isBlue); } // 悬浮展开 function onMouseEnter() { if (edgeSnap && !isDragging) { edgeHover = true; cancelEdgeSnap(false); } } function onMouseLeave() { if (edgeSnap && edgeHover && !isDragging) { edgeHover = false; applyEdgeSnap(edgeSnap); } } button.addEventListener('mousedown', onMouseDown); button.addEventListener('touchstart', onTouchStart); button.addEventListener('click', (e) => { if (!isDragging) toggleBlue(); }); button.addEventListener('mouseenter', onMouseEnter); button.addEventListener('mouseleave', onMouseLeave); // 窗口大小改变时重新计算边界和贴边 window.addEventListener('resize', () => { if (!isDragging) { // 先确保位置在边界内 const { left, top } = getStoredPos(); let newLeft = Math.min(Math.max(left, 0), window.innerWidth - button.offsetWidth); let newTop = Math.min(Math.max(top, 0), window.innerHeight - button.offsetHeight); if (newLeft !== left || newTop !== top) { setButtonPosition(newLeft, newTop); saveNormalPosition(); } checkEdgeSnap(); } }); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', restorePosition); } else { restorePosition(); } })();