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:
Isolation: An iframe creates a separate browsing context, keeping emulation independent from the main app.
Lifecycle Control: When the iframe is removed from the DOM (e.g., during navigation), all its processes stop automatically.
Versatility: This approach fits neatly into Nuxt.js without needing hacks or workarounds.
How to Implement It
The implementation is straightforward:
Add an
<iframe>
to your component.Dynamically inject HTML and scripts into the iframe to run EmulatorJS.
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>
Breaking It Down
<iframe>
Setup: The iframe is added to the template and referenced withref
.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!
Top comments (0)