Greasy Fork

Greasy Fork is available in English.

设备模拟器

模拟各种设备的 UA 和屏幕参数

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         设备模拟器
// @namespace    http://tampermonkey.net/
// @version      1.4
// @description  模拟各种设备的 UA 和屏幕参数
// @author       Your name
// @license      MIT
// @match        *://*/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM.setValue
// @grant        GM.getValue
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';
    
    // 如果存在保存的设置,立即应用
    const savedSettings = GM_getValue('deviceSettings', null);
    if (savedSettings) {
        try {
            const settings = JSON.parse(savedSettings);
            applySettings(settings);
        } catch (e) {
            console.error('加载初始设置失败:', e);
        }
    }

    // 预设设备列表
    const presetDevices = {
        'iPhone 13': {
            userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1',
            width: 390,
            height: 844,
            deviceScaleFactor: 3
        },
        'Pixel 5': {
            userAgent: 'Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.91 Mobile Safari/537.36',
            width: 393,
            height: 851,
            deviceScaleFactor: 2.75
        },
        'iPad Pro': {
            userAgent: 'Mozilla/5.0 (iPad; CPU OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1',
            width: 1024,
            height: 1366,
            deviceScaleFactor: 2
        }
    };

    // 创建悬浮窗
    function createFloatingWindow() {
        const container = document.createElement('div');
        container.innerHTML = `
            <div id="device-simulator" style="position: fixed; top: 20px; right: 20px; background: white; border: 1px solid #ccc; padding: 10px; z-index: 9999; box-shadow: 0 0 10px rgba(0,0,0,0.1); min-width: 200px;">
                <div id="simulator-header" style="cursor: move; padding: 5px; background: #f0f0f0; margin-bottom: 10px;">
                    <span>设备模拟器</span>
                    <button id="toggle-simulator" style="float: right;">收起</button>
                </div>
                <div id="simulator-content">
                    <select id="device-select" style="width: 100%; margin-bottom: 10px;">
                        <option value="">选择设备</option>
                        ${Object.keys(presetDevices).map(device => `<option value="${device}">${device}</option>`).join('')}
                    </select>
                    <div style="margin-bottom: 10px;">
                        <label>User Agent:</label>
                        <textarea id="ua-input" style="width: 100%; height: 60px;"></textarea>
                    </div>
                    <div style="margin-bottom: 10px;">
                        <label>屏幕宽度:</label>
                        <input type="number" id="width-input" style="width: 100%;">
                    </div>
                    <div style="margin-bottom: 10px;">
                        <label>屏幕高度:</label>
                        <input type="number" id="height-input" style="width: 100%;">
                    </div>
                    <div style="margin-bottom: 10px;">
                        <label>设备像素比:</label>
                        <input type="number" id="scale-input" style="width: 100%;" step="0.01">
                    </div>
                    <button id="apply-settings" style="width: 100%;">应用设置</button>
                </div>
            </div>
        `;

        document.body.appendChild(container);
        return container;
    }

    // 初始化拖拽功能
    function initializeDrag(container) {
        const header = container.querySelector('#simulator-header');
        let isDragging = false;
        let currentX;
        let currentY;
        let initialX;
        let initialY;

        header.addEventListener('mousedown', (e) => {
            isDragging = true;
            initialX = e.clientX - container.offsetLeft;
            initialY = e.clientY - container.offsetTop;
        });

        document.addEventListener('mousemove', (e) => {
            if (isDragging) {
                currentX = e.clientX - initialX;
                currentY = e.clientY - initialY;
                container.style.left = currentX + 'px';
                container.style.top = currentY + 'px';
            }
        });

        document.addEventListener('mouseup', () => {
            isDragging = false;
        });
    }

    // 初始化事件监听
    function initializeEvents(container) {
        const toggleBtn = container.querySelector('#toggle-simulator');
        const content = container.querySelector('#simulator-content');
        const deviceSelect = container.querySelector('#device-select');
        const uaInput = container.querySelector('#ua-input');
        const widthInput = container.querySelector('#width-input');
        const heightInput = container.querySelector('#height-input');
        const scaleInput = container.querySelector('#scale-input');
        const applyBtn = container.querySelector('#apply-settings');

        // 加载保存的设置
        const savedSettings = GM_getValue('deviceSettings', null);
        if (savedSettings) {
            try {
                const settings = JSON.parse(savedSettings);
                uaInput.value = settings.userAgent || '';
                widthInput.value = settings.width || '';
                heightInput.value = settings.height || '';
                scaleInput.value = settings.deviceScaleFactor || '';
                // 立即应用保存的设置
                applySettings(settings);
            } catch (e) {
                console.error('加载设置失败:', e);
            }
        }

        // 切换显示/隐藏
        toggleBtn.addEventListener('click', () => {
            if (content.style.display === 'none') {
                content.style.display = 'block';
                toggleBtn.textContent = '收起';
            } else {
                content.style.display = 'none';
                toggleBtn.textContent = '展开';
            }
        });

        // 选择预设设备
        deviceSelect.addEventListener('change', () => {
            const device = presetDevices[deviceSelect.value];
            if (device) {
                uaInput.value = device.userAgent;
                widthInput.value = device.width;
                heightInput.value = device.height;
                scaleInput.value = device.deviceScaleFactor;
            }
        });

        // 应用设置
        applyBtn.addEventListener('click', () => {
            const settings = {
                userAgent: uaInput.value,
                width: parseInt(widthInput.value) || window.innerWidth,
                height: parseInt(heightInput.value) || window.innerHeight,
                deviceScaleFactor: parseFloat(scaleInput.value) || window.devicePixelRatio
            };
            
            try {
                GM_setValue('deviceSettings', JSON.stringify(settings));
                applySettings(settings);
                console.log('设置已保存');
            } catch (e) {
                console.error('保存设置失败:', e);
            }
        });
    }

    // 应用设备设置
    function applySettings(settings) {
        try {
            // 修改 User Agent
            Object.defineProperty(navigator, 'userAgent', {
                get: function() {
                    return settings.userAgent || navigator.userAgent;
                },
                configurable: true
            });

            // 修改屏幕参数
            const screenProps = {
                width: settings.width,
                height: settings.height,
                availWidth: settings.width,
                availHeight: settings.height
            };

            // 修改 window 属性
            Object.defineProperties(window, {
                'innerWidth': { 
                    value: settings.width,
                    configurable: true,
                    writable: true
                },
                'outerWidth': { 
                    value: settings.width,
                    configurable: true,
                    writable: true
                },
                'innerHeight': { 
                    value: settings.height,
                    configurable: true,
                    writable: true
                },
                'outerHeight': { 
                    value: settings.height,
                    configurable: true,
                    writable: true
                },
                'devicePixelRatio': { 
                    value: settings.deviceScaleFactor,
                    configurable: true,
                    writable: true
                }
            });

            // 修改 screen 属性
            Object.defineProperties(screen, {
                'width': { 
                    value: screenProps.width,
                    configurable: true,
                    writable: true
                },
                'height': { 
                    value: screenProps.height,
                    configurable: true,
                    writable: true
                },
                'availWidth': { 
                    value: screenProps.availWidth,
                    configurable: true,
                    writable: true
                },
                'availHeight': { 
                    value: screenProps.availHeight,
                    configurable: true,
                    writable: true
                }
            });

            // 设置 viewport
            const viewportContent = `width=${settings.width}, initial-scale=1.0, user-scalable=yes, maximum-scale=1.0`;
            let viewport = document.querySelector('meta[name="viewport"]');
            if (viewport) {
                viewport.content = viewportContent;
            } else {
                viewport = document.createElement('meta');
                viewport.name = 'viewport';
                viewport.content = viewportContent;
                document.head.appendChild(viewport);
            }

            // 添加强制样式
            const styleId = 'device-simulator-styles';
            let styleSheet = document.getElementById(styleId);
            if (!styleSheet) {
                styleSheet = document.createElement('style');
                styleSheet.id = styleId;
                document.head.appendChild(styleSheet);
            }
            
            styleSheet.textContent = `
                :root {
                    --device-width: ${settings.width}px;
                }
                
                html, body {
                    overflow-x: hidden !important;
                    width: var(--device-width) !important;
                    min-width: var(--device-width) !important;
                    max-width: var(--device-width) !important;
                    margin: 0 !important;
                    padding: 0 !important;
                }
                
                body > * {
                    max-width: var(--device-width) !important;
                }

                img {
                    max-width: 100% !important;
                    height: auto !important;
                }

                * {
                    -webkit-text-size-adjust: none !important;
                    text-size-adjust: none !important;
                    box-sizing: border-box !important;
                }
            `;

            // 触发重绘事件
            window.dispatchEvent(new Event('resize'));
            window.dispatchEvent(new Event('orientationchange'));
            
            // 强制重新计算布局
            document.documentElement.style.zoom = '99.99999%';
            setTimeout(() => {
                document.documentElement.style.zoom = '100%';
            }, 10);

        } catch (e) {
            console.error('应用设置失败:', e);
        }
    }

    // 初始化
    const container = createFloatingWindow();
    initializeDrag(container);
    initializeEvents(container);
    
    // 默认收起状态
    container.querySelector('#simulator-content').style.display = 'none';
    container.querySelector('#toggle-simulator').textContent = '展开';
})();