🥕

i18nでaタグを設定している時にSPAのページ遷移をさせる方法

2024/09/24に公開

i18n(多言語化)のライブラリをウィ用している時にaタグを含んだ形で設定するタイミングがあり、用意させれいるv-htmlのディレクティブだと普通のページ遷移が行われてしまうのでそれを自前で実装する必要があった。

実装方法

今回はNuxt3での実装方法について記述していきます。
Nuxtのpluginsディレクトリにカスタムディレクティブの実装をしていきます。

ディレクティブとは

Vue.jsのディレクティブは、HTML要素に特別な機能を追加するための特別なトークンです。これらは主にデータバインディングやDOM操作に使用されます。Vue.jsでは、ディレクティブはv-で始まるプレフィックスを持っています。
例:v-bind、v-model、v-if、v-forなどなど

カスタムディレクティブを追加(./plugins/nuxt-html.ts)

export default defineNuxtPlugin((nuxtApp) => {
const idsWithListeners = new Set<string>();

const generateUuid = () => {
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
    const r = (Math.random() * 16) | 0,
      v = c === "x" ? r : (r & 0x3) | 0x8;
    return v.toString(16);
  });
}

const pushRoute = (event: MouseEvent) => {
  event.preventDefault();
  event.stopPropagation();
  const to = (event?.target as Element)?.getAttribute("href");
  if (to) {
    useNuxtApp().$router.push(to);
  }
  return false;
}

const assignAnchorsIds = (html: string) => {
  const domParser = new DOMParser();
  const doc = domParser.parseFromString(html, "text/html");
  const anchors = doc.querySelectorAll("a");

  anchors.forEach((anchor) => {
    if (!anchor.hasAttribute("id")) {
      anchor.setAttribute("id", generateUuid());
    }
  });
  return doc.documentElement.outerHTML;
}

const convertAnchorToNuxtLink = (html: HTMLElement) => {
  const anchors = html.querySelectorAll("a");

  anchors.forEach((anchor) => {
    if (anchor.id && !idsWithListeners.has(anchor.id)) {
      const link = document.getElementById(anchor.id);

      link?.addEventListener('click', pushRoute, false)

      idsWithListeners.add(anchor.id);
    }
  });
}

const removeListeners = () => {
  idsWithListeners.forEach((id) => {
    const link = document.getElementById(id);
    link?.removeEventListener('click', pushRoute, false)
    idsWithListeners.delete(id);
  });
}
  nuxtApp.vueApp.directive("nuxtHtml", {
    mounted(el: HTMLElement, binding) {
      el.innerHTML = assignAnchorsIds(binding.value);
      convertAnchorToNuxtLink(el);
    },
    updated(el, binding) {
      el.innerHTML = assignAnchorsIds(binding.value);
      convertAnchorToNuxtLink(el);
    },
    beforeUnmount() {
      removeListeners();
    },
  });
});

export default defineNuxtPlugin((nuxtApp) => {

defineNuxtPluginは、Nuxt 3 でプラグインを定義するためのヘルパー関数になっております。
Nuxt 3 は Vue 3 の上に構築されており、プラグインシステムも新しいコンセプトを導入しています。この関数を使用することで、Nuxt アプリケーションに追加の機能やサードパーティライブラリを統合するプラグインを簡単に作成できるものになります。

const generateUuid = () => {

generateUuid 関数は、一意の ID を生成するために使用しております。これは、
xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx の形式で、各 xy はランダムな16進数の値に置き換えられます。これにより、HTMLドキュメント内の各 <a> タグに一意の ID を割り当てるようにして後に取得できるようにしております。

const pushRoute = (event: MouseEvent) => {

pushRoute 関数は、ユーザーがリンクをクリックしたときに標準のブラウザナビゲーション(新しいページへのリダイレクトやページリロード)を防ぎ、代わりに Nuxt.js のルーターシステムを通じてプログラム的なページ遷移を行う役割を担っています。

useNuxtApp().$router.push(to);

useNuxtApp()は、Nuxt3におけるグローバルなアプリケーションの状態や機能にアクセスするための関数です。
Nuxt3におけるルーティング関連はユーティリティ関数として提供されておりuseNuxtApp経由でuseRouteruseRouteは提供されております。

const assignAnchorsIds = (html: string) => {

assignAnchorsIds 関数は、与えられた HTML 文字列を解析して、すべての <a> タグを見つけ、それぞれに ID 属性がなければ UUID を割り当てます。これにより、後でイベントリスナーを正確に追加・削除することが可能になります。

const convertAnchorToNuxtLink = (html: HTMLElement) => {

convertAnchorToNuxtLink 関数は、HTML 内のすべての <a> タグを取得し、それぞれに対して click イベントリスナーを追加します。このリスナーは pushRoute 関数を呼び出し、Nuxt.js のルーターを使用してページ遷移を行います。これにより、通常のページリロードを防ぎながらアプリケーション内のナビゲーションを実現します。

const removeListeners = () => {

removeListeners 関数は、すべての登録された <a> タグから click イベントリスナーを削除します。これはコンポーネントがアンマウントされる前に行われ、メモリリークを防ぐのに役立ちます。

nuxtApp.vueApp.directive("nuxtHtml", {

nuxtHtml というカスタムディレクティブを定義しており、これを使用すると、指定された要素の innerHTML にバインディングされた値(HTMLコンテンツ)を設定し、その中の <a> タグを自動的に処理します。

追加したpluginのファイルをnuxt.config.tsに設定させる

plugins: [
    '~/plugins/nuxt-html',
],

設定ができたらVueファイルで使用

<script setup>
	const text = '<a href="/news/1">これはテストのお知らせです。</a>'
</script>

<template>
	<div v-nuxt-html="text"></div>
</template>

おわりに+会社宣伝

上記コードに関してはあくまで一例ですのでここから各仕様に合わせてカスタマイズしていただけたらなと思います!

株式会社アクトビ

Discussion