设备模拟器

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

  1. // ==UserScript==
  2. // @name 设备模拟器
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.4
  5. // @description 模拟各种设备的 UA 和屏幕参数
  6. // @author Your name
  7. // @license MIT
  8. // @match *://*/*
  9. // @grant GM_setValue
  10. // @grant GM_getValue
  11. // @grant GM.setValue
  12. // @grant GM.getValue
  13. // @run-at document-start
  14. // ==/UserScript==
  15.  
  16. (function() {
  17. 'use strict';
  18. // 如果存在保存的设置,立即应用
  19. const savedSettings = GM_getValue('deviceSettings', null);
  20. if (savedSettings) {
  21. try {
  22. const settings = JSON.parse(savedSettings);
  23. applySettings(settings);
  24. } catch (e) {
  25. console.error('加载初始设置失败:', e);
  26. }
  27. }
  28.  
  29. // 预设设备列表
  30. const presetDevices = {
  31. 'iPhone 13': {
  32. 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',
  33. width: 390,
  34. height: 844,
  35. deviceScaleFactor: 3
  36. },
  37. 'Pixel 5': {
  38. userAgent: 'Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.91 Mobile Safari/537.36',
  39. width: 393,
  40. height: 851,
  41. deviceScaleFactor: 2.75
  42. },
  43. 'iPad Pro': {
  44. 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',
  45. width: 1024,
  46. height: 1366,
  47. deviceScaleFactor: 2
  48. }
  49. };
  50.  
  51. // 创建悬浮窗
  52. function createFloatingWindow() {
  53. const container = document.createElement('div');
  54. container.innerHTML = `
  55. <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;">
  56. <div id="simulator-header" style="cursor: move; padding: 5px; background: #f0f0f0; margin-bottom: 10px;">
  57. <span>设备模拟器</span>
  58. <button id="toggle-simulator" style="float: right;">收起</button>
  59. </div>
  60. <div id="simulator-content">
  61. <select id="device-select" style="width: 100%; margin-bottom: 10px;">
  62. <option value="">选择设备</option>
  63. ${Object.keys(presetDevices).map(device => `<option value="${device}">${device}</option>`).join('')}
  64. </select>
  65. <div style="margin-bottom: 10px;">
  66. <label>User Agent:</label>
  67. <textarea id="ua-input" style="width: 100%; height: 60px;"></textarea>
  68. </div>
  69. <div style="margin-bottom: 10px;">
  70. <label>屏幕宽度:</label>
  71. <input type="number" id="width-input" style="width: 100%;">
  72. </div>
  73. <div style="margin-bottom: 10px;">
  74. <label>屏幕高度:</label>
  75. <input type="number" id="height-input" style="width: 100%;">
  76. </div>
  77. <div style="margin-bottom: 10px;">
  78. <label>设备像素比:</label>
  79. <input type="number" id="scale-input" style="width: 100%;" step="0.01">
  80. </div>
  81. <button id="apply-settings" style="width: 100%;">应用设置</button>
  82. </div>
  83. </div>
  84. `;
  85.  
  86. document.body.appendChild(container);
  87. return container;
  88. }
  89.  
  90. // 初始化拖拽功能
  91. function initializeDrag(container) {
  92. const header = container.querySelector('#simulator-header');
  93. let isDragging = false;
  94. let currentX;
  95. let currentY;
  96. let initialX;
  97. let initialY;
  98.  
  99. header.addEventListener('mousedown', (e) => {
  100. isDragging = true;
  101. initialX = e.clientX - container.offsetLeft;
  102. initialY = e.clientY - container.offsetTop;
  103. });
  104.  
  105. document.addEventListener('mousemove', (e) => {
  106. if (isDragging) {
  107. currentX = e.clientX - initialX;
  108. currentY = e.clientY - initialY;
  109. container.style.left = currentX + 'px';
  110. container.style.top = currentY + 'px';
  111. }
  112. });
  113.  
  114. document.addEventListener('mouseup', () => {
  115. isDragging = false;
  116. });
  117. }
  118.  
  119. // 初始化事件监听
  120. function initializeEvents(container) {
  121. const toggleBtn = container.querySelector('#toggle-simulator');
  122. const content = container.querySelector('#simulator-content');
  123. const deviceSelect = container.querySelector('#device-select');
  124. const uaInput = container.querySelector('#ua-input');
  125. const widthInput = container.querySelector('#width-input');
  126. const heightInput = container.querySelector('#height-input');
  127. const scaleInput = container.querySelector('#scale-input');
  128. const applyBtn = container.querySelector('#apply-settings');
  129.  
  130. // 加载保存的设置
  131. const savedSettings = GM_getValue('deviceSettings', null);
  132. if (savedSettings) {
  133. try {
  134. const settings = JSON.parse(savedSettings);
  135. uaInput.value = settings.userAgent || '';
  136. widthInput.value = settings.width || '';
  137. heightInput.value = settings.height || '';
  138. scaleInput.value = settings.deviceScaleFactor || '';
  139. // 立即应用保存的设置
  140. applySettings(settings);
  141. } catch (e) {
  142. console.error('加载设置失败:', e);
  143. }
  144. }
  145.  
  146. // 切换显示/隐藏
  147. toggleBtn.addEventListener('click', () => {
  148. if (content.style.display === 'none') {
  149. content.style.display = 'block';
  150. toggleBtn.textContent = '收起';
  151. } else {
  152. content.style.display = 'none';
  153. toggleBtn.textContent = '展开';
  154. }
  155. });
  156.  
  157. // 选择预设设备
  158. deviceSelect.addEventListener('change', () => {
  159. const device = presetDevices[deviceSelect.value];
  160. if (device) {
  161. uaInput.value = device.userAgent;
  162. widthInput.value = device.width;
  163. heightInput.value = device.height;
  164. scaleInput.value = device.deviceScaleFactor;
  165. }
  166. });
  167.  
  168. // 应用设置
  169. applyBtn.addEventListener('click', () => {
  170. const settings = {
  171. userAgent: uaInput.value,
  172. width: parseInt(widthInput.value) || window.innerWidth,
  173. height: parseInt(heightInput.value) || window.innerHeight,
  174. deviceScaleFactor: parseFloat(scaleInput.value) || window.devicePixelRatio
  175. };
  176. try {
  177. GM_setValue('deviceSettings', JSON.stringify(settings));
  178. applySettings(settings);
  179. console.log('设置已保存');
  180. } catch (e) {
  181. console.error('保存设置失败:', e);
  182. }
  183. });
  184. }
  185.  
  186. // 应用设备设置
  187. function applySettings(settings) {
  188. try {
  189. // 修改 User Agent
  190. Object.defineProperty(navigator, 'userAgent', {
  191. get: function() {
  192. return settings.userAgent || navigator.userAgent;
  193. },
  194. configurable: true
  195. });
  196.  
  197. // 修改屏幕参数
  198. const screenProps = {
  199. width: settings.width,
  200. height: settings.height,
  201. availWidth: settings.width,
  202. availHeight: settings.height
  203. };
  204.  
  205. // 修改 window 属性
  206. Object.defineProperties(window, {
  207. 'innerWidth': {
  208. value: settings.width,
  209. configurable: true,
  210. writable: true
  211. },
  212. 'outerWidth': {
  213. value: settings.width,
  214. configurable: true,
  215. writable: true
  216. },
  217. 'innerHeight': {
  218. value: settings.height,
  219. configurable: true,
  220. writable: true
  221. },
  222. 'outerHeight': {
  223. value: settings.height,
  224. configurable: true,
  225. writable: true
  226. },
  227. 'devicePixelRatio': {
  228. value: settings.deviceScaleFactor,
  229. configurable: true,
  230. writable: true
  231. }
  232. });
  233.  
  234. // 修改 screen 属性
  235. Object.defineProperties(screen, {
  236. 'width': {
  237. value: screenProps.width,
  238. configurable: true,
  239. writable: true
  240. },
  241. 'height': {
  242. value: screenProps.height,
  243. configurable: true,
  244. writable: true
  245. },
  246. 'availWidth': {
  247. value: screenProps.availWidth,
  248. configurable: true,
  249. writable: true
  250. },
  251. 'availHeight': {
  252. value: screenProps.availHeight,
  253. configurable: true,
  254. writable: true
  255. }
  256. });
  257.  
  258. // 设置 viewport
  259. const viewportContent = `width=${settings.width}, initial-scale=1.0, user-scalable=yes, maximum-scale=1.0`;
  260. let viewport = document.querySelector('meta[name="viewport"]');
  261. if (viewport) {
  262. viewport.content = viewportContent;
  263. } else {
  264. viewport = document.createElement('meta');
  265. viewport.name = 'viewport';
  266. viewport.content = viewportContent;
  267. document.head.appendChild(viewport);
  268. }
  269.  
  270. // 添加强制样式
  271. const styleId = 'device-simulator-styles';
  272. let styleSheet = document.getElementById(styleId);
  273. if (!styleSheet) {
  274. styleSheet = document.createElement('style');
  275. styleSheet.id = styleId;
  276. document.head.appendChild(styleSheet);
  277. }
  278. styleSheet.textContent = `
  279. :root {
  280. --device-width: ${settings.width}px;
  281. }
  282. html, body {
  283. overflow-x: hidden !important;
  284. width: var(--device-width) !important;
  285. min-width: var(--device-width) !important;
  286. max-width: var(--device-width) !important;
  287. margin: 0 !important;
  288. padding: 0 !important;
  289. }
  290. body > * {
  291. max-width: var(--device-width) !important;
  292. }
  293.  
  294. img {
  295. max-width: 100% !important;
  296. height: auto !important;
  297. }
  298.  
  299. * {
  300. -webkit-text-size-adjust: none !important;
  301. text-size-adjust: none !important;
  302. box-sizing: border-box !important;
  303. }
  304. `;
  305.  
  306. // 触发重绘事件
  307. window.dispatchEvent(new Event('resize'));
  308. window.dispatchEvent(new Event('orientationchange'));
  309. // 强制重新计算布局
  310. document.documentElement.style.zoom = '99.99999%';
  311. setTimeout(() => {
  312. document.documentElement.style.zoom = '100%';
  313. }, 10);
  314.  
  315. } catch (e) {
  316. console.error('应用设置失败:', e);
  317. }
  318. }
  319.  
  320. // 初始化
  321. const container = createFloatingWindow();
  322. initializeDrag(container);
  323. initializeEvents(container);
  324. // 默认收起状态
  325. container.querySelector('#simulator-content').style.display = 'none';
  326. container.querySelector('#toggle-simulator').textContent = '展开';
  327. })();

QingJ © 2025

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