点击特效plus(所有网站适用!)
update 2.6:神秘大佬 twyeottk 在 原版 的基础上做了优化,主要是控制动画对象数量、在页面隐藏时暂停动画,长时间运行会稳定很多。
update 2.7:对所有方面进行了非常彻底的改动,增加了控制面板,增加了碰撞模式(按下键盘 ESC 下面的 ~ 开启,再按一下关闭)。
这里 是以往的所有版本。
前提条件
已经在浏览器上下载了 Tampermonkey 扩展(进不去就自己搜)。
安装方法
安装方法有两种:
-
从 Greasyfork 下载。下载不了的话,可以试着使用和进入洛谷国际站一样的方法(目前不是最新版)。
-
从 GitHub 下载(目前不是最新版)。
-
打开扩展,点击
添加新脚本,把下面的代码复制粘贴,最后按Ctrl+s保存,最后退出就行了。
提示
设置与上图保持一样。
可以长按(现在粒子可以跟随鼠标)。
新增:
点击 扩展,再点 篡改猴,这样能看到你安装了的所有脚本,也能在这里打开设置。
如果你完成上述步骤后,使用不了脚本,请私信我。
挺好看的对吧。
Code
// ==UserScript==
// @name Amazing click effect!
// @namespace http://tampermonkey.net/
// @version 1.14.0
// @description Enjoy clicking time!
// @author Super_Diu and twyeottk(1000ttank)
// @contributors Pigeonw Development
// @match *://*/*
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==
(function() {
'use strict';
const defCfg = {
BASE_SPD: 3,
BASE_SPD_MAX: 5,
BASE_CNT: 12,
MAX_SPD_LIMIT: 15,
MIN_SPD_LIMIT: 8,
MAX_CNT: 30,
LONG_PRESS: 800,
SCALE_DUR: 3000,
MAX_SIZE: 20,
MIN_ALPHA_SPD: 8,
ALPHA_SPD_BASE: 25,
ALPHA_DECAY_SCALE: 1.1,
TRI_SPD_DECAY: 0.998,
DOT_R_MIN: 1,
DOT_R_MAX: 3,
DOT_INIT_SPD: 1.2,
DOT_ACCEL: 0.2,
DOT_ALPHA_SPD: 0.02,
DOT_FADE_BASE: 0.03,
DOT_SPAWN_R_MIN: 120,
DOT_SPAWN_R_MAX: 350,
DOT_ANGLE_JITTER: 0.3,
SPD_ALPHA_FACTOR: 0.0001,
BASE_COLL_PART_CNT: 8,
MAX_COLL_PART_CNT: 20,
BASE_COLL_SPD: 2,
MAX_COLL_SPD: 10,
BASE_COLL_SIZE: 2,
MAX_COLL_SIZE: 5,
MAX_TRACK: 6,
DOT_TRACK: 3,
SPD_FADE_FACTOR: 0.002,
CHARGE_ALPHA_SCALE: 0.3,
NORMAL_ALPHA_SCALE: 1.0
};
const cfg = {};
Object.keys(defCfg).forEach(k => {
cfg[k] = GM_getValue(k, defCfg[k]);
});
const decimalParams = new Set(['NORMAL_ALPHA_SCALE', 'CHARGE_ALPHA_SCALE', 'MAX_COLL_SPD']);
function createCfgMenu() {
GM_registerMenuCommand('⚙️ 点击特效配置', () => {
const container = document.createElement('div');
container.style.cssText = `
position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);
background:#ffffff;border-radius:12px;box-shadow:0 8px 32px rgba(0,0,0,0.15);
z-index:9999999;width:90%;max-width:900px;height:80vh;max-height:700px;
display:flex;flex-direction:column;overflow:hidden;
font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;
`;
const header = document.createElement('div');
header.style.cssText = `
display:flex;align-items:center;justify-content:space-between;
padding:16px 20px;background:#f8fafc;border-bottom:1px solid #e2e8f0;
`;
container.appendChild(header);
const menuBar = document.createElement('div');
menuBar.style.cssText = `display:flex;gap:8px;`;
header.appendChild(menuBar);
const menuItems = [
{ id: 'cfg-panel', text: '配置面板' },
{ id: 'guide-panel', text: '使用说明' }
];
menuItems.forEach(item => {
const btn = document.createElement('button');
btn.textContent = item.text;
btn.style.cssText = `
padding:8px 16px;border:none;border-radius:6px;
background:${item.id === 'cfg-panel' ? '#3b82f6' : 'transparent'};
color:${item.id === 'cfg-panel' ? '#ffffff' : '#334155'};
cursor:pointer;transition:all 0.2s ease;
font-size:14px;font-weight:500;
`;
btn.dataset.target = item.id;
btn.addEventListener('click', (e) => {
menuBar.querySelectorAll('button').forEach(b => {
b.style.background = b.dataset.target === e.target.dataset.target ? '#3b82f6' : 'transparent';
b.style.color = b.dataset.target === e.target.dataset.target ? '#ffffff' : '#334155';
});
document.querySelectorAll('.content-panel').forEach(panel => {
panel.style.display = panel.id === e.target.dataset.target ? 'block' : 'none';
});
});
menuBar.appendChild(btn);
});
const closeBtn = document.createElement('button');
closeBtn.innerHTML = '×';
closeBtn.style.cssText = `
width:32px;height:32px;border:none;border-radius:50%;
background:#fef2f2;color:#ef4444;cursor:pointer;
font-size:20px;display:flex;align-items:center;justify-content:center;
transition:all 0.2s ease;position:relative;z-index:1;outline:none;
`;
closeBtn.addEventListener('mouseenter', () => {
closeBtn.style.background = '#ef4444';
closeBtn.style.color = '#ffffff';
});
closeBtn.addEventListener('mouseleave', () => {
closeBtn.style.background = '#fef2f2';
closeBtn.style.color = '#ef4444';
});
closeBtn.addEventListener('click', () => {
container.remove();
mask.remove();
});
header.appendChild(closeBtn);
const contentWrap = document.createElement('div');
contentWrap.style.cssText = `
flex:1;overflow-y:auto;padding:24px;max-height:100%;box-sizing:border-box;background:#fafafa;
`;
container.appendChild(contentWrap);
const cfgPanel = document.createElement('div');
cfgPanel.id = 'cfg-panel';
cfgPanel.className = 'content-panel';
cfgPanel.style.display = 'block';
cfgPanel.innerHTML = `
<h2 style="margin:0 0 24px 0;color:#1e293b;font-size:20px;font-weight:600;">点击特效配置</h2>
<div style="margin-bottom:24px;padding:20px;background:#ffffff;border-radius:8px;box-shadow:0 1px 3px rgba(0,0,0,0.05);">
<h3 style="margin:0 0 16px 0;color:#334155;font-size:16px;font-weight:600;">📌 普通点击专属参数</h3>
<div class="slider-group" data-param="BASE_SPD" style="margin:12px 0;display:flex;flex-direction:column;gap:8px;">
<label style="font-size:14px;color:#475569;font-weight:500;">普通点击基础速度</label>
<div class="slider-container" style="position:relative;width:100%;height:8px;border-radius:4px;background:#e2e8f0;">
<div class="slider-progress" style="position:absolute;top:0;left:0;height:100%;border-radius:4px;background:#3b82f6;width:${((cfg.BASE_SPD-1)/(10-1))*100}%;"></div>
<div class="slider-thumb" style="position:absolute;top:50%;transform:translate(-50%,-50%);width:20px;height:20px;border-radius:50%;background:#3b82f6;cursor:pointer;box-shadow:0 2px 6px rgba(59, 130, 246, 0.3);left:${((cfg.BASE_SPD-1)/(10-1))*100}%;"></div>
<input type="range" class="slider-input" min="1" max="10" step="1" value="${cfg.BASE_SPD}" style="opacity:0;position:absolute;top:0;left:0;width:100%;height:100%;margin:0;padding:0;cursor:pointer;">
</div>
<span class="slider-value" style="font-size:12px;color:#64748b;">当前值:${cfg.BASE_SPD}</span>
</div>
<div class="slider-group" data-param="BASE_SPD_MAX" style="margin:12px 0;display:flex;flex-direction:column;gap:8px;">
<label style="font-size:14px;color:#475569;font-weight:500;">普通点击最快速度</label>
<div class="slider-container" style="position:relative;width:100%;height:8px;border-radius:4px;background:#e2e8f0;">
<div class="slider-progress" style="position:absolute;top:0;left:0;height:100%;border-radius:4px;background:#3b82f6;width:${((cfg.BASE_SPD_MAX-3)/(15-3))*100}%;"></div>
<div class="slider-thumb" style="position:absolute;top:50%;transform:translate(-50%,-50%);width:20px;height:20px;border-radius:50%;background:#3b82f6;cursor:pointer;box-shadow:0 2px 6px rgba(59, 130, 246, 0.3);left:${((cfg.BASE_SPD_MAX-3)/(15-3))*100}%;"></div>
<input type="range" class="slider-input" min="3" max="15" step="1" value="${cfg.BASE_SPD_MAX}" style="opacity:0;position:absolute;top:0;left:0;width:100%;height:100%;margin:0;padding:0;cursor:pointer;">
</div>
<span class="slider-value" style="font-size:12px;color:#64748b;">当前值:${cfg.BASE_SPD_MAX}</span>
</div>
<div class="slider-group" data-param="BASE_CNT" style="margin:12px 0;display:flex;flex-direction:column;gap:8px;">
<label style="font-size:14px;color:#475569;font-weight:500;">普通点击生成数量</label>
<div class="slider-container" style="position:relative;width:100%;height:8px;border-radius:4px;background:#e2e8f0;">
<div class="slider-progress" style="position:absolute;top:0;left:0;height:100%;border-radius:4px;background:#3b82f6;width:${((cfg.BASE_CNT-5)/(20-5))*100}%;"></div>
<div class="slider-thumb" style="position:absolute;top:50%;transform:translate(-50%,-50%);width:20px;height:20px;border-radius:50%;background:#3b82f6;cursor:pointer;box-shadow:0 2px 6px rgba(59, 130, 246, 0.3);left:${((cfg.BASE_CNT-5)/(20-5))*100}%;"></div>
<input type="range" class="slider-input" min="5" max="20" step="1" value="${cfg.BASE_CNT}" style="opacity:0;position:absolute;top:0;left:0;width:100%;height:100%;margin:0;padding:0;cursor:pointer;">
</div>
<span class="slider-value" style="font-size:12px;color:#64748b;">当前值:${cfg.BASE_CNT}</span>
</div>
<div class="slider-group" data-param="NORMAL_ALPHA_SCALE" style="margin:12px 0;display:flex;flex-direction:column;gap:8px;">
<label style="font-size:14px;color:#475569;font-weight:500;">普通三角形消失速度倍率</label>
<div class="slider-container" style="position:relative;width:100%;height:8px;border-radius:4px;background:#e2e8f0;">
<div class="slider-progress" style="position:absolute;top:0;left:0;height:100%;border-radius:4px;background:#3b82f6;width:${((cfg.NORMAL_ALPHA_SCALE-0.1)/(2-0.1))*100}%;"></div>
<div class="slider-thumb" style="position:absolute;top:50%;transform:translate(-50%,-50%);width:20px;height:20px;border-radius:50%;background:#3b82f6;cursor:pointer;box-shadow:0 2px 6px rgba(59, 130, 246, 0.3);left:${((cfg.NORMAL_ALPHA_SCALE-0.1)/(2-0.1))*100}%;"></div>
<input type="range" class="slider-input" min="0.1" max="2" step="0.1" value="${cfg.NORMAL_ALPHA_SCALE}" style="opacity:0;position:absolute;top:0;left:0;width:100%;height:100%;margin:0;padding:0;cursor:pointer;">
</div>
<span class="slider-value" style="font-size:12px;color:#64748b;">当前值:${cfg.NORMAL_ALPHA_SCALE.toFixed(1)}</span>
</div>
</div>
<div style="margin-bottom:24px;padding:20px;background:#ffffff;border-radius:8px;box-shadow:0 1px 3px rgba(0,0,0,0.05);">
<h3 style="margin:0 0 16px 0;color:#334155;font-size:16px;font-weight:600;">🚀 蓄力专属参数</h3>
<div class="slider-group" data-param="MIN_SPD_LIMIT" style="margin:12px 0;display:flex;flex-direction:column;gap:8px;">
<label style="font-size:14px;color:#475569;font-weight:500;">蓄力最慢速度</label>
<div class="slider-container" style="position:relative;width:100%;height:8px;border-radius:4px;background:#e2e8f0;">
<div class="slider-progress" style="position:absolute;top:0;left:0;height:100%;border-radius:4px;background:#3b82f6;width:${((cfg.MIN_SPD_LIMIT-5)/(15-5))*100}%;"></div>
<div class="slider-thumb" style="position:absolute;top:50%;transform:translate(-50%,-50%);width:20px;height:20px;border-radius:50%;background:#3b82f6;cursor:pointer;box-shadow:0 2px 6px rgba(59, 130, 246, 0.3);left:${((cfg.MIN_SPD_LIMIT-5)/(15-5))*100}%;"></div>
<input type="range" class="slider-input" min="5" max="15" step="1" value="${cfg.MIN_SPD_LIMIT}" style="opacity:0;position:absolute;top:0;left:0;width:100%;height:100%;margin:0;padding:0;cursor:pointer;">
</div>
<span class="slider-value" style="font-size:12px;color:#64748b;">当前值:${cfg.MIN_SPD_LIMIT}</span>
</div>
<div class="slider-group" data-param="MAX_SPD_LIMIT" style="margin:12px 0;display:flex;flex-direction:column;gap:8px;">
<label style="font-size:14px;color:#475569;font-weight:500;">蓄力最大速度</label>
<div class="slider-container" style="position:relative;width:100%;height:8px;border-radius:4px;background:#e2e8f0;">
<div class="slider-progress" style="position:absolute;top:0;left:0;height:100%;border-radius:4px;background:#3b82f6;width:${((cfg.MAX_SPD_LIMIT-10)/(30-10))*100}%;"></div>
<div class="slider-thumb" style="position:absolute;top:50%;transform:translate(-50%,-50%);width:20px;height:20px;border-radius:50%;background:#3b82f6;cursor:pointer;box-shadow:0 2px 6px rgba(59, 130, 246, 0.3);left:${((cfg.MAX_SPD_LIMIT-10)/(30-10))*100}%;"></div>
<input type="range" class="slider-input" min="10" max="30" step="1" value="${cfg.MAX_SPD_LIMIT}" style="opacity:0;position:absolute;top:0;left:0;width:100%;height:100%;margin:0;padding:0;cursor:pointer;">
</div>
<span class="slider-value" style="font-size:12px;color:#64748b;">当前值:${cfg.MAX_SPD_LIMIT}</span>
</div>
<div class="slider-group" data-param="MAX_CNT" style="margin:12px 0;display:flex;flex-direction:column;gap:8px;">
<label style="font-size:14px;color:#475569;font-weight:500;">蓄力最大生成数量</label>
<div class="slider-container" style="position:relative;width:100%;height:8px;border-radius:4px;background:#e2e8f0;">
<div class="slider-progress" style="position:absolute;top:0;left:0;height:100%;border-radius:4px;background:#3b82f6;width:${((cfg.MAX_CNT-20)/(50-20))*100}%;"></div>
<div class="slider-thumb" style="position:absolute;top:50%;transform:translate(-50%,-50%);width:20px;height:20px;border-radius:50%;background:#3b82f6;cursor:pointer;box-shadow:0 2px 6px rgba(59, 130, 246, 0.3);left:${((cfg.MAX_CNT-20)/(50-20))*100}%;"></div>
<input type="range" class="slider-input" min="20" max="50" step="1" value="${cfg.MAX_CNT}" style="opacity:0;position:absolute;top:0;left:0;width:100%;height:100%;margin:0;padding:0;cursor:pointer;">
</div>
<span class="slider-value" style="font-size:12px;color:#64748b;">当前值:${cfg.MAX_CNT}</span>
</div>
<div class="slider-group" data-param="LONG_PRESS" style="margin:12px 0;display:flex;flex-direction:column;gap:8px;">
<label style="font-size:14px;color:#475569;font-weight:500;">蓄力触发时间(ms)</label>
<div class="slider-container" style="position:relative;width:100%;height:8px;border-radius:4px;background:#e2e8f0;">
<div class="slider-progress" style="position:absolute;top:0;left:0;height:100%;border-radius:4px;background:#3b82f6;width:${((cfg.LONG_PRESS-500)/(2000-500))*100}%;"></div>
<div class="slider-thumb" style="position:absolute;top:50%;transform:translate(-50%,-50%);width:20px;height:20px;border-radius:50%;background:#3b82f6;cursor:pointer;box-shadow:0 2px 6px rgba(59, 130, 246, 0.3);left:${((cfg.LONG_PRESS-500)/(2000-500))*100}%;"></div>
<input type="range" class="slider-input" min="500" max="2000" step="100" value="${cfg.LONG_PRESS}" style="opacity:0;position:absolute;top:0;left:0;width:100%;height:100%;margin:0;padding:0;cursor:pointer;">
</div>
<span class="slider-value" style="font-size:12px;color:#64748b;">当前值:${cfg.LONG_PRESS}</span>
</div>
<div class="slider-group" data-param="CHARGE_ALPHA_SCALE" style="margin:12px 0;display:flex;flex-direction:column;gap:8px;">
<label style="font-size:14px;color:#475569;font-weight:500;">蓄力消失速度倍率</label>
<div class="slider-container" style="position:relative;width:100%;height:8px;border-radius:4px;background:#e2e8f0;">
<div class="slider-progress" style="position:absolute;top:0;left:0;height:100%;border-radius:4px;background:#3b82f6;width:${((cfg.CHARGE_ALPHA_SCALE-0.1)/(1-0.1))*100}%;"></div>
<div class="slider-thumb" style="position:absolute;top:50%;transform:translate(-50%,-50%);width:20px;height:20px;border-radius:50%;background:#3b82f6;cursor:pointer;box-shadow:0 2px 6px rgba(59, 130, 246, 0.3);left:${((cfg.CHARGE_ALPHA_SCALE-0.1)/(1-0.1))*100}%;"></div>
<input type="range" class="slider-input" min="0.1" max="1" step="0.1" value="${cfg.CHARGE_ALPHA_SCALE}" style="opacity:0;position:absolute;top:0;left:0;width:100%;height:100%;margin:0;padding:0;cursor:pointer;">
</div>
<span class="slider-value" style="font-size:12px;color:#64748b;">当前值:${cfg.CHARGE_ALPHA_SCALE.toFixed(1)}</span>
</div>
</div>
<div style="margin-bottom:24px;padding:20px;background:#ffffff;border-radius:8px;box-shadow:0 1px 3px rgba(0,0,0,0.05);">
<h3 style="margin:0 0 16px 0;color:#334155;font-size:16px;font-weight:600;">🔧 核心通用参数</h3>
<div class="slider-group" data-param="MAX_SIZE" style="margin:12px 0;display:flex;flex-direction:column;gap:8px;">
<label style="font-size:14px;color:#475569;font-weight:500;">三角形最大尺寸</label>
<div class="slider-container" style="position:relative;width:100%;height:8px;border-radius:4px;background:#e2e8f0;">
<div class="slider-progress" style="position:absolute;top:0;left:0;height:100%;border-radius:4px;background:#3b82f6;width:${((cfg.MAX_SIZE-10)/(30-10))*100}%;"></div>
<div class="slider-thumb" style="position:absolute;top:50%;transform:translate(-50%,-50%);width:20px;height:20px;border-radius:50%;background:#3b82f6;cursor:pointer;box-shadow:0 2px 6px rgba(59, 130, 246, 0.3);left:${((cfg.MAX_SIZE-10)/(30-10))*100}%;"></div>
<input type="range" class="slider-input" min="10" max="30" step="1" value="${cfg.MAX_SIZE}" style="opacity:0;position:absolute;top:0;left:0;width:100%;height:100%;margin:0;padding:0;cursor:pointer;">
</div>
<span class="slider-value" style="font-size:12px;color:#64748b;">当前值:${cfg.MAX_SIZE}</span>
</div>
<div class="slider-group" data-param="MAX_COLL_SPD" style="margin:12px 0;display:flex;flex-direction:column;gap:8px;">
<label style="font-size:14px;color:#475569;font-weight:500;">碰撞粒子最大速度</label>
<div class="slider-container" style="position:relative;width:100%;height:8px;border-radius:4px;background:#e2e8f0;">
<div class="slider-progress" style="position:absolute;top:0;left:0;height:100%;border-radius:4px;background:#3b82f6;width:${((cfg.MAX_COLL_SPD-5)/(20-5))*100}%;"></div>
<div class="slider-thumb" style="position:absolute;top:50%;transform:translate(-50%,-50%);width:20px;height:20px;border-radius:50%;background:#3b82f6;cursor:pointer;box-shadow:0 2px 6px rgba(59, 130, 246, 0.3);left:${((cfg.MAX_COLL_SPD-5)/(20-5))*100}%;"></div>
<input type="range" class="slider-input" min="5" max="20" step="0.5" value="${cfg.MAX_COLL_SPD}" style="opacity:0;position:absolute;top:0;left:0;width:100%;height:100%;margin:0;padding:0;cursor:pointer;">
</div>
<span class="slider-value" style="font-size:12px;color:#64748b;">当前值:${cfg.MAX_COLL_SPD.toFixed(1)}</span>
</div>
<div class="slider-group" data-param="MAX_TRACK" style="margin:12px 0;display:flex;flex-direction:column;gap:8px;">
<label style="font-size:14px;color:#475569;font-weight:500;">三角形尾迹长度</label>
<div class="slider-container" style="position:relative;width:100%;height:8px;border-radius:4px;background:#e2e8f0;">
<div class="slider-progress" style="position:absolute;top:0;left:0;height:100%;border-radius:4px;background:#3b82f6;width:${((cfg.MAX_TRACK-2)/(15-2))*100}%;"></div>
<div class="slider-thumb" style="position:absolute;top:50%;transform:translate(-50%,-50%);width:20px;height:20px;border-radius:50%;background:#3b82f6;cursor:pointer;box-shadow:0 2px 6px rgba(59, 130, 246, 0.3);left:${((cfg.MAX_TRACK-2)/(15-2))*100}%;"></div>
<input type="range" class="slider-input" min="2" max="15" step="1" value="${cfg.MAX_TRACK}" style="opacity:0;position:absolute;top:0;left:0;width:100%;height:100%;margin:0;padding:0;cursor:pointer;">
</div>
<span class="slider-value" style="font-size:12px;color:#64748b;">当前值:${cfg.MAX_TRACK}</span>
</div>
<div class="slider-group" data-param="DOT_TRACK" style="margin:12px 0;display:flex;flex-direction:column;gap:8px;">
<label style="font-size:14px;color:#475569;font-weight:500;">圆点尾迹长度</label>
<div class="slider-container" style="position:relative;width:100%;height:8px;border-radius:4px;background:#e2e8f0;">
<div class="slider-progress" style="position:absolute;top:0;left:0;height:100%;border-radius:4px;background:#3b82f6;width:${((cfg.DOT_TRACK-1)/(8-1))*100}%;"></div>
<div class="slider-thumb" style="position:absolute;top:50%;transform:translate(-50%,-50%);width:20px;height:20px;border-radius:50%;background:#3b82f6;cursor:pointer;box-shadow:0 2px 6px rgba(59, 130, 246, 0.3);left:${((cfg.DOT_TRACK-1)/(8-1))*100}%;"></div>
<input type="range" class="slider-input" min="1" max="8" step="1" value="${cfg.DOT_TRACK}" style="opacity:0;position:absolute;top:0;left:0;width:100%;height:100%;margin:0;padding:0;cursor:pointer;">
</div>
<span class="slider-value" style="font-size:12px;color:#64748b;">当前值:${cfg.DOT_TRACK}</span>
</div>
</div>
<div style="display:flex;gap:12px;margin-top:24px;">
<button id="saveBtn" style="padding:10px 20px;background:#10b981;color:white;border:none;border-radius:6px;cursor:pointer;font-weight:500;transition:all 0.2s;">
保存配置
</button>
<button id="resetBtn" style="padding:10px 20px;background:#f59e0b;color:white;border:none;border-radius:6px;cursor:pointer;font-weight:500;transition:all 0.2s;">
重置默认值
</button>
</div>
`;
contentWrap.appendChild(cfgPanel);
const guidePanel = document.createElement('div');
guidePanel.id = 'guide-panel';
guidePanel.className = 'content-panel';
guidePanel.style.display = 'none';
guidePanel.innerHTML = `
<h2 style="margin:0 0 24px 0;color:#1e293b;font-size:20px;font-weight:600;">使用说明</h2>
<div style="font-size:14px;line-height:1.8;color:#475569;background:#ffffff;padding:20px;border-radius:8px;box-shadow:0 1px 3px rgba(0,0,0,0.05);">
<div style="margin-bottom:20px;">
<h4 style="margin:0 0 12px 0;color:#334155;font-size:16px;font-weight:600;">1. 基础操作</h4>
<ul style="margin:0;padding-left:20px;">
<li>点击:释放蓝色三角</li>
<li>长按:蓄力越久,威力越大</li>
<li>开启碰撞模式:按下键盘左上角 "~"(ESC下面)开启,所有三角形都会爆炸!</li>
</ul>
</div>
<div style="margin-bottom:20px;">
<h4 style="margin:0 0 12px 0;color:#334155;font-size:16px;font-weight:600;">2. 注意事项</h4>
<ul style="margin:0;padding-left:20px;">
<li>配置修改后需点击「保存配置」才能生效</li>
<li>最慢速度建议大于最快速度(不然到底是最大还是最小?)</li>
<li>参数值过大可能导致卡顿哦~</li>
</ul>
</div>
<div style="margin-bottom:0;">
<h4 style="margin:0 0 12px 0;color:#334155;font-size:16px;font-weight:600;">3. 开发人员</h4>
<ul style="margin:0;padding-left:20px;">
<li>Super_Diu(uid:1057013)项目开创者</li>
<li>twyeottk (uid:1635665)项目合作者(比楼上厉害)</li>
<li>doubao(uid:?)项目开发者</li>
</ul>
</div>
</div>
`;
contentWrap.appendChild(guidePanel);
const mask = document.createElement('div');
mask.style.cssText = `
position:fixed;top:0;left:0;width:100%;height:100%;
background:rgba(0,0,0,0.5);z-index:9999998;backdrop-filter:blur(2px);
`;
document.body.appendChild(mask);
document.body.appendChild(container);
function initSlider(sliderGroup) {
const container = sliderGroup.querySelector('.slider-container');
const progress = sliderGroup.querySelector('.slider-progress');
const thumb = sliderGroup.querySelector('.slider-thumb');
const input = sliderGroup.querySelector('.slider-input');
const valueDisplay = sliderGroup.querySelector('.slider-value');
const paramName = sliderGroup.dataset.param;
const min = parseFloat(input.min);
const max = parseFloat(input.max);
const step = parseFloat(input.step);
const showDecimal = decimalParams.has(paramName);
const getPercent = (value) => {
return Math.max(0, Math.min(100, ((value - min) / (max - min)) * 100));
};
const formatValue = (value) => {
if (showDecimal) {
return value.toFixed(1);
}
return Math.round(value).toString();
};
const updateSlider = (value, animate = false) => {
const percent = getPercent(value);
if (animate) {
progress.style.transition = 'width 0.2s ease';
thumb.style.transition = 'left 0.2s ease';
} else {
progress.style.transition = 'none';
thumb.style.transition = 'none';
}
progress.style.width = `${percent}%`;
thumb.style.left = `${percent}%`;
input.value = value;
valueDisplay.textContent = `当前值:${formatValue(value)}`;
};
const getValueFromMouse = (clientX) => {
const rect = container.getBoundingClientRect();
const mouseX = Math.max(0, Math.min(rect.width, clientX - rect.left));
const percent = (mouseX / rect.width);
let value = min + percent * (max - min);
if (step !== 1) {
value = Math.round(value / step) * step;
} else {
value = Math.round(value);
}
return Math.max(min, Math.min(max, value));
};
updateSlider(parseFloat(input.value), false);
let isDragging = false;
const handleMouseDown = (e) => {
e.preventDefault();
e.stopPropagation();
isDragging = true;
const value = getValueFromMouse(e.clientX);
updateSlider(value, false);
const handleMouseMove = (e) => {
if (!isDragging) return;
const value = getValueFromMouse(e.clientX);
updateSlider(value, false);
};
const handleMouseUp = () => {
if (!isDragging) return;
isDragging = false;
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
document.removeEventListener('mouseleave', handleMouseUp);
};
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
document.addEventListener('mouseleave', handleMouseUp);
};
thumb.addEventListener('mousedown', handleMouseDown);
container.addEventListener('mousedown', handleMouseDown);
input.addEventListener('input', () => {
updateSlider(parseFloat(input.value), true);
});
sliderGroup.getValue = () => {
return parseFloat(input.value);
};
}
document.querySelectorAll('.slider-group').forEach(initSlider);
document.getElementById('saveBtn').addEventListener('click', () => {
document.querySelectorAll('.slider-group').forEach(group => {
const paramName = group.dataset.param;
const value = group.getValue();
GM_setValue(paramName, value);
cfg[paramName] = value;
});
alert('配置保存成功!立即生效');
container.remove();
mask.remove();
});
document.getElementById('resetBtn').addEventListener('click', () => {
if (confirm('确定要重置所有配置为默认值吗?')) {
Object.keys(defCfg).forEach(k => {
GM_setValue(k, defCfg[k]);
cfg[k] = defCfg[k];
});
alert('已重置为默认配置!');
container.remove();
mask.remove();
location.reload();
}
});
});
}
function clickTriEffect() {
let tris = [];
let dots = [];
let cvs, ctx;
let w, h;
const blueColors = ["#1E90FF", "#4169E1", "#00BFFF", "#87CEFA", "#6495ED", "#0099FF", "#7B68EE"];
const triTypes = ["acute", "right", "obtuse", "isosceles", "equilateral"];
let MAX_TRACK = cfg.MAX_TRACK;
let DOT_TRACK = cfg.DOT_TRACK;
let pressStart = 0;
let isPress = false;
let px = 0, py = 0;
let lastDotSpawn = 0;
const DOT_INTERVAL = 150;
let collMode = false;
const CONTENT_TAGS = new Set(['P', 'SPAN', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'A', 'LI', 'TD', 'IMG', 'CANVAS', 'SVG', 'VIDEO']);
const INTERACTIVE_TAGS = new Set(['INPUT', 'TEXTAREA', 'BUTTON', 'SELECT', 'TEXTAREA', 'LABEL', 'FORM']);
cvs = document.createElement("canvas");
document.body.appendChild(cvs);
cvs.setAttribute("style", "width:100%;height:100%;top:0;left:0;z-index:99999;position:fixed;pointer-events:none;");
ctx = cvs.getContext("2d");
updateCvsSize();
window.addEventListener('resize', updateCvsSize, false);
requestAnimationFrame(loop);
window.addEventListener("keydown", function(e) {
if (e.keyCode === 192) {
const activeEl = document.activeElement;
if (INTERACTIVE_TAGS.has(activeEl.tagName) || activeEl.isContentEditable) {
return;
} else {
collMode = !collMode;
e.preventDefault();
}
}
});
window.addEventListener("mousedown", function(e){
isPress = true;
pressStart = Date.now();
px = e.clientX;
py = e.clientY;
lastDotSpawn = Date.now();
});
window.addEventListener("mouseup", createEffect);
window.addEventListener("mouseout", function(){
isPress = false;
pressStart = 0;
dots.forEach(dot => dot.needFade = true);
});
window.addEventListener("mousemove", function(e){
if(isPress){
px = e.clientX;
py = e.clientY;
dots.forEach(dot => {
if(!dot.arrived && !dot.hasColl) {
dot.tx = px;
dot.ty = py;
dot.angle = Math.atan2(dot.ty - dot.y, dot.tx - dot.x);
}
});
}
});
function updateCvsSize(){
cvs.width = window.innerWidth * 2;
cvs.height = window.innerHeight * 2;
cvs.style.width = window.innerWidth + 'px';
cvs.style.height = window.innerHeight + 'px';
ctx.scale(2,2);
w = window.innerWidth;
h = window.innerHeight;
}
function alphaToHex(alpha){
const hex = Math.floor(Math.max(0, Math.min(255, alpha*255))).toString(16);
return hex.length===1 ? '0'+hex : hex;
}
function randInt(min,max){ return Math.floor(Math.random()*(max-min+1))+min; }
function randFloat(min,max){ return Math.random()*(max-min)+min; }
function isRealContentEl(el) {
if (!el) return false;
if (INTERACTIVE_TAGS.has(el.tagName)) return false;
if (['IMG', 'CANVAS', 'SVG', 'VIDEO'].includes(el.tagName)) return true;
if (CONTENT_TAGS.has(el.tagName)) {
const text = el.textContent?.trim() || '';
const rect = el.getBoundingClientRect();
const hasSize = rect.width > 0 && rect.height > 0;
const isVisible = window.getComputedStyle(el).display !== 'none' &&
window.getComputedStyle(el).visibility !== 'hidden';
return text.length > 0 && hasSize && isVisible;
}
const parent = el.parentElement;
return parent ? isRealContentEl(parent) : false;
}
function isPointOnContent(x, y) {
const el = document.elementFromPoint(x, y);
if (!el || el === cvs) return false;
return isRealContentEl(el);
}
function checkColl(x, y) {
if (!collMode) return false;
if (x < 0 || x > w || y < 0 || y > h) return true;
return isPointOnContent(x, y);
}
function getBounceRange(flyAngle) {
flyAngle = flyAngle % (Math.PI * 2);
if (flyAngle < 0) flyAngle += Math.PI * 2;
let minAngle, maxAngle;
if (flyAngle >= Math.PI/2 && flyAngle <= Math.PI*3/2) {
if (flyAngle >= Math.PI/2 && flyAngle <= Math.PI) {
minAngle = -Math.PI;
maxAngle = 0;
} else {
minAngle = -Math.PI/2;
maxAngle = Math.PI/2;
}
} else {
if (flyAngle < Math.PI/2) {
minAngle = -Math.PI/2;
maxAngle = Math.PI/2;
} else {
minAngle = Math.PI/2;
maxAngle = Math.PI*3/2;
}
}
if (flyAngle > Math.PI/4 && flyAngle < Math.PI*3/4) {
minAngle = -Math.PI;
maxAngle = 0;
} else if (flyAngle > Math.PI*5/4 && flyAngle < Math.PI*7/4) {
minAngle = -Math.PI/2;
maxAngle = Math.PI/2;
}
return { minAngle, maxAngle };
}
function createCollEffect(x, y, color, size, flyAngle, spd) {
const sizeRatio = Math.min(1, size / cfg.MAX_SIZE);
const spdRatio = Math.min(1, spd / cfg.MAX_SPD_LIMIT);
const partCnt = Math.floor(cfg.BASE_COLL_PART_CNT + sizeRatio * (cfg.MAX_COLL_PART_CNT - cfg.BASE_COLL_PART_CNT));
const maxSpd = cfg.BASE_COLL_SPD + spdRatio * (cfg.MAX_COLL_SPD - cfg.BASE_COLL_SPD);
const maxSize = cfg.BASE_COLL_SIZE + sizeRatio * (cfg.MAX_COLL_SIZE - cfg.BASE_COLL_SIZE);
const { minAngle, maxAngle } = getBounceRange(flyAngle);
for (let i = 0; i < partCnt; i++) {
const angle = randFloat(minAngle, maxAngle);
const centerAngle = (minAngle + maxAngle) / 2;
const spdFactor = Math.cos(angle - centerAngle);
const partSpd = randFloat(maxSpd * 0.7, maxSpd) * (0.9 + spdFactor * 0.3);
const partSize = randFloat(maxSize * 0.5, maxSize);
tris.push(new CollPart(
x, y,
angle,
partSpd,
partSize,
color,
sizeRatio
));
}
}
function createEffect(){
if(!isPress) return;
const pressDur = Date.now() - pressStart;
isPress = false;
pressStart = 0;
dots.forEach(dot => dot.needFade = true);
let sizeMin = 7, sizeMax = 13;
let alphaSpdMin = cfg.ALPHA_SPD_BASE, alphaSpdMax = 35;
let createCnt = cfg.BASE_CNT;
let spdMin, spdMax;
let isCharge = false;
if(pressDur < cfg.LONG_PRESS){
spdMin = cfg.BASE_SPD;
spdMax = cfg.BASE_SPD_MAX;
} else {
isCharge = true;
const over = pressDur - cfg.LONG_PRESS;
const scale = Math.min(1, over/cfg.SCALE_DUR);
createCnt = Math.floor(cfg.BASE_CNT + scale*(cfg.MAX_CNT - cfg.BASE_CNT));
sizeMax = 13 + scale*(cfg.MAX_SIZE - 13);
spdMin = cfg.MIN_SPD_LIMIT;
spdMax = cfg.MAX_SPD_LIMIT;
alphaSpdMin = cfg.ALPHA_SPD_BASE - scale*(cfg.ALPHA_SPD_BASE - cfg.MIN_ALPHA_SPD);
alphaSpdMax = 35 - scale*(35 - cfg.MIN_ALPHA_SPD);
}
for(let i=0;i<createCnt;i++){
const triSpd = randFloat(spdMin, spdMax);
let triAlphaSpd = randFloat(alphaSpdMin, alphaSpdMax);
triAlphaSpd = (Math.min(triAlphaSpd + triSpd * (isCharge ? 0.5 : 2), 50) / (1000 * cfg.ALPHA_DECAY_SCALE)) + (triSpd * cfg.SPD_FADE_FACTOR);
const triSize = randInt(sizeMin, sizeMax);
tris.push(new Triangle(px, py, triSize, triSpd, triAlphaSpd, isCharge));
}
}
class CollPart {
constructor(x, y, angle, spd, size, color, sizeRatio) {
this.x = x;
this.y = y;
this.angle = angle;
this.spd = spd;
this.size = size;
this.color = color;
this.alpha = 1;
this.alphaSpd = randFloat(0.012, 0.025) * (1 - sizeRatio * 0.5);
this.sizeSpd = randFloat(0.04, 0.08) * (1 - sizeRatio * 0.4);
this.isCollPart = true;
this.decel = 0.97;
this.decelStep = 0.005;
}
update() {
if (this.alpha <= 0) return;
this.x += Math.cos(this.angle) * this.spd;
this.y += Math.sin(this.angle) * this.spd;
this.spd *= this.decel;
this.decel = Math.max(0.92, this.decel - this.decelStep);
this.alpha = Math.max(0, this.alpha - this.alphaSpd);
this.size = Math.max(0, this.size - this.sizeSpd);
}
draw() {
if (this.alpha <= 0 || this.size <= 0) return;
ctx.save();
ctx.globalAlpha = this.alpha;
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
ctx.restore();
}
}
class Dot {
constructor(tx,ty){
this.tx = tx; this.ty = ty;
this.hasColl = false;
let x, y, attempts = 0;
do {
const r = randFloat(cfg.DOT_SPAWN_R_MIN, cfg.DOT_SPAWN_R_MAX);
let angle = randFloat(0, Math.PI*2);
angle += randFloat(-cfg.DOT_ANGLE_JITTER, cfg.DOT_ANGLE_JITTER);
x = tx + Math.cos(angle)*r;
y = ty + Math.sin(angle)*r;
attempts++;
} while (isPointOnContent(x, y) && attempts < 30);
this.x = x;
this.y = y;
this.r = randFloat(cfg.DOT_R_MIN, cfg.DOT_R_MAX);
this.color = blueColors[Math.floor(Math.random()*blueColors.length)];
this.alpha = 0;
this.spd = cfg.DOT_INIT_SPD + randFloat(-0.2, 0.2);
this.accel = cfg.DOT_ACCEL + randFloat(-0.05, 0.05);
this.needFade = false;
this.angle = Math.atan2(ty - this.y, tx - this.x);
this.track = [];
this.arrived = false;
}
addTrack(){
if(this.arrived || this.hasColl) return;
this.track.push({x:this.x,y:this.y,alpha:this.alpha,r:this.r});
if(this.track.length>DOT_TRACK) this.track.shift();
}
update(){
if(this.arrived || this.hasColl){
this.alpha=0;
this.track = [];
return;
}
if (collMode && checkColl(this.x, this.y)) {
this.hasColl = true;
this.alpha = 0;
this.track = [];
return;
}
this.addTrack();
const spdFactor = 1+this.spd*cfg.SPD_ALPHA_FACTOR;
this.alpha = this.needFade ? Math.max(0,this.alpha-cfg.DOT_FADE_BASE*spdFactor)
: Math.min(1,this.alpha+cfg.DOT_ALPHA_SPD*spdFactor);
const dx = this.tx - this.x;
const dy = this.ty - this.y;
const dist = Math.sqrt(dx*dx+dy*dy);
const nextStep = this.spd+this.accel;
if(dist<=this.r*3 || nextStep>=dist){
this.arrived=true;
this.alpha=0;
this.track=[];
this.x=this.tx;
this.y=this.ty;
} else {
this.spd = Math.min(this.spd+this.accel,8);
this.x += Math.cos(this.angle)*this.spd;
this.y += Math.sin(this.angle)*this.spd;
}
}
drawTrail(){
if(this.track.length<2 || this.alpha<=0 || this.arrived || this.hasColl) return;
ctx.save();
ctx.beginPath();
ctx.moveTo(this.track[0].x,this.track[0].y);
for(let i=1;i<this.track.length;i++) ctx.lineTo(this.track[i].x,this.track[i].y);
const lineW = this.r*0.3;
if(lineW<=0){ctx.restore();return;}
const grad = ctx.createLinearGradient(
this.track[0].x,this.track[0].y,
this.track[this.track.length-1].x,this.track[this.track.length-1].y
);
grad.addColorStop(0, `${this.color}1A`);
grad.addColorStop(1, `${this.color}${alphaToHex(Math.min(0.6,this.alpha))}`);
ctx.lineWidth = lineW;
ctx.lineCap="butt";
ctx.lineJoin="miter";
ctx.strokeStyle = grad;
ctx.stroke();
ctx.restore();
}
draw(){
if(this.alpha<=0||this.arrived||this.hasColl) return;
this.drawTrail();
ctx.save();
ctx.globalAlpha=this.alpha;
ctx.fillStyle=this.color;
ctx.beginPath();
ctx.arc(this.x,this.y,this.r,0,Math.PI*2);
ctx.closePath();
ctx.fill();
ctx.restore();
}
}
class Triangle {
constructor(x,y,size,spd,alphaSpd,isCharge){
this.x=x;this.y=y;
this.angle=Math.random()*Math.PI*2;
this.spd=spd;
this.rotSpd=(Math.random()-0.5)*18;
this.rot=0;
this.size=size;
this.color=blueColors[Math.floor(Math.random()*blueColors.length)];
this.type=triTypes[Math.floor(Math.random()*triTypes.length)];
this.alpha=1;
this.alphaSpd=alphaSpd;
this.sizeSpd=randInt(5,12)/2000;
this.track=[];
this.hasColl = false;
this.isCharge = isCharge || false;
}
update(){
if(this.alpha<=0) return;
if (collMode && !this.hasColl && checkColl(this.x, this.y)) {
this.hasColl = true;
createCollEffect(this.x, this.y, this.color, this.size, this.angle, this.spd);
this.alpha = 0;
return;
}
this.addTrack();
this.spd *= cfg.TRI_SPD_DECAY;
this.x += Math.cos(this.angle) * this.spd;
this.y += Math.sin(this.angle) * this.spd;
this.rot += this.rotSpd;
this.size = Math.max(1, this.size - this.sizeSpd);
const alphaScale = this.isCharge ? cfg.CHARGE_ALPHA_SCALE : cfg.NORMAL_ALPHA_SCALE;
this.alpha = Math.max(0, this.alpha - this.alphaSpd * alphaScale);
}
addTrack(){
this.track.push({x:this.x,y:this.y,size:this.size,alpha:this.alpha});
if(this.track.length>MAX_TRACK) this.track.shift();
}
drawSingle(ctx,x,y,rot,size,alpha){
if(alpha<=0||size<=0) return;
ctx.save();
ctx.globalAlpha=alpha;
ctx.translate(x,y);
ctx.rotate(rot*Math.PI/180);
ctx.fillStyle=this.color;
ctx.beginPath();
switch(this.type){
case"acute":ctx.moveTo(0,-size);ctx.lineTo(size*0.7,size*0.6);ctx.lineTo(-size*0.7,size*0.6);break;
case"right":ctx.moveTo(0,-size*0.8);ctx.lineTo(size*0.8,size*0.8);ctx.lineTo(-size*0.8,size*0.8);break;
case"obtuse":ctx.moveTo(0,-size);ctx.lineTo(size*1.0,size*0.4);ctx.lineTo(-size*1.0,size*0.4);break;
case"isosceles":ctx.moveTo(0,-size);ctx.lineTo(size*0.6,size*0.8);ctx.lineTo(-size*0.6,size*0.8);break;
case"equilateral":const h=size*Math.sqrt(3)/2;ctx.moveTo(0,-h/2);ctx.lineTo(size/2,h/2);ctx.lineTo(-size/2,h/2);break;
}
ctx.closePath();ctx.fill();ctx.restore();
}
drawTrail(ctx){
if(this.track.length<2||this.alpha<=0) return;
const trackCnt=this.track.length;
const startP=this.track[0];
const endP=this.track[trackCnt-1];
const trailAngle=Math.atan2(endP.y-startP.y,endP.x-startP.x);
const endW=endP.size*0.3;
const startW=endW*0.05;
ctx.save();
const endLX=endP.x+Math.cos(trailAngle+Math.PI/2)*endW;
const endLY=endP.y+Math.sin(trailAngle+Math.PI/2)*endW;
const endRX=endP.x+Math.cos(trailAngle-Math.PI/2)*endW;
const endRY=endP.y+Math.sin(trailAngle-Math.PI/2)*endW;
const startX=startP.x+Math.cos(trailAngle)*startW;
const startY=startP.y+Math.sin(trailAngle)*startW;
ctx.beginPath();
ctx.moveTo(endLX,endLY);
ctx.lineTo(endRX,endRY);
ctx.lineTo(startX,startY);
ctx.closePath();
const grad=ctx.createLinearGradient(endP.x,endP.y,startP.x,startP.y);
grad.addColorStop(0,`${this.color}${alphaToHex(Math.min(0.4,this.alpha))}`);
grad.addColorStop(1,`${this.color}10`);
ctx.fillStyle=grad;
ctx.fill();
ctx.restore();
}
draw(){if(this.alpha<=0) return; this.drawTrail(ctx); this.drawSingle(ctx,this.x,this.y,this.rot,this.size,this.alpha);}
}
function loop(){
ctx.clearRect(0,0,w,h);
if(isPress){
const pressDur=Date.now()-pressStart;
if(pressDur >= cfg.LONG_PRESS){
const now=Date.now();
if(now-lastDotSpawn>=DOT_INTERVAL){
dots.push(new Dot(px,py));
lastDotSpawn=now;
}
}
}
dots.forEach(dot=>dot.update());
dots.forEach(dot=>dot.draw());
dots=dots.filter(dot=>dot.alpha>0&&!dot.arrived&&!dot.hasColl);
tris.forEach(tri=>{
if (tri.isCollPart) {
tri.update();
} else {
tri.update();
}
});
tris.forEach(tri=>{
if (tri.isCollPart) {
tri.draw();
} else {
tri.draw();
}
});
tris=tris.filter(tri=>{
if (tri.isCollPart) {
return tri.alpha > 0 && tri.size > 0;
} else {
return tri.alpha > 0 && !tri.hasColl;
}
});
requestAnimationFrame(loop);
}
}
createCfgMenu();
clickTriEffect();
})();
部分代码使用 AI 生成。