定时关闭网页

拖拽弹窗,滑块设置分钟+秒,右上角显示剩余时间

目前為 2025-08-18 提交的版本,檢視 最新版本

// ==UserScript==
// @name         定时关闭网页
// @namespace    https://github.com/yingchen6
// @version      1.8.7.12
// @description  拖拽弹窗,滑块设置分钟+秒,右上角显示剩余时间
// @author       yingchen6
// @match        *://*/*
// @grant        GM_registerMenuCommand
// @license MIT
// @require      https://unpkg.com/sweetalert/dist/sweetalert.min.js
// ==/UserScript==


(function(){
    'use strict';

    let timerId=null;
    let remainingSeconds=0;
    let totalSeconds=0;
    let ball=null;
    let canvas=null;
    let paused=false;
    let theme='green'; // 默认主题

    const THEMES={
        green:{bg:'rgba(150,180,160,0.6)',progress:'#5B7A63',panel:'rgba(150,180,160,0.5)',label:'#ffffff',btnBg:'#8DA897',btnText:'#fff'},
        bluegray:{bg:'rgba(120,140,160,0.6)',progress:'#3E586C',panel:'rgba(120,140,160,0.5)',label:'#ffffff',btnBg:'#708090',btnText:'#fff'},
        yellow:{bg:'rgba(200,180,120,0.6)',progress:'#A1864D',panel:'rgba(200,180,120,0.5)',label:'#ffffff',btnBg:'#BFA76F',btnText:'#fff'},
        purple:{bg:'rgba(160,140,180,0.6)',progress:'#7A637A',panel:'rgba(160,140,180,0.5)',label:'#ffffff',btnBg:'#8F7090',btnText:'#fff'}
    };

    function formatTime(seconds){
        const m=Math.floor(seconds/60);
        const s=seconds%60;
        return `${m}:${s.toString().padStart(2,'0')}`;
    }

    function createBall(minutes){
        totalSeconds=remainingSeconds=minutes*60;
        paused=false;

        if(ball) ball.remove();

        ball=document.createElement("div");
        ball.id="timer-ball";
        Object.assign(ball.style,{
            position:"fixed",top:"20px",left:"20px",width:"120px",height:"120px",
            borderRadius:"50%",zIndex:"999999",cursor:"grab",display:"flex",
            justifyContent:"center",alignItems:"center",userSelect:"none",
            background:THEMES[theme].bg,boxShadow:"0 8px 25px rgba(0,0,0,0.5)"
        });
        document.body.appendChild(ball);

        canvas=document.createElement("canvas");
        canvas.width=120;canvas.height=120;
        canvas.style.position="absolute";canvas.style.top="0";canvas.style.left="0";
        ball.appendChild(canvas);

        const label=document.createElement("div");
        label.id="timer-label";
        Object.assign(label.style,{position:"absolute",color:THEMES[theme].label,fontWeight:"bold",fontSize:"20px"});
        ball.appendChild(label);

        const pauseBtn=document.createElement("div");
        pauseBtn.id="pause-btn";
        Object.assign(pauseBtn.style,{
            position:"absolute",bottom:"10px",fontSize:"16px",color:THEMES[theme].btnText,
            background:THEMES[theme].btnBg,borderRadius:"10px",padding:"6px 12px",cursor:"pointer"
        });
        pauseBtn.textContent="暂停";
        ball.appendChild(pauseBtn);

        pauseBtn.addEventListener("click",e=>{
            e.stopPropagation();
            paused=!paused;
            pauseBtn.textContent=paused?"继续":"暂停";
        });

        // 拖拽 + 双击修改时间
        let isDown=false,isDragging=false,startX=0,startY=0,downTime=0;
        ball.addEventListener("mousedown",e=>{
            if(e.target===pauseBtn) return;
            isDown=true;isDragging=false;
            startX=e.clientX-ball.offsetLeft;startY=e.clientY-ball.offsetTop;
            downTime=Date.now();
            ball.style.cursor="grabbing";
        });

        document.addEventListener("mousemove",e=>{
            if(!isDown) return;
            const dx=e.clientX-startX-ball.offsetLeft;
            const dy=e.clientY-startY-ball.offsetTop;
            if(Math.sqrt(dx*dx+dy*dy)>5)isDragging=true;
            if(isDragging){
                ball.style.left=(e.clientX-startX)+"px";
                ball.style.top=(e.clientY-startY)+"px";
            }
        });

        document.addEventListener("mouseup",e=>{
            if(!isDown) return;
            isDown=false;
            ball.style.cursor="grab";
            const elapsed=Date.now()-downTime;
            if(!isDragging && elapsed<400){
                showTimeSetting(); // 双击打开剩余时间设置
            }
        });

        startCountdown(minutes);
    }

    function startCountdown(minutes){
        totalSeconds=remainingSeconds=minutes*60;
        paused=false;
        if(timerId) clearInterval(timerId);

        timerId=setInterval(()=>{
            if(!paused){
                remainingSeconds--;
                if(remainingSeconds<=0){clearInterval(timerId);window.location.href="about:blank";return;}
                const label=document.getElementById("timer-label");
                label.textContent=formatTime(remainingSeconds);

                const ctx=canvas.getContext("2d");
                ctx.clearRect(0,0,canvas.width,canvas.height);
                ctx.beginPath();
                ctx.arc(canvas.width/2,canvas.height/2,50,0,2*Math.PI);
                ctx.strokeStyle="rgba(255,255,255,0.2)";
                ctx.lineWidth=10;ctx.stroke();

                const fraction=remainingSeconds/totalSeconds;
                ctx.beginPath();
                ctx.arc(canvas.width/2,canvas.height/2,50,-Math.PI/2,-Math.PI/2+2*Math.PI*fraction);
                ctx.strokeStyle=THEMES[theme].progress;
                ctx.lineWidth=10;ctx.lineCap="round";ctx.stroke();
            }
        },1000);
    }

    function showTimeSetting(){
    const wrapper=document.createElement("div");
    Object.assign(wrapper.style,{
        display:"flex",
        flexDirection:"column",
        alignItems:"center",
        gap:"10px",
        padding:"15px",
        borderRadius:"12px",
        background:THEMES[theme].panel,
        position:"fixed",
        top:"50%",
        left:"50%",
        transform:"translate(-50%,-50%)",
        zIndex:"1000000",
        cursor:"grab",
        minWidth:"260px"
    });

    // 内部HTML
    wrapper.innerHTML=`
        <div style="width:100%;display:flex;justify-content:space-between;align-items:center;">
            <div style="font-size:20px;font-weight:bold;color:${THEMES[theme].label}">设置倒计时</div>
            <select id="themeSelect" style="border-radius:6px;padding:2px 6px;background:${THEMES[theme].btnBg};color:${THEMES[theme].btnText};border:none;cursor:pointer;">
                <option value="green">绿色</option>
                <option value="bluegray">蓝灰</option>
                <option value="yellow">黄色</option>
                <option value="purple">紫色</option>
            </select>
        </div>
        <label>分钟: <input id="timeMin" type="range" min="0" max="180" step="1" value="${Math.floor(remainingSeconds/60)}"></label>
        <span id="minLabel">${Math.floor(remainingSeconds/60)}</span> 分钟
        <label>秒: <input id="timeSec" type="range" min="0" max="59" step="1" value="${remainingSeconds%60}"></label>
        <span id="secLabel">${remainingSeconds%60}</span> 秒
        <div style="display:flex;gap:10px;margin-top:15px;">
            <button id="confirmBtn">确认</button>
            <button id="cancelBtn">取消</button>
        </div>
    `;
    document.body.appendChild(wrapper);

    // 滑块事件
    const minInput=wrapper.querySelector("#timeMin");
    const secInput=wrapper.querySelector("#timeSec");
    const minLabel=wrapper.querySelector("#minLabel");
    const secLabel=wrapper.querySelector("#secLabel");
    minInput.addEventListener("input",()=>{minLabel.textContent=minInput.value;});
    secInput.addEventListener("input",()=>{secLabel.textContent=secInput.value;});

    // 按钮样式函数
    function styleBtn(btn){
        Object.assign(btn.style,{
            padding:"8px 14px",
            border:"none",
            borderRadius:"8px",
            fontSize:"16px",
            background:THEMES[theme].btnBg,
            color:THEMES[theme].btnText,
            cursor:"pointer",
            flex:"1"
        });
    }
    styleBtn(wrapper.querySelector("#confirmBtn"));
    styleBtn(wrapper.querySelector("#cancelBtn"));

    // 拖拽面板
    let isDown=false,startX=0,startY=0;
    wrapper.addEventListener("mousedown",e=>{
        if(["confirmBtn","cancelBtn","themeSelect"].includes(e.target.id)) return;
        isDown=true;
        startX=e.clientX-wrapper.offsetLeft;
        startY=e.clientY-wrapper.offsetTop;
        wrapper.style.cursor="grabbing";
    });
    document.addEventListener("mousemove",e=>{
        if(!isDown) return;
        wrapper.style.left=(e.clientX-startX)+"px";
        wrapper.style.top=(e.clientY-startY)+"px";
        wrapper.style.transform="translate(0,0)";
    });
    document.addEventListener("mouseup",()=>{isDown=false; wrapper.style.cursor="grab";});

    // 主题选择下拉框
    const themeSelect=wrapper.querySelector("#themeSelect");
    themeSelect.value=theme;
    themeSelect.addEventListener("change",()=>{
        theme=themeSelect.value;
        wrapper.style.background=THEMES[theme].panel;
        wrapper.querySelector("div").style.color=THEMES[theme].label;
        styleBtn(wrapper.querySelector("#confirmBtn"));
        styleBtn(wrapper.querySelector("#cancelBtn"));
        themeSelect.style.background=THEMES[theme].btnBg;
        themeSelect.style.color=THEMES[theme].btnText;
    });

    // 确认
    wrapper.querySelector("#confirmBtn").addEventListener("click",()=>{
        const min=parseInt(minInput.value);
        const sec=parseInt(secInput.value);
        remainingSeconds=totalSeconds=min*60+sec;
        wrapper.remove();
        createBall(Math.ceil(totalSeconds/60));
    });

    // 取消
    wrapper.querySelector("#cancelBtn").addEventListener("click",()=>{wrapper.remove();});
}



    function registerMenu(){
        if(typeof GM_registerMenuCommand==="undefined") return;
        GM_registerMenuCommand("开始计时", showTimeSetting);
    }

    setTimeout(registerMenu, 500);

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址