Amazing click effect!

Long press for surprises! Enjoy clicking ~

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

You will need to install an extension such as Tampermonkey to install this script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==UserScript==
// @name         Amazing click effect!
// @namespace    http://tampermonkey.net/
// @version      1.12.3
// @description  Long press for surprises! Enjoy clicking ~
// @author       Super_Diu
// @match        *://*/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    function clickTriangleEffect() {
        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"];
        // 尾迹轨迹点
        const MAX_TRACK = 10;
        const DOT_TRACK = 5;

        // 长按核心参数(0.8s触发蓄力)
        let pressStart = 0;
        let isPress = false;
        let px = 0, py = 0;
        const LONG_PRESS = 800;
        // 进阶特效上限
        const MAX_SIZE = 20;
        const MAX_SPEED = 15;
        const MIN_ALPHA_SPEED = 8;

        // 三角形数量随长按递增
        const BASE_COUNT = 12;
        const MAX_COUNT = 30;
        const SCALE_DUR = 3000;

        // 蓄力小圆点配置
        const DOT_R_MIN = 1;
        const DOT_R_MAX = 3;
        const DOT_INTERVAL = 150;
        const DOT_INIT_SPD = 1.2;
        const DOT_ACCEL = 0.2;
        const DOT_ALPHA_SPD = 0.02;
        const DOT_FADE_BASE = 0.03;
        const DOT_SPAWN_R_MIN = 180;
        const DOT_SPAWN_R_MAX = 280;
        const SPEED_ALPHA_FACTOR = 0.0001;
        let lastDotSpawn = 0;

        // 创建画布
        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;");

        // 初始化画布
        if (cvs.getContext && window.addEventListener) {
            ctx = cvs.getContext("2d");
            updateCvsSize();
            window.addEventListener('resize', updateCvsSize, false);
            requestAnimationFrame(loop);

            // 鼠标事件
            window.addEventListener("mousedown", function(e) {
                isPress = true;
                pressStart = Date.now();
                px = e.clientX;
                py = e.clientY;
                lastDotSpawn = Date.now();
            }, false);
            window.addEventListener("mouseup", createEffect, false);
            window.addEventListener("mouseout", function() {
                isPress = false;
                pressStart = 0;
                dots.forEach(dot => dot.needFade = true);
            }, false);

        } else {
            console.log("当前浏览器不支持Canvas或addEventListener!");
        }

        // 画布大小更新
        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 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 spdMin = 4, spdMax = 9;
            let alphaSpdMin = 25, alphaSpdMax = 35;
            let createCnt = BASE_COUNT;

            if (pressDur >= LONG_PRESS) {
                const overThresh = pressDur - LONG_PRESS;
                const scale = Math.min(1, overThresh / SCALE_DUR);
                createCnt = Math.floor(BASE_COUNT + scale * (MAX_COUNT - BASE_COUNT));
                sizeMax = 13 + scale * (MAX_SIZE - 13);
                spdMax = 9 + scale * (MAX_SPEED - 9);
                alphaSpdMin = 25 - scale * (25 - MIN_ALPHA_SPEED);
                alphaSpdMax = 35 - scale * (35 - MIN_ALPHA_SPEED);
            }

            // 生成三角形
            for (let i = 0; i < createCnt; i++) {
                const triSpd = randInt(spdMin, spdMax);
                let triAlphaSpd = randInt(alphaSpdMin, alphaSpdMax);
                triAlphaSpd = triAlphaSpd + triSpd * 2;
                triAlphaSpd = Math.min(triAlphaSpd, 50);

                tris.push(new Triangle(
                    px, py,
                    randInt(sizeMin, sizeMax),
                    triSpd,
                    triAlphaSpd / 1000
                ));
            }
        }

        // 蓄力小圆点类
        class Dot {
            constructor(tx, ty) {
                this.tx = tx;
                this.ty = ty;
                const r = randFloat(DOT_SPAWN_R_MIN, DOT_SPAWN_R_MAX);
                const angle = randFloat(0, Math.PI * 2);
                this.x = tx + Math.cos(angle) * r;
                this.y = ty + Math.sin(angle) * r;
                this.r = randFloat(DOT_R_MIN, DOT_R_MAX);
                this.color = blueColors[Math.floor(Math.random() * blueColors.length)];
                this.alpha = 0;
                this.spd = DOT_INIT_SPD;
                this.accel = DOT_ACCEL;
                this.needFade = false;
                this.angle = Math.atan2(ty - this.y, tx - this.x);
                this.track = [];
                this.arrived = false;
            }

            addTrack() {
                if (this.arrived) 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.alpha = 0;
                    return;
                }
                this.addTrack();

                // 速度关联显隐/消失速度
                const spdFactor = 1 + this.spd * SPEED_ALPHA_FACTOR;
                if (this.needFade) {
                    this.alpha = Math.max(0, this.alpha - DOT_FADE_BASE * spdFactor);
                } else {
                    this.alpha = Math.min(1, this.alpha + 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 += this.accel;
                    this.spd = Math.min(this.spd, 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) 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) 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) 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) {
                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) / 100;
                this.track = [];
            }

            update() {
                this.addTrack();
                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);
                this.alpha = Math.max(0, this.alpha - this.alphaSpd);
            }

            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 trailLen = Math.sqrt(
                    Math.pow(endP.x - startP.x, 2) +
                    Math.pow(endP.y - startP.y, 2)
                );
                const endW = endP.size * 0.3; // 宽边(朝向三角形):从0.6→0.3
                const startW = endW * 0.05; // 窄边(尾迹末端):从0.1→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 >= 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);
            // 三角形更新绘制
            tris.forEach(tri => tri.update());
            tris.forEach(tri => tri.draw());
            tris = tris.filter(tri => tri.alpha > 0);

            requestAnimationFrame(loop);
        }
    }

    // 调用特效
    clickTriangleEffect();
})();