Skip to content

Buenas prácticas para integrar juegos mediante iframe

Muchas plataformas (Itch.io, Newgrounds, Cinevva o tu propio sitio) integran juegos mediante iframes. Este tutorial explica cómo hacer que tu juego funcione correctamente cuando está integrado.

1) Configuración básica para la integración

Tu juego debe funcionar sin necesitar el control total de la 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) Detectar el contexto del iframe

js
const isEmbedded = window.self !== window.top

if (isEmbedded) {
  // Adjust behavior for embedded context
  hideExternalLinks()
  adjustUIForSmallSize()
}

function hideExternalLinks() {
  document.querySelectorAll('a[target="_blank"]').forEach(link => {
    link.style.display = 'none'
  })
}

3) Permitir los permisos necesarios del iframe

La página principal controla lo que puede hacer tu iframe. Estos son algunos permisos habituales:

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>

Tu juego debe gestionar correctamente la falta de permisos:

js
// Check if fullscreen is available
const canFullscreen = document.fullscreenEnabled || document.webkitFullscreenEnabled

if (!canFullscreen) {
  document.getElementById('fullscreen-btn').style.display = 'none'
}

4) Pantalla completa desde un iframe

La pantalla completa requiere el 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) {
    // Fullscreen not allowed - show message
    showMessage('La pantalla completa no está disponible cuando el juego está integrado')
  }
}

5) Comunicación con la página principal

Usa postMessage para establecer una comunicación segura entre distintos orígenes:

En tu juego:

js
// Send message to parent
function notifyParent(type, data) {
  if (window.parent !== window) {
    window.parent.postMessage({ type, data, source: 'game' }, '*')
  }
}

// Receive messages from parent
window.addEventListener('message', (event) => {
  // Validate origin if needed
  // 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
  }
})

// Notify when game is ready
window.addEventListener('load', () => {
  notifyParent('ready', { width: 800, height: 600 })
})

// Notify on game events
function onGameOver(score) {
  notifyParent('gameover', { score })
}

En la página principal:

js
const iframe = document.getElementById('game-iframe')

iframe.addEventListener('load', () => {
  // Listen for messages from game
  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)
    }
  })
})

// Send commands to game
function pauseGame() {
  iframe.contentWindow.postMessage({ type: 'pause' }, '*')
}

6) Gestionar el foco

Los iframes pueden perder el foco e impedir que funcione la entrada del teclado:

js
// Auto-focus canvas when clicked
canvas.addEventListener('click', () => {
  canvas.focus()
})

// Make canvas focusable
canvas.tabIndex = 1

// Handle focus loss
window.addEventListener('blur', () => {
  // Reset held keys
  input.left = input.right = input.up = input.down = false
  
  if (isEmbedded) {
    // Optionally pause
    // pauseGame()
  }
})

// Request focus from parent
function requestFocus() {
  notifyParent('requestFocus', {})
}

7) Tamaño adaptable del contenido integrado

Gestiona las distintas dimensiones de integración:

js
function handleResize() {
  const width = window.innerWidth
  const height = window.innerHeight
  
  // Adjust UI based on size
  if (width < 400 || height < 300) {
    enableCompactUI()
  } else {
    enableFullUI()
  }
  
  // Scale game appropriately
  resizeCanvas(width, height)
}

window.addEventListener('resize', handleResize)
handleResize()

8) Indicador de carga para contenido integrado

Muestra algo de inmediato:

js
// Inline in HTML for instant display
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>Cargando...</p>
    </div>
  </div>
`

// Remove when game is ready
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 específicos de cada plataforma

Algunas plataformas tienen sus propias API:

Itch.io:

js
// No SDK required, but can use postMessage for achievements
window.parent.postMessage({ type: 'itch-achievement', data: { id: 'first-win' }}, '*')

Newgrounds:

js
// Include Newgrounds.io SDK
const ngio = new Newgrounds.io.core('APP_ID', 'AES_KEY')
ngio.callComponent('Medal.unlock', { id: 12345 })

Cinevva:

js
// Signal game is ready
window.parent.postMessage({ type: 'cinevva:ready' }, '*')

// Signal playable state
window.parent.postMessage({ type: 'cinevva:playable' }, '*')

10) Consideraciones de seguridad

js
// Validate message origins when sensitive
window.addEventListener('message', (event) => {
  const trustedOrigins = [
    'https://itch.io',
    'https://cinevva.com',
    'https://yoursite.com'
  ]
  
  if (!trustedOrigins.includes(event.origin)) {
    return // Ignore untrusted messages
  }
  
  // Process message...
})

// Don't expose sensitive operations via postMessage
// Only allow whitelisted commands
const allowedCommands = ['pause', 'resume', 'setVolume', 'mute']

window.addEventListener('message', (event) => {
  const { type } = event.data
  if (!allowedCommands.includes(type)) return
  
  // Handle command...
})

Lista de comprobación para las pruebas

  • Pruebas locales: Usa un servidor HTTP sencillo, no file://
  • Entre distintos orígenes: Prueba el juego integrado en un iframe real
  • Permisos: Haz pruebas con un entorno aislado restringido
  • Foco: Prueba el teclado después de hacer clic fuera del iframe
  • Cambio de tamaño: Prueba distintas dimensiones de integración
  • Dispositivos móviles: Prueba los controles táctiles en un contexto integrado
html
<!-- Test embed page -->
<!DOCTYPE html>
<html>
<body style="background: #333; padding: 20px;">
  <h1 style="color: #fff;">Prueba de integración</h1>
  <iframe 
    src="http://localhost:8000" 
    width="800" 
    height="600"
    allow="fullscreen; autoplay; gamepad"
  ></iframe>
</body>
</html>

Contenido relacionado

Recursos externos