Boas práticas para incorporar jogos com iframe
Muitas plataformas (Itch.io, Newgrounds, Cinevva e seu próprio site) incorporam jogos em iframes. Este tutorial mostra como fazer seu jogo funcionar bem quando incorporado.
1) Configuração básica para incorporação
Seu jogo deve funcionar sem exigir controle total da página:
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) Detectando o contexto de iframe
js
const isEmbedded = window.self !== window.top
if (isEmbedded) {
// Ajuste o comportamento para o contexto incorporado
hideExternalLinks()
adjustUIForSmallSize()
}
function hideExternalLinks() {
document.querySelectorAll('a[target="_blank"]').forEach(link => {
link.style.display = 'none'
})
}3) Permitindo as permissões necessárias no iframe
A página principal controla o que seu iframe pode fazer. Permissões comuns:
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>Seu jogo deve lidar de forma adequada com permissões ausentes:
js
// Verifique se a tela cheia está disponível
const canFullscreen = document.fullscreenEnabled || document.webkitFullscreenEnabled
if (!canFullscreen) {
document.getElementById('fullscreen-btn').style.display = 'none'
}4) Tela cheia a partir do iframe
A tela cheia exige o atributo 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) {
// Tela cheia não permitida — exiba uma mensagem
showMessage('Tela cheia indisponível quando o jogo está incorporado')
}
}5) Comunicação com a página principal
Use postMessage para uma comunicação segura entre origens:
No seu jogo:
js
// Envie uma mensagem à página principal
function notifyParent(type, data) {
if (window.parent !== window) {
window.parent.postMessage({ type, data, source: 'game' }, '*')
}
}
// Receba mensagens da página principal
window.addEventListener('message', (event) => {
// Valide a origem, se necessário
// 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
}
})
// Avise quando o jogo estiver pronto
window.addEventListener('load', () => {
notifyParent('ready', { width: 800, height: 600 })
})
// Avise sobre eventos do jogo
function onGameOver(score) {
notifyParent('gameover', { score })
}Na página principal:
js
const iframe = document.getElementById('game-iframe')
iframe.addEventListener('load', () => {
// Aguarde mensagens do jogo
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('Jogo pronto:', data)
}
if (type === 'gameover') {
showScoreModal(data.score)
}
})
})
// Envie comandos ao jogo
function pauseGame() {
iframe.contentWindow.postMessage({ type: 'pause' }, '*')
}6) Gerenciando o foco
Iframes podem perder o foco, interrompendo a entrada pelo teclado:
js
// Coloque o canvas em foco automaticamente ao receber um clique
canvas.addEventListener('click', () => {
canvas.focus()
})
// Permita que o canvas receba foco
canvas.tabIndex = 1
// Gerencie a perda de foco
window.addEventListener('blur', () => {
// Redefina as teclas pressionadas
input.left = input.right = input.up = input.down = false
if (isEmbedded) {
// Pause opcionalmente
// pauseGame()
}
})
// Solicite foco à página principal
function requestFocus() {
notifyParent('requestFocus', {})
}7) Tamanho responsivo da incorporação
Adapte-se a diferentes dimensões de incorporação:
js
function handleResize() {
const width = window.innerWidth
const height = window.innerHeight
// Ajuste a interface com base no tamanho
if (width < 400 || height < 300) {
enableCompactUI()
} else {
enableFullUI()
}
// Redimensione o jogo adequadamente
resizeCanvas(width, height)
}
window.addEventListener('resize', handleResize)
handleResize()8) Indicador de carregamento para incorporações
Exiba algo imediatamente:
js
// Inclua diretamente no HTML para exibição instantânea
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>Carregando...</p>
</div>
</div>
`
// Remova quando o jogo estiver pronto
function hideLoading() {
const loading = document.getElementById('loading')
if (loading) {
loading.style.opacity = '0'
loading.style.transition = 'opacity 0.3s'
setTimeout(() => loading.remove(), 300)
}
}9) SDKs específicos das plataformas
Algumas plataformas têm APIs próprias:
Itch.io:
js
// Nenhum SDK é necessário, mas você pode usar postMessage para conquistas
window.parent.postMessage({ type: 'itch-achievement', data: { id: 'first-win' }}, '*')Newgrounds:
js
// Inclua o SDK Newgrounds.io
const ngio = new Newgrounds.io.core('APP_ID', 'AES_KEY')
ngio.callComponent('Medal.unlock', { id: 12345 })Cinevva:
js
// Sinalize que o jogo está pronto
window.parent.postMessage({ type: 'cinevva:ready' }, '*')
// Sinalize o estado jogável
window.parent.postMessage({ type: 'cinevva:playable' }, '*')10) Considerações de segurança
js
// Valide as origens das mensagens em operações sensíveis
window.addEventListener('message', (event) => {
const trustedOrigins = [
'https://itch.io',
'https://cinevva.com',
'https://yoursite.com'
]
if (!trustedOrigins.includes(event.origin)) {
return // Ignore mensagens não confiáveis
}
// Processe a mensagem...
})
// Não exponha operações sensíveis via postMessage
// Permita apenas comandos incluídos na lista de permissões
const allowedCommands = ['pause', 'resume', 'setVolume', 'mute']
window.addEventListener('message', (event) => {
const { type } = event.data
if (!allowedCommands.includes(type)) return
// Processe o comando...
})Checklist de testes
- Teste local: use um servidor HTTP simples, não
file:// - Entre origens: teste com o jogo realmente incorporado em um iframe
- Permissões: teste com um sandbox restrito
- Foco: teste o teclado depois de clicar fora do iframe
- Redimensionamento: teste com vários tamanhos de incorporação
- Dispositivos móveis: teste os controles por toque no contexto incorporado
html
<!-- Página de teste da incorporação -->
<!DOCTYPE html>
<html>
<body style="background: #333; padding: 20px;">
<h1 style="color: #fff;">Teste de incorporação</h1>
<iframe
src="http://localhost:8000"
width="800"
height="600"
allow="fullscreen; autoplay; gamepad"
></iframe>
</body>
</html>Conteúdo relacionado
- Publique um jogo web que carrega rapidamente
- Jogos web otimizados para dispositivos móveis
- Para criadores
- COOP/COEP e SharedArrayBuffer — cabeçalhos entre origens que afetam a incorporação em iframes
- Como lançar seu jogo no itch.io — o itch.io incorpora nativamente jogos de navegador em iframes
Recursos externos
- MDN: elemento iframe — referência completa dos atributos de iframe
- MDN: Política de Permissões — controle dos recursos que os iframes podem usar
- MDN: API postMessage — comunicação entre o iframe e a página principal