DEV Community

Cover image for Managing EmulatorJS Lifecycle in Nuxt.js with iframes
Vasiliy Grudinin
Vasiliy Grudinin

Posted on

Managing EmulatorJS Lifecycle in Nuxt.js with iframes

Hello! In this article, I’ll walk you through a solution to a tricky issue I faced while building JoystickLab.ru, a game emulation platform powered by Nuxt.js. The problem? EmulatorJS wouldn’t stop running when users navigated away from a game page. I solved it using iframes, and I’ll share the details—complete with a code example—below.

The Problem

When integrating EmulatorJS into JoystickLab.ru, I noticed something odd: after launching a game on one page and navigating to another, the emulation kept going. This caused a few headaches:

  • Game Activity Persisted: Characters moved, enemies attacked, and the game continued as if the page was still active.

  • Audio Kept Playing: Background music and sound effects echoed through the site, even on unrelated pages.

  • Processes Lingered: Rendering, input handling, and other emulation tasks stayed alive in the background.

This was a big no-no for a single-page application (SPA) like Nuxt.js, where seamless page transitions are the norm, not full browser reloads. Users expect the emulation to stop when they leave the game page—and rightfully so.

Why It Happens

The root of the issue lies in how EmulatorJS works. It leverages JavaScript, WebAssembly, and canvas to run emulations, but it doesn’t have a built-in way to halt when the page becomes inactive. In Nuxt.js, which builds on Vue.js and its lifecycle hooks (onMounted, onUnmounted), this creates a disconnect:

  • Vue components follow a lifecycle, but EmulatorJS processes aren’t tied to them.

  • When a user navigates away, the component unmounts, but the emulation keeps chugging along in the browser’s global scope.

Without a way to signal EmulatorJS to stop, it blissfully continues in the background.

The Solution: iframes to the Rescue

To tackle this, I turned to iframes to isolate the emulation and manage its lifecycle. Here’s why this works so well:

  1. Isolation: An iframe creates a separate browsing context, keeping emulation independent from the main app.

  2. Lifecycle Control: When the iframe is removed from the DOM (e.g., during navigation), all its processes stop automatically.

  3. Versatility: This approach fits neatly into Nuxt.js without needing hacks or workarounds.

How to Implement It

The implementation is straightforward:

  1. Add an <iframe> to your component.

  2. Dynamically inject HTML and scripts into the iframe to run EmulatorJS.

  3. Let the iframe’s removal (on component unmount) handle stopping the emulation.

Code Example

Here’s a simplified version in Nuxt.js:

<template>
  <div class="game-container">
    <iframe ref="iframeRef" class="game-iframe"></iframe>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue';

const iframeRef = ref(null);

const injectGameScript = () => {
  if (!iframeRef.value) return;

  const iframeDoc = iframeRef.value.contentDocument || iframeRef.value.contentWindow.document;
  iframeDoc.open();
  iframeDoc.write(`
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Game</title>
      <style>
        body { margin: 0; padding: 0; background-color: #222; }
        #game { width: 100%; height: 100%; }
      </style>
    </head>
    <body>
      <div id="game"></div>
      <script type="text/javascript">
        EJS_player = '#game';
        EJS_core = 'nes'; // Example: NES emulation
        EJS_gameUrl = '/path/to/game.rom';
        EJS_pathtodata = '/data/';
      </script>
      <script src="/data/loader.js"></script>
    </body>
    </html>
  `);
  iframeDoc.close();
};

onMounted(() => {
  injectGameScript();
});
</script>

<style scoped>
.game-container {
  width: 640px;
  height: 480px;
  max-width: 100%;
}
.game-iframe {
  width: 100%;
  height: 100%;
  border: none;
}
</style>
Enter fullscreen mode Exit fullscreen mode

Breaking It Down

  • <iframe> Setup: The iframe is added to the template and referenced with ref.

  • Script Injection: The injectGameScript function builds the iframe’s content, including EmulatorJS config and loader.

  • Lifecycle Magic: When the component unmounts (e.g., on page navigation), the iframe is removed, and the emulation stops instantly.

Why It’s Awesome

This approach delivers some solid wins:

  • Process Isolation: Emulation stays sandboxed, avoiding conflicts with the main app.

  • Clean Shutdown: Removing the iframe kills all emulation tasks—no lingering processes.

  • Platform Flexibility: It works for NES, SNES, PSX, N64 or any other system EmulatorJS supports.

Real-World Tweaks

On JoystickLab.ru, I took it further:

  • Added dynamic configs for different platforms (e.g., BIOS settings or fullscreen mode).

  • Enabled mobile support by opening emulation in a new window when needed.

  • Styled the iframe’s content for a polished look.

Wrapping Up

Using iframes is a clean, effective way to manage the EmulatorJS lifecycle in a Nuxt.js app. It keeps resources in check and ensures a smooth user experience. Got questions or ideas to make this even better? Drop a comment—I’d love to hear your thoughts!

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

Top comments (0)

👋 Kindness is contagious

If you found this post helpful, please leave a ❤️ or a friendly comment below!

Okay