您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Fps limiter for browser games or some 2D/3D animations
当前为
// ==UserScript== // @name HTML canvas fps limiter // @description Fps limiter for browser games or some 2D/3D animations // @author Konf // @namespace https://gf.qytechs.cn/users/424058 // @icon https://img.icons8.com/external-neu-royyan-wijaya/32/external-animation-neu-solid-neu-royyan-wijaya.png // @icon64 https://img.icons8.com/external-neu-royyan-wijaya/64/external-animation-neu-solid-neu-royyan-wijaya.png // @version 1.0.0 // @match *://*/* // @compatible Chrome // @compatible Opera // @compatible Firefox // @run-at document-start // @grant unsafeWindow // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // ==/UserScript== /* * Implementation is kinda rough, but it seems working, so I don't care anymore * * A huge part is inspired (stolen) from: * https://chriscourses.com/blog/standardize-your-javascript-games-framerate-for-different-monitors * * msPrevMap is needed to provide individual rate limiting in cases * where requestAnimationFrame is used by more than one function loop. * Using a variable instead of a map in such cases makes so the only one * random loop will be working, and the others will not be working at all. * But if some loop is using anonymous functions, the map mode can't limit it, * so I've decided to make a switcher: the map mode or the single variable mode. * Default is the map mode (mode 1) */ /* jshint esversion: 8 */ (function() { function DataStore(uuid, defaultStorage = {}) { if (typeof uuid !== 'string' && typeof uuid !== 'number') { throw new Error('Expected uuid when creating DataStore'); } let cachedStorage = defaultStorage; try { cachedStorage = JSON.parse(GM_getValue(uuid)); } catch (err) { GM_setValue(uuid, JSON.stringify(defaultStorage)); } const getter = (obj, prop) => cachedStorage[prop]; const setter = (obj, prop, val) => { cachedStorage[prop] = val; GM_setValue(uuid, JSON.stringify(cachedStorage)); return val; } return new Proxy({}, { get: getter, set: setter }); } const MODE = { map: 1, variable: 2, }; const DEFAULT_FPS_CAP = 5; const DEFAULT_MODE = MODE.map; const s = DataStore('storage', { fpsCap: DEFAULT_FPS_CAP, isFirstRun: true, mode: DEFAULT_MODE, }); const oldRequestAnimationFrame = window.requestAnimationFrame; const msPrevMap = new Map(); const menuCommandsIds = []; let msPerFrame = 1000 / s.fpsCap; let msPrev = 0; unsafeWindow.requestAnimationFrame = function newRequestAnimationFrame(cb, el) { return oldRequestAnimationFrame((now) => { const msPassed = now - ((s.mode === MODE.map ? msPrevMap.get(cb) : msPrev) || 0); if (msPassed < msPerFrame) return newRequestAnimationFrame(cb, el); if (s.mode === MODE.variable) { msPrev = now - (msPassed % msPerFrame); // subtract excess time } else { msPrevMap.set(cb, now - (msPassed % msPerFrame)); } return cb(now); }, el); } // mode 1 garbage collector. 50 is random number setInterval(() => (msPrevMap.size > 50) && msPrevMap.clear(), 1000); function changeFpsCapWithUser() { const userInput = prompt( `Current fps cap: ${s.fpsCap}. ` + 'What should be the new one? Leave empty or cancel to not to change' ); if (userInput !== null && userInput !== '') { let userInputNum = Number(userInput); if (isNaN(userInputNum)) { messageUser('bad input', 'Seems like the input is not number'); } else if (userInputNum > 9999) { s.fpsCap = 9999; messageUser( 'bad input', 'Seems like the input is way too big number. Decreasing it to 9999', ); } else if (userInputNum < 0) { messageUser( 'bad input', "The input number can't be negative", ); } else { s.fpsCap = userInputNum; } msPerFrame = 1000 / s.fpsCap; // can't be applied in iframes messageUser( `the fps cap was set to ${s.fpsCap}`, "For some places the fps cap change can't be applied without a reload, " + "and if you can't tell worked it out or not, better to refresh the page", ); unregisterMenuCommands(); registerMenuCommands(); } } function messageUser(title, text) { alert(`Fps limiter: ${title}.\n\n${text}`); } function registerMenuCommands() { menuCommandsIds.push(GM_registerMenuCommand( `Cap fps (${s.fpsCap} now)`, () => changeFpsCapWithUser(), 'c' )); menuCommandsIds.push(GM_registerMenuCommand( `Switch mode to ${s.mode === MODE.map ? MODE.variable : MODE.map}`, () => { s.mode = s.mode === MODE.map ? MODE.variable : MODE.map; // can't be applied in iframes messageUser( `the mode was set to ${s.mode}`, "For some places the mode change can't be applied without a reload, " + "and if you can't tell worked it out or not, better to refresh the page. " + "You can find description of the modes at the script download page", ); unregisterMenuCommands(); registerMenuCommands(); }, 'm' )); } function unregisterMenuCommands() { for (const id of menuCommandsIds) { GM_unregisterMenuCommand(id); } menuCommandsIds.length = 0; } registerMenuCommands(); if (s.isFirstRun) { messageUser( 'it seems like your first run of this script', 'You need to refresh the page on which this script should work. ' + `What fps cap do you prefer? Default is ${DEFAULT_FPS_CAP} as a demonstration. ` + 'You can always quickly change it from your script manager icon ↗' ); changeFpsCapWithUser(); s.isFirstRun = false; } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址