Решение проблемы управления эмуляцией в EmulatorJS с помощью iframe в Nuxt.js

Решение проблемы управления эмуляцией в EmulatorJS с помощью iframe в Nuxt.js
Решение проблемы управления эмуляцией в EmulatorJS с помощью iframe в Nuxt.js

Привет! Меня зовут Василий, я Frontend-разработчик с опытом работы на Vue.js и Nuxt.js, основатель игрового сервиса JoystickLab. Эта статья посвящена решению технической задачи, возникшей при работе над проектом JoystickLab.ru — платформой для эмуляции игр, реализованной на Nuxt.js. В процессе интеграции библиотеки EmulatorJS была выявлена проблема: эмуляция не останавливалась при переходе между страницами. Здесь описан способ, как удалось решить данную проблему, с использованием iframe, а также приведён пример кода.

Постановка проблемы

При использовании EmulatorJS на сайте JoystickLab.ru возникала следующая ситуация: после запуска игры на одной странице и последующего перехода на другую страницу эмуляция не останавливалась. Это проявлялось следующим образом:

  • Игра оставалась активной: персонажи продолжали двигаться, враги атаковали, а игровой процесс шёл своим чередом, несмотря на то, что страница с игрой больше не была открыта.

  • Звук не отключался: музыка и звуковые эффекты из игры продолжали играть в фоновом режиме.

  • Процессы эмуляции не прерывались: рендеринг графики, обработка ввода и другие связанные с эмуляцией задачи оставались активными.

Такое поведение создавало впечатление, что игра "живёт своей жизнью" даже после того, как пользователь покинул страницу. Это особенно нежелательно в одностраничных приложениях (Single Page Application (SPA)), таких как Nuxt.js, где переходы между страницами не сопровождаются полной перезагрузкой браузера, и пользователь ожидает, что эмуляция завершится при уходе со страницы игры.

Причина проблемы

Проблема связана с особенностями работы EmulatorJS. Библиотека запускает эмуляцию через JavaScript с использованием WebAssembly и canvas, но не имеет встроенного механизма для её остановки при уходе со страницы. В контексте Nuxt.js, который использует компоненты Vue.js с их жизненным циклом (onMounted, onUnmounted), это приводит к следующим сложностям:

  • Компоненты управляются через жизненный цикл, но эмуляция не привязана к этим хукам напрямую.

  • При навигации компонент уничтожается, однако процессы эмуляции остаются активными в глобальной области браузера.

Таким образом, EmulatorJS не отслеживает, что страница больше не активна, и продолжает работать в фоне.

Подход к решению

Для решения задачи был выбран метод с использованием iframe. Этот элемент позволяет изолировать эмуляцию и управлять её жизненным циклом. Основные преимущества такого подхода:

  1. Изоляция: iframe создаёт отдельный контекст выполнения, отделяя эмуляцию от основного приложения.

  2. Управление: при удалении iframe из DOM (например, при уходе со страницы) все процессы внутри него завершаются.

  3. Универсальность: метод работает с Nuxt.js без необходимости сложных изменений.

Реализация

Решение было реализовано следующим образом:

  1. В компонент добавлен элемент <iframe>.

  2. В iframe динамически внедряется HTML-код и скрипты для работы EmulatorJS.

  3. При уничтожении компонента iframe удаляется, что останавливает эмуляцию.

Пример кода

Вот упрощённый пример реализации в 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="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Игра</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'; // Например, для NES 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>

Как это работает

  • <iframe>: Добавляется в шаблон и управляется через ref.

  • Инъекция кода: Функция injectGameScript создаёт структуру внутри iframe, включая настройки EmulatorJS.

  • Жизненный цикл: При уходе со страницы компонент уничтожается, iframe удаляется, и эмуляция останавливается.

Преимущества

Этот подход показал свою эффективность:

  • Изоляция процессов: Эмуляция не влияет на основное приложение.

  • Автоматическая остановка: Удаление iframe завершает все связанные процессы.

  • Гибкость: Метод подходит для разных платформ эмуляции (NES, SNES, PSX, N64 и т.д.).

Интеграция в проект

В JoystickLab.ru решение дополнено:

  • Динамическими настройками для разных платформ (например, BIOS или fullscreen-режим).
  • Поддержкой мобильных устройств через открытие эмуляции в новом окне.

  • Стилизацией интерфейса внутри iframe.

Заключение

Использование iframe позволило решить проблему управления эмуляцией в EmulatorJS при работе с Nuxt.js. Такой подход обеспечивает контроль над жизненным циклом ресурсов и удобство для пользователей. Если у вас есть вопросы или идеи по улучшению, буду рад обсудить!

Начать дискуссию