游戏 iframe 嵌入的最佳实践
很多平台(Itch.io、Newgrounds、Cinevva、你自己的站点)通过 iframe 嵌入游戏。这篇教程讲怎么让游戏在被嵌入时也能好好工作。
1)基础可嵌入设置
你的游戏要在不需要完整页面控制权的前提下也能跑起来:
html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
* { margin: 0; padding: 0; }
html, body { width: 100%; height: 100%; overflow: hidden; }
canvas { display: block; width: 100%; height: 100%; }
</style>
</head>
<body>
<canvas id="game"></canvas>
<script src="game.js"></script>
</body>
</html>2)检测是否在 iframe 中
js
const isEmbedded = window.self !== window.top
if (isEmbedded) {
// 针对嵌入场景调整行为
hideExternalLinks()
adjustUIForSmallSize()
}
function hideExternalLinks() {
document.querySelectorAll('a[target="_blank"]').forEach(link => {
link.style.display = 'none'
})
}3)允许必要的 iframe 权限
父页面控制 iframe 能做什么。常见权限:
html
<iframe
src="https://game.example.com"
allow="fullscreen; autoplay; gamepad; pointer-lock"
sandbox="allow-scripts allow-same-origin allow-pointer-lock allow-popups"
></iframe>你的游戏应该优雅处理缺失的权限:
js
// 检查是否支持全屏
const canFullscreen = document.fullscreenEnabled || document.webkitFullscreenEnabled
if (!canFullscreen) {
document.getElementById('fullscreen-btn').style.display = 'none'
}4)在 iframe 中进入全屏
全屏需要 allow="fullscreen" 属性:
js
async function requestFullscreen() {
const elem = document.documentElement
try {
if (elem.requestFullscreen) {
await elem.requestFullscreen()
} else if (elem.webkitRequestFullscreen) {
await elem.webkitRequestFullscreen()
}
} catch (err) {
// 不允许全屏 —— 展示提示
showMessage('Fullscreen not available when embedded')
}
}5)与父页面通信
用 postMessage 做安全的跨域通信:
在你的游戏里:
js
// 发消息给父页面
function notifyParent(type, data) {
if (window.parent !== window) {
window.parent.postMessage({ type, data, source: 'game' }, '*')
}
}
// 接收父页面消息
window.addEventListener('message', (event) => {
// 必要时校验来源
// if (event.origin !== 'https://trusted-host.com') return
const { type, data } = event.data
switch (type) {
case 'pause':
pauseGame()
break
case 'resume':
resumeGame()
break
case 'setVolume':
setVolume(data.volume)
break
}
})
// 游戏就绪时通知
window.addEventListener('load', () => {
notifyParent('ready', { width: 800, height: 600 })
})
// 游戏事件通知
function onGameOver(score) {
notifyParent('gameover', { score })
}在父页面:
js
const iframe = document.getElementById('game-iframe')
iframe.addEventListener('load', () => {
// 监听来自游戏的消息
window.addEventListener('message', (event) => {
if (event.source !== iframe.contentWindow) return
if (event.data.source !== 'game') return
const { type, data } = event.data
if (type === 'ready') {
console.log('Game ready:', data)
}
if (type === 'gameover') {
showScoreModal(data.score)
}
})
})
// 向游戏发命令
function pauseGame() {
iframe.contentWindow.postMessage({ type: 'pause' }, '*')
}6)处理焦点
iframe 可能丢失焦点,导致键盘输入失效:
js
// 点击时自动聚焦到 canvas
canvas.addEventListener('click', () => {
canvas.focus()
})
// 让 canvas 可聚焦
canvas.tabIndex = 1
// 处理焦点丢失
window.addEventListener('blur', () => {
// 重置按住的键
input.left = input.right = input.up = input.down = false
if (isEmbedded) {
// 可选:暂停
// pauseGame()
}
})
// 向父页面请求焦点
function requestFocus() {
notifyParent('requestFocus', {})
}7)响应嵌入尺寸
处理各种嵌入尺寸:
js
function handleResize() {
const width = window.innerWidth
const height = window.innerHeight
// 根据尺寸调整 UI
if (width < 400 || height < 300) {
enableCompactUI()
} else {
enableFullUI()
}
// 适当缩放游戏
resizeCanvas(width, height)
}
window.addEventListener('resize', handleResize)
handleResize()8)嵌入页面的加载提示
立刻展示点东西:
js
// 直接写在 HTML 里以便瞬间显示
const loadingHTML = `
<div id="loading" style="
position: fixed;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background: #1a1a2e;
color: #fff;
font-family: sans-serif;
">
<div>
<div class="spinner"></div>
<p>Loading...</p>
</div>
</div>
`
// 游戏就绪后移除
function hideLoading() {
const loading = document.getElementById('loading')
if (loading) {
loading.style.opacity = '0'
loading.style.transition = 'opacity 0.3s'
setTimeout(() => loading.remove(), 300)
}
}9)平台特有的 SDK
一些平台有自己的 API:
Itch.io:
js
// 不需要 SDK,但可以用 postMessage 处理成就
window.parent.postMessage({ type: 'itch-achievement', data: { id: 'first-win' }}, '*')Newgrounds:
js
// 引入 Newgrounds.io SDK
const ngio = new Newgrounds.io.core('APP_ID', 'AES_KEY')
ngio.callComponent('Medal.unlock', { id: 12345 })Cinevva:
js
// 信号:游戏就绪
window.parent.postMessage({ type: 'cinevva:ready' }, '*')
// 信号:可玩状态
window.parent.postMessage({ type: 'cinevva:playable' }, '*')10)安全考量
js
// 涉及敏感操作时校验消息来源
window.addEventListener('message', (event) => {
const trustedOrigins = [
'https://itch.io',
'https://cinevva.com',
'https://yoursite.com'
]
if (!trustedOrigins.includes(event.origin)) {
return // 忽略不可信消息
}
// 处理消息...
})
// 不要通过 postMessage 暴露敏感操作
// 只允许白名单中的命令
const allowedCommands = ['pause', 'resume', 'setVolume', 'mute']
window.addEventListener('message', (event) => {
const { type } = event.data
if (!allowedCommands.includes(type)) return
// 处理命令...
})测试清单
- 本地测试:用一个简单 HTTP 服务器,不要用
file:// - 跨域:用真正的 iframe 嵌入来测
- 权限:在受限 sandbox 下测
- 焦点:点击 iframe 外区域后再测键盘
- resize:在不同嵌入尺寸下测
- 移动端:在嵌入场景下测触摸
html
<!-- 嵌入测试页 -->
<!DOCTYPE html>
<html>
<body style="background: #333; padding: 20px;">
<h1 style="color: #fff;">Embed Test</h1>
<iframe
src="http://localhost:8000"
width="800"
height="600"
allow="fullscreen; autoplay; gamepad"
></iframe>
</body>
</html>相关阅读
- 发布一个快速加载的网页游戏
- 移动端友好的网页游戏
- 给创作者
- COOP/COEP 与 SharedArrayBuffer —— 影响 iframe 嵌入的跨域响应头
- 如何在 itch.io 发布你的游戏 —— itch.io 原生通过 iframe 嵌入浏览器游戏
外部资源
- MDN: iframe element —— iframe 属性的完整参考
- MDN: Permissions Policy —— 控制 iframe 可用功能
- MDN: postMessage API —— iframe 与父页面之间的通信