Перейти к содержимому

Освоение межоконного взаимодействия в JavaScript

Межоконное взаимодействие в JavaScript необходимо веб-разработчикам, которым нужно управлять обменом данными между различными контекстами просмотра. Это подробное руководство глубоко раскрывает нюансы межоконного взаимодействия, предоставляя детальные объяснения и практические примеры. Наша цель — вооружить вас знаниями для эффективного использования этой продвинутой функции JavaScript.

Понимание межоконного взаимодействия

Межоконное взаимодействие относится к процессу обмена данными между различными окнами или фреймами. Это может включать взаимодействие между родительским окном и дочерним окном (всплывающим окном) или между несколькими фреймами в рамках одного родительского окна.

Сценарии, требующие межоконного взаимодействия

  1. Всплывающие окна (Popups): Когда новое окно открывается с помощью window.open(), становится необходимым взаимодействие между родительским и дочерним окнами.
  2. Взаимодействие с iframe: Часто веб-приложения используют iframe для встраивания контента. Эффективное взаимодействие между родительским окном и iframe имеет решающее значение.
  3. Вкладки: Иногда требуется обмен данными между различными вкладками.

Методы межоконного взаимодействия

Использование window.postMessage()

Метод window.postMessage() обеспечивает безопасную отправку данных между различными окнами или фреймами.


html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Cross-Window Communication</title>
  <style>
    #childIframe, #childPopup {
      width: 100%;
      height: 200px;
      border: 1px solid black;
      margin-top: 20px;
    }
  </style>
</head>
<body>
  <h1>Cross-Window Communication Examples</h1>

  <!-- Button to Open Popup -->
  <button id="openPopup">Open Popup</button>
  <div id="parentPopupDisplay"></div>

  <!-- Iframe -->
  <iframe id="childIframe" srcdoc="
    <!DOCTYPE html>
    <html lang='en'>
    <head>
      <meta charset='UTF-8' />
      <title>Child Iframe</title>
    </head>
    <body>
      <div id='childIframeDisplay'></div>
      <script>
        window.addEventListener('message', (event) => {
          // Note: For cross-origin contexts, replace window.location.origin with the hardcoded parent origin.
          if (event.origin !== window.location.origin) return;
          document.getElementById('childIframeDisplay').innerText = 'Message from parent: ' + event.data;
          event.source.postMessage('Hello, Parent Window!', event.origin);
        });
      </script>
    </body>
    </html>
  "></iframe>
  <div id="iframeDisplay"></div>

  <!-- Scripts for Parent Window -->
  <script>
    // Handle Popup Communication
    document.getElementById('openPopup').addEventListener('click', () => {
      const popup = window.open('', 'popupWindow', 'width=600,height=400');
      popup.document.write(`
        <!DOCTYPE html>
        <html lang='en'>
        <head>
          <meta charset='UTF-8' />
          <title>Popup Window</title>
        </head>
        <body>
          <div id='popupDisplay'></div>
          <script>
            window.addEventListener('message', (event) => {
              // Note: For cross-origin contexts, replace window.location.origin with the hardcoded parent origin.
              if (event.origin !== window.location.origin) return;
              document.getElementById('popupDisplay').innerText = 'Message from parent: ' + event.data;
              event.source.postMessage('Hello, Parent Window!', event.origin);
            });
          <\/script>
        </body>
        </html>
      `);
      setTimeout(() => {
        // For cross-origin, replace '*' with the exact target origin (e.g., 'https://example.com')
        popup.postMessage('Hello from parent!', '*');
      }, 1000);
    });

    // Handle Iframe Communication
    const iframe = document.getElementById('childIframe');

    iframe.onload = () => {
      iframe.contentWindow.postMessage('Hello from parent window!', '*');
    };

    window.addEventListener('message', (event) => {
      if (event.origin !== window.location.origin) return;
      if (event.source === iframe.contentWindow) {
        document.getElementById('iframeDisplay').innerText = 'Message from iframe: ' + event.data;
      } else {
        document.getElementById('parentPopupDisplay').innerText = 'Message from popup: ' + event.data;
      }
    });
  </script>
</body>
</html>

В этом объединенном примере родительское окно открывает всплывающее окно и встраивает iframe. И всплывающее окно, и iframe могут взаимодействовать с родительским окном с помощью postMessage(). Сообщения отображаются в соответствующих элементах div для наглядности.

note

Хотя document.write() подходит для простых демонстраций, современные рекомендации рекомендуют использовать DOMParser или URL-адреса Blob для безопасной вставки контента во всплывающие окна.

Получение ссылок на окна

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


html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Direct Manipulation Example</title>
  <style>
    #childIframe {
      width: 100%;
      height: 200px;
      border: 1px solid black;
      margin-top: 20px;
    }
  </style>
</head>
<body>
  <h1>Direct Manipulation Example</h1>

  <!-- Button to Open Popup -->
  <button id="openChild">Open Child Window</button>
  <div id="parentChildDisplay"></div>

  <!-- Iframe -->
  <iframe id="childIframe" srcdoc="
    <!DOCTYPE html>
    <html lang='en'>
    <head>
      <meta charset='UTF-8' />
      <title>Child Iframe</title>
    </head>
    <body>
      <div id='childIframeContent'>Initial Content</div>
    </body>
    </html>
  "></iframe>

  <!-- Scripts for Parent Window -->
  <script>
    document.getElementById('openChild').addEventListener('click', () => {
      const childWindow = window.open('', 'childWindow', 'width=600,height=400');
      childWindow.document.write(`
        <!DOCTYPE html>
        <html lang='en'>
        <head>
          <meta charset='UTF-8' />
          <title>Child Window</title>
        </head>
        <body>
          <div id='childContent'>Initial Content</div>
        </body>
        </html>
      `);
      // Ensure the content is updated after the window has fully loaded
      setTimeout(() => {
        childWindow.document.body.innerHTML += '<p>Message from parent window</p>';
      }, 1000); // Adjust the timeout duration as necessary
    });

    const iframe = document.getElementById('childIframe');

    iframe.onload = () => {
      const iframeDoc = iframe.contentWindow.document;
      iframeDoc.getElementById('childIframeContent').innerText += ' - Updated by Parent Window';
    };
  </script>
</body>
</html>

В этом примере родительское окно открывает дочернее окно и напрямую изменяет его содержимое после загрузки. Кроме того, оно обновляет содержимое встроенного iframe.

WARNING

Прямое манипулирование DOM через contentWindow.document или window.opener ограничено политикой одинакового происхождения (SOP) для контекстов с разным происхождением. Для безопасной и надежной связи всегда предпочитайте postMessage(). Для всплывающих окон с тем же происхождением window.opener можно использовать как альтернативу для прямого доступа к родительскому окну.

note

При использовании srcdoc содержимое iframe загружается асинхронно. Обработчик onload гарантирует готовность DOM, но для сложных сценариев рассмотрите возможность запуска взаимодействия через событие DOMContentLoaded, инициированное внутри iframe.

Использование Local Storage и Session Storage

Local Storage и Session Storage предоставляют альтернативный метод межоконного взаимодействия. Оба варианта хранения данных ограничены областью действия происхождения (origin), что позволяет различным окнам одного происхождения получать доступ к общим данным.


html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Local Storage Example</title>
</head>
<body>
  <h1>Local Storage Example</h1>
  <button id="storeData">Store Data</button>
  <button id="retrieveData">Retrieve Data</button>
  <div id="storageDisplay"></div>

  <script>
    // Listen for changes triggered by other windows/tabs
    window.addEventListener('storage', (event) => {
      if (event.key === 'sharedData') {
        document.getElementById('storageDisplay').innerText = 'Updated Data: ' + event.newValue;
      }
    });

    document.getElementById('storeData').addEventListener('click', () => {
      localStorage.setItem('sharedData', 'This is shared data');
    });

    document.getElementById('retrieveData').addEventListener('click', () => {
      const data = localStorage.getItem('sharedData');
      document.getElementById('storageDisplay').innerText = 'Stored Data: ' + data;
    });
  </script>
</body>
</html>

В этом примере родительское окно сохраняет данные в localStorage и извлекает их при нажатии кнопок. Для включения межоконной синхронизации добавлен обработчик события storage. Обратите внимание, что событие storage срабатывает только в других контекстах просмотра, а не в том, которое вызвало изменение.

API Broadcast Channel

API Broadcast Channel обеспечивает простое взаимодействие между контекстами просмотра (окнами, вкладками, iframe), которые имеют общее происхождение.


html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Broadcast Channel Example</title>
</head>
<body>
  <h1>Broadcast Channel Example</h1>
  <button id="sendMessage">Send Message</button>
  <div id="broadcastDisplay"></div>

  <script>
    const channel = new BroadcastChannel('example_channel');

    channel.onmessage = (event) => {
      document.getElementById('broadcastDisplay').innerText = 'Broadcast message received: ' + event.data;
    };

    document.getElementById('sendMessage').addEventListener('click', () => {
      channel.postMessage('Hello from another context!');
    });
  </script>
</body>
</html>

В этом примере создается канал Broadcast Channel, и сообщение отправляется при нажатии кнопки. Сообщение принимается и отображается внутри элемента div.

Чтобы правильно протестировать этот пример:

  1. Дважды нажмите кнопку «Попробуйте сами» (Try it Yourself), чтобы открыть страницу примера в двух разных вкладках.
  2. Затем нажмите кнопку «Отправить сообщение» (Send Message) в одной из вкладок/окон.
  3. Вы должны увидеть, как сообщение появится в другой вкладке/окне.

API BroadcastChannel предназначен для межвкладочного взаимодействия, поэтому сообщение будет отправлено из одной вкладки/окна во все остальные, открытые для того же происхождения (в данном случае — того же HTML-файла).

WARNING

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


javascript
const message = { type: 'greeting', content: 'Hello, Child Window!' };
childWindow.postMessage(JSON.stringify(message), '*');

WARNING

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


javascript
try {
  childWindow.postMessage('Hello, Child Window!', '*');
} catch (e) {
  console.error('Failed to send message:', e);
}

Заключение

Межоконное взаимодействие в JavaScript — это мощная функция, которая при правильном использовании может значительно повысить интерактивность и удобство использования веб-приложений. Используя такие методы, как window.postMessage(), локальное хранилище и API Broadcast Channel, разработчики могут эффективно управлять обменом данными между различными окнами, вкладками и фреймами. Следуйте лучшим практикам для обеспечения безопасной и надежной связи и используйте приведенные примеры для интеграции этих методов в свои проекты.

Практика

Что верно относительно межоконного взаимодействия в JavaScript?

Считаете ли это полезным?

Предпросмотр dual-run — сравните с маршрутами Symfony на продакшене.