Cocos Creator 的 Web 屏幕适配链路:DPR 钳制、安全区域 CSS 变量与旋转帧的源码拆解

发布时间:2026/9/17 20:35:19
Cocos Creator 的 Web 屏幕适配链路:DPR 钳制、安全区域 CSS 变量与旋转帧的源码拆解 Cocos Creator 的 Web 屏幕适配链路DPR 钳制、安全区域 CSS 变量与旋转帧的源码拆解【免费下载链接】cocos-engineCocos simplifies game creation and distribution with Cocos Creator, a free, open-source, cross-platform game engine. Empowering millions of developers to create high-performance, engaging 2D/3D games and instant web entertainment.项目地址: https://gitcode.com/GitHub_Trending/co/cocos-engine在 iPhone 13 Pro 上跑一份 720×1280 的画布把手机横过来 90 度游戏框换了宽高window.resize却没有触发刘海屏的安全内边距只有顶部有引擎却把上下补齐成对称矩形。Cocos Creator 的屏幕适配链路就埋着这样的取舍全在 pal/screen-adapter/web/screen-adapter.ts 一个文件里。下面拆开它的 DPR 钳制、安全区域变量和旋转帧三段实现看每个决定背后放弃了什么。核心链路在哪整条 Web 端适配只落在一个类上ScreenAdapter在 pal/screen-adapter/web/screen-adapter.ts 里接管窗口事件、全屏状态和方向变化输出是给#GameDiv和#Cocos3dGameContainer这两个 DOM 节点写样式。适配容器的核心逻辑本质上就是在算一个谁先撑满的比值const designedResolution legacyCC.view.getDesignResolutionSize() as Size; const frameW frame.clientWidth; const frameH frame.clientHeight; const designW designedResolution.width; const designH designedResolution.height; const scaleX frameW / designW; const scaleY frameH / designH; // ... 省略 3 行 if (scaleX scaleY) { containerW frameW; containerH designH * scaleX; } else { containerW designW * scaleY; containerH frameH; }横向或纵向先撑满窗口另一边就按同一比例回缩——这是等比缩放不出变形的全部秘密后面所有设计决策都是围绕它展开的。设计决策拆解DPR 上限为什么卡在 2 而不是直接透传现象3x 设备iPhone 13 Pro 的window.devicePixelRatio是 3上screen.devicePixelRatio返回 2screen.windowSize比真实物理像素小三分之一UI 文字和系统界面比会显得略微发虚。源码指向pal/screen-adapter/web/screen-adapter.ts 第 100~103 行的 getter。public get devicePixelRatio (): number { // TODO: remove the down sampling operation in DPR after supporting resolutionScale return Math.min(window.devicePixelRatio ?? 1, 2); }?? 1是给老浏览器的兜底真正的决定是Math.min这个上限canvas 缓冲区的物理像素数等于 CSS 像素乘 DPR3x 全屏渲染意味着过绘制面积接近翻倍中端机直接掉帧所以引擎统一降采样到 2。代价是写死在 getter 里没有出口——cocos/core/platform/screen.ts 里resolutionScale的 setter 整段被注释掉第 103~108 行项目侧目前无法调回 3x高 DPI 设备永远是用清晰度换帧率的方案 A。安全区域为什么读 CSS 变量而不是 JS API现象刘海屏上safeAreaEdge能拿到顶部 inset但一旦构建模板没配置viewport-fitcover四个方向静默全部为 0SafeArea组件形同虚设且没有任何报错。源码指向读取端在 pal/screen-adapter/web/screen-adapter.ts 第 152~165 行变量定义在 templates/web-mobile/style.css 第 56~61 行的:root。:root { --safe-top: env(safe-area-inset-top); --safe-right: env(safe-area-inset-right); --safe-bottom: env(safe-area-inset-bottom); --safe-left: env(safe-area-inset-left); }public get safeAreaEdge (): SafeAreaEdge { const dpr this.devicePixelRatio; const _top parseInt(getComputedStyle(document.documentElement).getPropertyValue(--safe-top) || 0) * dpr; // ... 省略 3 行bottom/left/right 同 top 的处理 return { top: _top, bottom: _bottom, left: _left, right: _right }; }走 CSS 变量中转是因为env(safe-area-inset-*)是浏览器在样式层计算的JS 在非 CSS 上下文里根本没有 API 能读到它——CSS 变量是当前唯一跨浏览器的通道。乘dpr是把 CSS 像素换算成物理像素与windowSize的单位对齐|| 0防的是变量未定义时parseInt出NaN。取舍在于引擎放弃了一个标准 JS API的体面换取了 iOS/Android/微信容器都能工作的兼容面代价是整条链路完全寄生在构建模板上——模板被改坏引擎只会安静地返回 0。横屏游戏在竖屏手机上怎么转正现象配置为竖屏的项目被用户横着拿手机画面不会被拉宽而是整个游戏框直立着占住屏幕中央两侧留黑边就像外层 div 被写了rotate(90deg)。源码指向判定在_updateFrameState第 595~603 行执行在_resizeFrame第 538~545 行同一文件。// 判定配置方向与物理方向冲突且只针对移动端 this.isFrameRotated systemInfo.isMobile ((isBrowserLandscape orientation Orientation.PORTRAIT) || (!isBrowserLandscape orientation Orientation.LANDSCAPE)); // 执行frame 宽高互换、旋转 90 度margin 把转出去的框挪回视口 // ... 省略 2 行-webkit- 前缀的同款 transform if (this.isFrameRotated) { this._gameFrame.style.transform rotate(90deg); this._gameFrame.style.transformOrigin 0px 0px 0px; this._gameFrame.style.margin 0 0 0 ${winWidth}px; this._gameFrame.style.width ${winHeight}px; this._gameFrame.style.height ${winWidth}px; }方向位定义在 pal/screen-adapter/enum-type/orientation.ts 第 25~32 行用位运算让AUTO 15能覆盖所有方向组合orientation比较天然支持横屏左右皆可这类配置。选 DOM transform 而不是重排 canvas是因为旋转发生在合成层引擎侧零拷贝成本2D/3D 场景内容完全无感。但这个技巧在移动端全屏下会失效——_getFullscreenTarget第 569~571 行直接返回document.body做全屏目标注释写明 the transform of game frame doesnt work when its on fullscreen于是旋转帧逻辑被整条绕过这就是下面第一个坑的来源。落到项目里 顶部栏/底部栏这种必须避开刘海和 Home 指示条的组件不要自己读env()直接消费引擎已经换算好的物理像素数据import { _decorator, Component, screen, sys, view, UITransform } from cc; const { ccclass } _decorator; ccclass(SafeTopBar) export class SafeTopBar extends Component { onLoad (): void { // 引擎的 window-resize 已在方向变化防抖后才派发不必自己再监听 matchMedia screen.on(window-resize, this.refresh, this); screen.on(orientation-change, this.refresh, this); this.refresh(); } onDestroy (): void { screen.off(window-resize, this.refresh, this); screen.off(orientation-change, this.refresh, this); } refresh (): void { const visible view.getVisibleSize(); // symmetricfalse 保留真实上下 inset只算顶部偏移 const safeRect sys.getSafeAreaRect(false); const topInset Math.max(visible.height - safeRect.y - safeRect.height, 0); const ui this.node.getComponent(UITransform)!; ui.setContentSize(ui.width, ui.height - topInset); } }适用边界H5 里顶栏、血条、返回键这类贴边组件可以直接用微信/字节小游戏容器里 CSS 变量不一定被填充小游戏走 pal/screen-adapter/minigame/screen-adapter.ts 的独立实现贴边组件建议改用平台自带的安全区 API 双通道取值。多分辨率适配的另一半在设计分辨率映射上一行即可声明策略import { view, ResolutionPolicy } from cc; // 设计分辨率 720×1280按最小边缩放保证内容完整显示SHOW_ALL view.setDesignResolutionSize(720, 1280, ResolutionPolicy.SHOW_ALL);适用边界SHOW_ALL在任何宽高比下不裁切内容是默认安全选项但在 18:9 长屏上左右黑边会明显偏宽想撑满画面应切到FIXED_HEIGHT再手动处理左右超出区域的 UI 裁切——策略清单和各自实现见 cocos/ui/view.ts 第 94~99 行。边界与翻车记录⚠️旋转后 UI 刷新慢半拍前 200ms 布局用的还是旧尺寸。根因方向变化回调被EVENT_TIMEOUT防抖Web 200ms、编辑器内 5ms第 41 行与第 431~433 行pal/screen-adapter/web/screen-adapter.ts因为orientationchange事件触发时window.innerWidth/innerHeight尚未更新。处理布局逻辑挂在引擎的orientation-change/window-resize上别自己再写matchMedia双份监听否则会用旧尺寸先刷一次错版。⚠️进全屏后竖屏游戏在横拿的手机上不再转正画面被拉宽。根因移动端全屏时 frame 的 transform 不生效_getFullscreenTarget回退到document.body第 569~571 行旋转帧那套样式写到了不会被全屏命中的节点上。处理全屏场景下别依赖isFrameRotated一律以sys.windowSize为准重算布局产品上要么限制全屏入口只出现在同方向设备要么接受全屏内画面拉伸。⚠️刘海只在顶部布局却把底部也顶下来一截。根因sys.getSafeAreaRect默认symmetrictrue会把上下或左右取大值补齐成对称矩形cocos/core/platform/sys.ts 第 374~386 行。这是有意为之的保守策略——横竖切换后刘海位置会变对称值最不容易把内容顶进裁切区。处理确实需要真实非对称 inset 时传getSafeAreaRect(false)或直接读screenAdapter.safeAreaEdge原始值。往哪走想看设计分辨率到可见区的完整映射EXACT_FIT/SHOW_ALL/FIXED_HEIGHT五种策略的实现差异入口在 cocos/ui/view.ts 的ResolutionPolicy族想看引擎官方怎么把安全区域数据喂给布局系统SafeArea组件cocos/ui/safe-area.ts演示了getSafeAreaRect与Widget的对齐联动。如果你手上某个具体机型出现了适配问题欢迎在项目 issue 里贴复现步骤机型、浏览器/容器版本、物理朝向三段信息齐了就能直接定位到上面哪一环。【免费下载链接】cocos-engineCocos simplifies game creation and distribution with Cocos Creator, a free, open-source, cross-platform game engine. Empowering millions of developers to create high-performance, engaging 2D/3D games and instant web entertainment.项目地址: https://gitcode.com/GitHub_Trending/co/cocos-engine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询