(function (global) { 'use strict'; /** * shell(按真机形态做 CSS 边框): * - classic Home 键(iPhone 8 / SE) * - notch 刘海(iPhone X~14 标准) * - island 灵动岛(iPhone 14 Pro 起) * - punch 居中挖孔(多数安卓) * - punchL 左上挖孔(部分三星/小米) * - teardrop 水滴(中端安卓) * - tablet 平板 * - tabletCam 平板+前摄 * - flat 无装饰框 */ var SHELL = { classic: { padX: 0.055, padY: 0.12, minPadX: 14, minPadY: 32, radius: 0.1 }, notch: { padX: 0.032, padY: 0.032, minPadX: 10, minPadY: 10, radius: 0.13 }, island: { padX: 0.028, padY: 0.028, minPadX: 10, minPadY: 10, radius: 0.14 }, punch: { padX: 0.022, padY: 0.022, minPadX: 8, minPadY: 8, radius: 0.11 }, punchL: { padX: 0.022, padY: 0.022, minPadX: 8, minPadY: 8, radius: 0.11 }, teardrop: { padX: 0.024, padY: 0.024, minPadX: 8, minPadY: 8, radius: 0.11 }, android: { padX: 0.022, padY: 0.022, minPadX: 8, minPadY: 8, radius: 0.11 }, tablet: { padX: 0.036, padY: 0.036, minPadX: 16, minPadY: 16, radius: 0.05 }, tabletCam: { padX: 0.036, padY: 0.036, minPadX: 16, minPadY: 16, radius: 0.05 }, flat: { padX: 0.018, padY: 0.018, minPadX: 6, minPadY: 6, radius: 0.08 }, }; /** 主流厂商代表性 / 较新机型(逻辑分辨率) */ var DEVICES = [ { id: 'fit', name: '适应窗口', w: 0, h: 0, frame: false, shell: 'flat', brand: '通用' }, // —— Apple iPhone —— { id: 'iphone-se', name: 'Apple iPhone SE 3', w: 375, h: 667, frame: true, shell: 'classic', brand: 'Apple' }, { id: 'iphone-8', name: 'Apple iPhone 8', w: 375, h: 667, frame: true, shell: 'classic', brand: 'Apple' }, { id: 'iphone-x', name: 'Apple iPhone X / XS', w: 375, h: 812, frame: true, shell: 'notch', brand: 'Apple' }, { id: 'iphone-xr', name: 'Apple iPhone XR / 11', w: 414, h: 896, frame: true, shell: 'notch', brand: 'Apple' }, { id: 'iphone-12', name: 'Apple iPhone 12 / 13', w: 390, h: 844, frame: true, shell: 'notch', brand: 'Apple' }, { id: 'iphone-12p', name: 'Apple iPhone 12 / 13 Pro', w: 390, h: 844, frame: true, shell: 'notch', brand: 'Apple' }, { id: 'iphone-12pm', name: 'Apple iPhone 12 / 13 Pro Max', w: 428, h: 926, frame: true, shell: 'notch', brand: 'Apple' }, { id: 'iphone-14', name: 'Apple iPhone 14', w: 390, h: 844, frame: true, shell: 'notch', brand: 'Apple' }, { id: 'iphone-14-plus', name: 'Apple iPhone 14 Plus', w: 428, h: 926, frame: true, shell: 'notch', brand: 'Apple' }, { id: 'iphone-14-pro', name: 'Apple iPhone 14 Pro', w: 393, h: 852, frame: true, shell: 'island', brand: 'Apple' }, { id: 'iphone-14-promax', name: 'Apple iPhone 14 Pro Max', w: 430, h: 932, frame: true, shell: 'island', brand: 'Apple' }, { id: 'iphone-15', name: 'Apple iPhone 15 / 15 Plus', w: 393, h: 852, frame: true, shell: 'island', brand: 'Apple' }, { id: 'iphone-15-pro', name: 'Apple iPhone 15 Pro', w: 393, h: 852, frame: true, shell: 'island', brand: 'Apple' }, { id: 'iphone-15-promax', name: 'Apple iPhone 15 Pro Max', w: 430, h: 932, frame: true, shell: 'island', brand: 'Apple' }, { id: 'iphone-16', name: 'Apple iPhone 16', w: 393, h: 852, frame: true, shell: 'island', brand: 'Apple' }, { id: 'iphone-16-plus', name: 'Apple iPhone 16 Plus', w: 430, h: 932, frame: true, shell: 'island', brand: 'Apple' }, { id: 'iphone-16-pro', name: 'Apple iPhone 16 Pro', w: 402, h: 874, frame: true, shell: 'island', brand: 'Apple' }, { id: 'iphone-16-promax', name: 'Apple iPhone 16 Pro Max', w: 440, h: 956, frame: true, shell: 'island', brand: 'Apple' }, { id: 'iphone-17', name: 'Apple iPhone 17', w: 393, h: 852, frame: true, shell: 'island', brand: 'Apple' }, { id: 'iphone-17-pro', name: 'Apple iPhone 17 Pro', w: 402, h: 874, frame: true, shell: 'island', brand: 'Apple' }, { id: 'iphone-air', name: 'Apple iPhone Air', w: 430, h: 932, frame: true, shell: 'island', brand: 'Apple' }, // —— Apple iPad —— { id: 'ipad', name: 'Apple iPad 10', w: 820, h: 1180, frame: true, shell: 'tabletCam', brand: 'Apple' }, { id: 'ipad-9', name: 'Apple iPad 9', w: 768, h: 1024, frame: true, shell: 'classic', brand: 'Apple' }, { id: 'ipad-air', name: 'Apple iPad Air', w: 820, h: 1180, frame: true, shell: 'tabletCam', brand: 'Apple' }, { id: 'ipad-pro', name: 'Apple iPad Pro 11', w: 834, h: 1194, frame: true, shell: 'tabletCam', brand: 'Apple' }, { id: 'ipad-pro13', name: 'Apple iPad Pro 13', w: 1024, h: 1366, frame: true, shell: 'tabletCam', brand: 'Apple' }, // —— Samsung 直板 —— { id: 'galaxy-s20', name: 'Samsung Galaxy S20', w: 360, h: 800, frame: true, shell: 'punch', brand: 'Samsung' }, { id: 'galaxy-s21', name: 'Samsung Galaxy S21 / S22', w: 360, h: 800, frame: true, shell: 'punch', brand: 'Samsung' }, { id: 'galaxy-s23', name: 'Samsung Galaxy S23 / S24', w: 360, h: 800, frame: true, shell: 'punch', brand: 'Samsung' }, { id: 'galaxy-s25', name: 'Samsung Galaxy S25', w: 360, h: 800, frame: true, shell: 'punch', brand: 'Samsung' }, { id: 'galaxy-s25u', name: 'Samsung Galaxy S25 Ultra', w: 412, h: 915, frame: true, shell: 'punch', brand: 'Samsung' }, { id: 'galaxy-s24u', name: 'Samsung Galaxy S24 Ultra', w: 412, h: 915, frame: true, shell: 'punch', brand: 'Samsung' }, { id: 'galaxy-note20', name: 'Samsung Galaxy Note 20', w: 412, h: 915, frame: true, shell: 'punch', brand: 'Samsung' }, { id: 'galaxy-a54', name: 'Samsung Galaxy A54', w: 360, h: 800, frame: true, shell: 'punch', brand: 'Samsung' }, // —— Google —— { id: 'pixel-5', name: 'Google Pixel 5', w: 393, h: 851, frame: true, shell: 'punch', brand: 'Google' }, { id: 'pixel-6', name: 'Google Pixel 6 / 7', w: 412, h: 915, frame: true, shell: 'punch', brand: 'Google' }, { id: 'pixel-8', name: 'Google Pixel 8 / 8 Pro', w: 412, h: 915, frame: true, shell: 'punch', brand: 'Google' }, { id: 'pixel-9', name: 'Google Pixel 9 / 9 Pro', w: 412, h: 915, frame: true, shell: 'punch', brand: 'Google' }, // —— Xiaomi / Redmi —— { id: 'mi-13', name: 'Xiaomi 小米 13 / 14', w: 393, h: 873, frame: true, shell: 'punch', brand: 'Xiaomi' }, { id: 'mi-14-pro', name: 'Xiaomi 小米 14 Pro', w: 400, h: 882, frame: true, shell: 'punch', brand: 'Xiaomi' }, { id: 'mi-15', name: 'Xiaomi 小米 15 / 15 Pro', w: 393, h: 873, frame: true, shell: 'punch', brand: 'Xiaomi' }, { id: 'redmi-note12', name: 'Xiaomi Redmi Note 12 / 13', w: 360, h: 800, frame: true, shell: 'teardrop', brand: 'Xiaomi' }, { id: 'redmi-k60', name: 'Xiaomi Redmi K60 / K70', w: 393, h: 873, frame: true, shell: 'punch', brand: 'Xiaomi' }, { id: 'redmi-k80', name: 'Xiaomi Redmi K80', w: 393, h: 873, frame: true, shell: 'punch', brand: 'Xiaomi' }, // —— Huawei / Honor —— { id: 'huawei-mate60', name: 'Huawei Mate 60 Pro', w: 400, h: 880, frame: true, shell: 'punch', brand: 'Huawei' }, { id: 'huawei-p60', name: 'Huawei P60 / nova', w: 360, h: 800, frame: true, shell: 'punch', brand: 'Huawei' }, { id: 'huawei-pura70', name: 'Huawei Pura 70', w: 400, h: 880, frame: true, shell: 'punch', brand: 'Huawei' }, { id: 'honor-magic6', name: 'Honor 荣耀 Magic6', w: 360, h: 800, frame: true, shell: 'punch', brand: 'Honor' }, { id: 'honor-magic7', name: 'Honor 荣耀 Magic7', w: 393, h: 873, frame: true, shell: 'punch', brand: 'Honor' }, // —— OPPO / OnePlus / vivo / iQOO —— { id: 'oppo-find', name: 'OPPO Find X6 / X7', w: 360, h: 800, frame: true, shell: 'punch', brand: 'OPPO' }, { id: 'oppo-find-x8', name: 'OPPO Find X8', w: 412, h: 915, frame: true, shell: 'punch', brand: 'OPPO' }, { id: 'oppo-reno', name: 'OPPO Reno 11 / 12', w: 360, h: 800, frame: true, shell: 'punch', brand: 'OPPO' }, { id: 'oneplus-12', name: 'OnePlus 一加 12', w: 450, h: 1000, frame: true, shell: 'punch', brand: 'OnePlus' }, { id: 'oneplus-13', name: 'OnePlus 一加 13', w: 450, h: 1000, frame: true, shell: 'punch', brand: 'OnePlus' }, { id: 'vivo-x100', name: 'vivo X100 / X200', w: 412, h: 915, frame: true, shell: 'punch', brand: 'vivo' }, { id: 'iqoo-12', name: 'iQOO 12 / 13', w: 393, h: 873, frame: true, shell: 'punch', brand: 'iQOO' }, // —— 折叠屏 · Samsung —— { id: 'zfold2-in', name: 'Samsung Galaxy Z Fold 2 内屏', w: 2208, h: 1768, frame: true, shell: 'tabletCam', brand: 'Samsung 折叠' }, { id: 'zfold3-in', name: 'Samsung Galaxy Z Fold 3 内屏', w: 2208, h: 1768, frame: true, shell: 'tabletCam', brand: 'Samsung 折叠' }, { id: 'zfold4-in', name: 'Samsung Galaxy Z Fold 4 内屏', w: 2176, h: 1812, frame: true, shell: 'tabletCam', brand: 'Samsung 折叠' }, { id: 'zfold5-in', name: 'Samsung Galaxy Z Fold 5 内屏', w: 2176, h: 1812, frame: true, shell: 'tabletCam', brand: 'Samsung 折叠' }, { id: 'zfold6-in', name: 'Samsung Galaxy Z Fold 6 内屏', w: 2160, h: 1856, frame: true, shell: 'tabletCam', brand: 'Samsung 折叠' }, { id: 'zfold7-in', name: 'Samsung Galaxy Z Fold 7 内屏', w: 2184, h: 1968, frame: true, shell: 'tabletCam', brand: 'Samsung 折叠' }, { id: 'zfold8-in', name: 'Samsung Galaxy Z Fold 8 内屏', w: 2184, h: 1968, frame: true, shell: 'tabletCam', brand: 'Samsung 折叠' }, { id: 'zfold3-out', name: 'Samsung Galaxy Z Fold 3 外屏', w: 2260, h: 832, frame: true, shell: 'punch', brand: 'Samsung 折叠' }, { id: 'zfold5-out', name: 'Samsung Galaxy Z Fold 5 外屏', w: 2316, h: 904, frame: true, shell: 'punch', brand: 'Samsung 折叠' }, { id: 'zfold7-out', name: 'Samsung Galaxy Z Fold 7 外屏', w: 2376, h: 968, frame: true, shell: 'punch', brand: 'Samsung 折叠' }, { id: 'zflip3-in', name: 'Samsung Galaxy Z Flip 3 内屏', w: 2640, h: 1080, frame: true, shell: 'punch', brand: 'Samsung 折叠' }, { id: 'zflip5-in', name: 'Samsung Galaxy Z Flip 5 内屏', w: 2640, h: 1080, frame: true, shell: 'punch', brand: 'Samsung 折叠' }, { id: 'zflip6-in', name: 'Samsung Galaxy Z Flip 6 内屏', w: 2640, h: 1080, frame: true, shell: 'punch', brand: 'Samsung 折叠' }, { id: 'zflip7-in', name: 'Samsung Galaxy Z Flip 7 内屏', w: 2640, h: 1080, frame: true, shell: 'punch', brand: 'Samsung 折叠' }, // —— 折叠屏 · Huawei —— { id: 'mate-x2', name: 'Huawei Mate X2 内屏', w: 2480, h: 2200, frame: true, shell: 'tabletCam', brand: 'Huawei 折叠' }, { id: 'mate-x3', name: 'Huawei Mate X3 / X5 内屏', w: 2496, h: 2224, frame: true, shell: 'tabletCam', brand: 'Huawei 折叠' }, { id: 'mate-x6', name: 'Huawei Mate X6 内屏', w: 2440, h: 2240, frame: true, shell: 'tabletCam', brand: 'Huawei 折叠' }, { id: 'mate-x7', name: 'Huawei Mate X7 / XT 内屏', w: 2440, h: 2240, frame: true, shell: 'tabletCam', brand: 'Huawei 折叠' }, { id: 'mate-x3-out', name: 'Huawei Mate X3 外屏', w: 2504, h: 1080, frame: true, shell: 'punch', brand: 'Huawei 折叠' }, { id: 'mate-x6-out', name: 'Huawei Mate X6 外屏', w: 2520, h: 1080, frame: true, shell: 'punch', brand: 'Huawei 折叠' }, { id: 'pocket2', name: 'Huawei Pocket 2 内屏', w: 2700, h: 1188, frame: true, shell: 'teardrop', brand: 'Huawei 折叠' }, { id: 'pocket3', name: 'Huawei Pocket 3 内屏', w: 2700, h: 1188, frame: true, shell: 'teardrop', brand: 'Huawei 折叠' }, // —— 折叠屏 · Xiaomi —— { id: 'mix-fold2', name: 'Xiaomi MIX Fold 2 内屏', w: 2160, h: 1914, frame: true, shell: 'tabletCam', brand: 'Xiaomi 折叠' }, { id: 'mix-fold3', name: 'Xiaomi MIX Fold 3 内屏', w: 2160, h: 1916, frame: true, shell: 'tabletCam', brand: 'Xiaomi 折叠' }, { id: 'mix-fold4', name: 'Xiaomi MIX Fold 4 内屏', w: 2488, h: 2224, frame: true, shell: 'tabletCam', brand: 'Xiaomi 折叠' }, { id: 'mix-fold5', name: 'Xiaomi MIX Fold 5 内屏', w: 2488, h: 2224, frame: true, shell: 'tabletCam', brand: 'Xiaomi 折叠' }, { id: 'mix-flip', name: 'Xiaomi MIX Flip / Flip 2 内屏', w: 2912, h: 1388, frame: true, shell: 'punch', brand: 'Xiaomi 折叠' }, // —— 折叠屏 · OPPO / OnePlus —— { id: 'find-n', name: 'OPPO Find N / N2 内屏', w: 1920, h: 1792, frame: true, shell: 'tabletCam', brand: 'OPPO 折叠' }, { id: 'find-n3', name: 'OPPO Find N3 内屏', w: 2344, h: 2156, frame: true, shell: 'tabletCam', brand: 'OPPO 折叠' }, { id: 'find-n5', name: 'OPPO Find N5 内屏', w: 2248, h: 2480, frame: true, shell: 'tabletCam', brand: 'OPPO 折叠' }, { id: 'find-n6', name: 'OPPO Find N6 内屏', w: 2248, h: 2480, frame: true, shell: 'tabletCam', brand: 'OPPO 折叠' }, { id: 'oneplus-open', name: 'OnePlus Open / Open 2 内屏', w: 2344, h: 2156, frame: true, shell: 'tabletCam', brand: 'OnePlus 折叠' }, // —— 折叠屏 · vivo —— { id: 'xfold', name: 'vivo X Fold 内屏', w: 2160, h: 1916, frame: true, shell: 'tabletCam', brand: 'vivo 折叠' }, { id: 'xfold3', name: 'vivo X Fold3 内屏', w: 2480, h: 2200, frame: true, shell: 'tabletCam', brand: 'vivo 折叠' }, { id: 'xfold5', name: 'vivo X Fold5 内屏', w: 2480, h: 2200, frame: true, shell: 'tabletCam', brand: 'vivo 折叠' }, { id: 'xfold-n5', name: 'vivo X Fold N5 内屏', w: 2248, h: 2480, frame: true, shell: 'tabletCam', brand: 'vivo 折叠' }, { id: 'xflip', name: 'vivo X Flip 内屏', w: 2520, h: 1080, frame: true, shell: 'punch', brand: 'vivo 折叠' }, // —— 折叠屏 · Honor / Google —— { id: 'magic-v', name: 'Honor Magic V 内屏', w: 2272, h: 1984, frame: true, shell: 'tabletCam', brand: 'Honor 折叠' }, { id: 'magic-v3', name: 'Honor Magic V3 内屏', w: 2344, h: 2156, frame: true, shell: 'tabletCam', brand: 'Honor 折叠' }, { id: 'magic-v4', name: 'Honor Magic V4 内屏', w: 2344, h: 2156, frame: true, shell: 'tabletCam', brand: 'Honor 折叠' }, { id: 'magic-v-flip', name: 'Honor Magic V Flip 内屏', w: 2320, h: 1080, frame: true, shell: 'punch', brand: 'Honor 折叠' }, { id: 'pixel-fold', name: 'Google Pixel Fold 内屏', w: 2208, h: 1840, frame: true, shell: 'tabletCam', brand: 'Google 折叠' }, { id: 'pixel-9-fold', name: 'Google Pixel 9 Pro Fold 内屏', w: 2076, h: 2152, frame: true, shell: 'tabletCam', brand: 'Google 折叠' }, { id: 'pixel-10-fold', name: 'Google Pixel 10 Pro Fold 内屏', w: 2076, h: 2152, frame: true, shell: 'tabletCam', brand: 'Google 折叠' }, // —— 其它常见分辨率 —— { id: 'android-360', name: '通用 Android 360×800', w: 360, h: 800, frame: true, shell: 'punch', brand: '通用' }, { id: 'android-412', name: '通用 Android 412×915', w: 412, h: 915, frame: true, shell: 'punch', brand: '通用' }, { id: 'android-720', name: '通用 720×1280', w: 720, h: 1280, frame: true, shell: 'flat', brand: '通用' }, { id: 'android-1080', name: '通用 1080×1920', w: 1080, h: 1920, frame: true, shell: 'flat', brand: '通用' }, { id: 'design', name: '通用 设计分辨率', w: 750, h: 1600, frame: true, shell: 'island', brand: '通用' }, ]; /** * 设备预览:真实机型边框 + 画布铺满屏幕容器(无 CSS transform 缩放)。 * @constructor */ function DevicePreview() { this.deviceId = 'iphone-x'; this.rotated = false; this._capsuleOn = true; this._capsuleStyle = 'douyin'; this._stage = null; this._bezel = null; this._screen = null; this._cutout = null; this._homeBar = null; this._homeBtn = null; this._scaleLabel = null; this._center = null; this._applying = false; this._notifyTimer = 0; this._lastKey = ''; this._frameW = 0; this._frameH = 0; this._ro = null; this._onWinResize = this._onWinResize.bind(this); } DevicePreview.prototype.mount = function (centerEl, contentEl) { var stage = document.createElement('div'); stage.className = 'pw-device-stage'; stage.id = 'pw-device-stage'; var bezel = document.createElement('div'); bezel.className = 'pw-device-bezel'; bezel.id = 'pw-device-bezel'; bezel.innerHTML = '
' + '' + '' + '' + '' + '