Vueで埋め込みiframeのURLおよび内部DOM操作の実装

外部の可視化ダッシュボード(例:Grafana)をVueアプリケーション内にiframeとして埋め込む際、動的に変化するURLの取得やiframe内のDOM要素に対する操作(非表示・削除・追加)が必要になるケースがある。これらの操作は同源ポリシーにより制限されるため、事前にバックエンド側でプロキシ設定を行い、iframeのドメインを現在のVueアプリケーションと同一オリジンにする必要がある。

動的iframe URLの取得

iframeのsrcは初期ロード時にAPIから取得し、Vueのリアクティブなプロパティにバインドする。iframeが読み込まれた後、contentWindow.location.hrefを参照することで現在のURLを取得できる。

<template>
  <iframe
    ref="dashboardFrame"
    :src="currentUrl"
    frameborder="0"
    style="width: 100%; height: 100%"
    @load="onIframeLoaded"
  />
</template>

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

const dashboardFrame = ref(null);
const currentUrl = ref('');

// 初期URLはAPIから取得済みと仮定
currentUrl.value = 'https://your-proxied-grafana-endpoint/';

const getCurrentIframeUrl = () => {
  if (dashboardFrame.value) {
    try {
      return dashboardFrame.value.contentWindow.location.href;
    } catch (e) {
      console.error('iframe URLの取得に失敗:', e);
      return null;
    }
  }
};
</script>

iframe内部DOMへの操作

iframeのロード完了後に、一定間隔で内部DOMの存在を確認し、特定の要素を操作する。以下は、GrafanaのUI要素を非表示にし、カスタムボタンを挿入する例である。

const pollInterval = ref(null);

const onIframeLoaded = () => {
  if (pollInterval.value) clearInterval(pollInterval.value);
  
  pollInterval.value = setInterval(() => {
    try {
      const doc = dashboardFrame.value?.contentDocument || 
                  dashboardFrame.value?.contentWindow?.document;
      
      if (!doc) return;

      // 特定のツールバーボタンを非表示
      const toolbarButtons = doc.querySelectorAll('.css-kg0g4j-toolbar-button');
      toolbarButtons.forEach(btn => {
        btn.style.visibility = 'hidden';
      });

      // カスタム操作ボタンを挿入
      const targetContainer = doc.querySelector('.css-exd1zr');
      if (targetContainer && targetContainer.children.length === 4) {
        const getBtn = doc.createElement('button');
        getBtn.textContent = '現在のURLを取得';
        getBtn.className = 'custom-toolbar-btn';
        getBtn.onclick = () => {
          const url = getCurrentIframeUrl();
          console.log('現在のiframe URL:', url);
        };

        const setBtn = doc.createElement('button');
        setBtn.textContent = '現在のクエリをデフォルトに設定';
        setBtn.className = 'custom-toolbar-btn';
        setBtn.onclick = () => {
          // デフォルトURL設定ロジック
        };

        targetContainer.insertBefore(getBtn, targetContainer.firstChild);
        targetContainer.insertBefore(setBtn, targetContainer.firstChild);

        // 不要な元の要素を削除
        const elementsToRemove = Array.from(targetContainer.children).slice(0, 2);
        elementsToRemove.forEach(el => el.remove());

        clearInterval(pollInterval.value);
        pollInterval.value = null;
      }
    } catch (error) {
      console.warn('iframe DOM操作中にエラー発生(通常は読み込み中):', error.message);
    }
  }, 300);
};

onMounted(() => {
  if (dashboardFrame.value) {
    onIframeLoaded();
  }
});

上記の実装では、setIntervalを用いてiframe内のDOMが完全にレンダリングされるまでポーリングを行い、目的の要素が存在するタイミングで操作を実行している。操作完了後はタイマーをクリアし、不要な処理を回避する。

タグ: Vue iframe DOM操作 grafana 同源ポリシー

7月31日 17:30 投稿