【jQuery不要】外部ドメインのリンクを別ウィンドウで開く Javascript コード

Web サイトの記事にはリンクがつきものです。リンクと言っても「同一ウィンドウ内でページ遷移する」「新しいページを開いてページ遷移する」「画面遷移をせずに同一ページ内でモーダルを開く」というようにいくつかのバリエーションがあります。

この記事では以下の目的の読者を想定しています。

  • 「自サイト内の遷移は同一のウィンドウで。自サイト外(外部のドメイン)への遷移は新規ウィンドウで開きたい。」という操作感を実現する
  • いちいち自サイトと他サイトを区別して target="_blank" を指定するのは面倒くさい。プログラムで自走的に処理をしてしまいたい

このような課題を抱える方に対して、JavaScript で実現するためのコード例を紹介します。

本記事では、プログラミング初心者の方でも少しでも理解を深められるような記載を心がけています。有識者の方にとっては回りくどい表現に感じられたり、必ずしも正確な表現ではないと感じられるかもしれませんが、趣旨のご理解をお願いします。

完成物(新規ウィンドウで開く JavaScript コード)

最終的には以下のコードになります。

window.addEventListener("DOMContentLoaded", () => {
  const domain = document.domain;
  const regexp = new RegExp(domain);

  // 次の行がポイント。記事の本文がある場所を正確に伝えるために、CSSセレクタを指定する
  document.querySelectorAll(".entry-content a").forEach((element) => {
    let href = element.getAttribute("href");
    if (!regexp.test(href)) {
      element.setAttribute("target", "_blank");
      element.setAttribute("rel", "nofollow noopener");
    }
  });
});

以降のセクションでは、このコードに関する補足説明をします。

解説1 jQuery を使わない理由

jQuery を使わない Web サイトが増えてきたので、少しでも使える場面を増やすためです。目的のコードを見つけた!と思ったら jQuery が前提となっていて、そのためだけに導入するのはちょっと・・・とはなりたくないですからね。

一昔前は「Javascript で何かしたければとりあえず jQuery を入れておけ」というくらい使われていました。

では現在はどうでしょうか?もちろん現役ですし、ちょっとだけ Javascript で画面を何とかしたいシーンでは便利ですが、世界的な利用トレンドとしては下降気味です。Bootstrap 5 からは、いよいよ jQuery も不要になりましたからね

この時代背景には、動きの激しいリッチな Web アプリケーションを作りたい場合には jQuery で頑張り続けるのが厳しくなったことがあります。生産性が低いわけですね。

代わりにどうなったのかというと、最近の Web アプリケーション開発では SPA(Single Page Application)と呼ばれる設計手法が取られることが増えました。その実装(フレームワーク)には Angular, React, Vue と呼ばれるものが代表的です。

話が逸れていくので SPA についてはここまでとしますが、何でも jQuery の時代ではなくなったという点は抑えておくとよいかと思います。

解説2 コードの解説

実行のタイミング

window.addEventListener("DOMContentLoaded", () => {
  // ...
});

addEventListener は、コードが実行されるタイミングを明確にするものです。

EventTarget.addEventListener() - Web API | MDN

DOMContentLoaded は、具体的なタイミングを指定しています。

最初の HTML 文書の読み込みと解析が完了したとき、スタイルシート、画像、サブフレームの読み込みが完了するのを待たずに発生します。今回の場合、処理をしたい対象はリンクのタグに対してですので、 HTML 文書の読み込みが完了すれば目的を達成します。

Window: DOMContentLoaded イベント - Web API | MDN

つまり、HTML の読み込みが完了したら “別ウィンドウで開く” ためのコードを実行します。

リンク用のタグに処理を加える

// 現在開いているページ(自分のサイト)のドメインを取得する
const domain = document.domain;
// ドメインと一致するかどうかを判定するための判定条件を作る
const regexp = new RegExp(domain);

RegExp は、パターンで検索するためのオブジェクトです。堅苦しいので言い換えると、文字列が自身の期待する条件と一致するかどうかを検索(判定)するための命令をするようなものです。

RegExp - JavaScript | MDN

  // 次の行がポイント。記事の本文がある場所を正確に伝えるために、CSSセレクタを指定する
  document.querySelectorAll(".entry-content a").forEach((element) => {
    // ...
    }
  });

document.querySelectorAll(".entry-content a") の部分がかなり重要です。ここを間違えるとリンク文字列を全く見つけられないことになります。

極端な話、 document.querySelectorAll("a") と書いても動きます。ですが、Web サイトには色々なリンクがありますよね。例えば、自サイトのトップページに戻るリンク、新着記事一覧へのリンク、関連記事へのリンク・・・などです。

これら全てのリンクに対して処理をしたいのならば問題ないのですが、多くの場合は記事本文に対してのみ適用したいと思います。

そのため、記事の本文がある場所を明示的に指定して上げる必要があります。その指定に用いるのが CSS セレクターと呼ばれる手法です。聞き慣れない名前かもしれませんが、あながが日頃 CSS を操作するならば既に知識習得しているものですので、多くの場合は違和感なく使いこなせるでしょう。

CSS セレクター - CSS: カスケーディングスタイルシート | MDN

なお .entry-content は、WordPress を用いている場合は記事本文を指す領域です。利用しているテーマによっては修正されている可能性もありますので、自身のサイトがどうなっているかはしっかり確認してください。

.forEach では、見つかったリンクタグ( a タグ)の全てに対して処理を行うためのループ処理です。

document.querySelectorAll(".entry-content a").forEach((element) => {
  // 検出した a タグから、リンク先を示す部分 "href" 属性を抽出する
  let href = element.getAttribute("href");
  // リンク先のURLに、自サイトのドメインを含むかを判定する
  // [注意] 厳密には、ドメイン部分を比較しているのではなくURL前提を比較しているため検出できないケースあり
  if (!regexp.test(href)) {
    // if 判定の内部は、條件に一致していない場合、つまりリンク先が自ドメイン以外の場合の処理を記載
    // 自サイト外の場合は target="_blank" および ref="nofollow noopener" を適用する
    element.setAttribute("target", "_blank");
    element.setAttribute("rel", "nofollow noopener");
  }
});

前述の通り a タグ全てに対して、ドメインの判定および外部リンクに対する挙動を指定します。細部はコード内のコメントとして記載している通りですが、少し補足します。

今回の場合、「リンク先の URL 全体に対して、自分のドメインが含まれるかどうか」という判定しかしていません。一見実害がないように見えますが、リンク先の URL のパス部分に自サイトのドメインが含まれるケースでは誤判定します。

例えば、以下のケースですね。

  • 自サイトが https://example.com/ だった場合
    • → 自サイトのドメインとして example.com が検索条件になる
  • リンク先サイトが https://www.google.com/search?q=example.com だった場合

後者の URL は、Google に対して “example.com” というキーワードで検索したい場合の URL で、非現実的な條件ではありません。この場合、Google という別のサイトなのに example.com が含まれるので、自サイトとして認識してしまいます。

より正確に処理をしたい場合は、リンク先の URL を取得した let href = element.getAttribute("href"); の部分について、href の変数を分解してドメイン部分を厳密に抽出し、その部分を比較すれば解決します。

解説3 nofollow の必要性

nofollow は、検索エンジンのクローラー(自動巡回しているロボット)に対して、リンク先をクロールさせないための属性情報を指します。調べると、よく SEO との関連で言及されている情報です。

何より気をつけたいのは、リンク先がいつでも正常とは限らないという点でしょう。

外部ドメインは、自身の責任ではコントロールできないサイトです。記事を書いた時点では正常なサイトだったかもしれませんが、サイト先の運営者が更新をやめてドメインが売りに出されたり、あるいはハッキングにあっていたりと、悪意を持つサイトに変わっている可能性もあります。

その場合、「あなたのサイトは悪性のサイトに対してリンクを掲載している(推奨している!?)」サイトと捉えられてしまうわけですね。そのように勘違いされないよう、いつどのようになるか分からないサイトには nofollow を明記し、ロボットの巡回をそこで止めるのが望ましいでしょう。

逆に自サイト内の場合、より多くのページを巡回してもらったほうがいいので nofollow は付与しません。

解説4 noopener の必要性

noopenerは “No Opener” を意味するものです。これが何を意味するのか。一言で表すとセキュリティ的に危ないので target="_blank" で新規ウィンドウを開く場合は必ず付与すべき属性です。

細部は Javascript の細かい話になってしまいますのでここでは割愛しますが、以下の記事が画像デモつきで分かりやすいと思いました。HTML 本当は怖い target="_blank" 。rel=“noopener” ってなに? - かもメモ

なお、noopenerとあわせて、noreferrer についてもセットで語られる傾向があります。No Opener は IE では使えないようなのです。一時期は Edge でも未対応でしたが、2020 年 1 月以降は対応しているようですね。

rel=noopener | Can I use… Support tables for HTML5, CSS3, etc

そのため、切り捨てるのは IE のみとなりますが、IE も終焉が間もなく(2022 年 6 月)ですので、切り捨てで差し支えないでしょう。Microsoft 社 Internet Explorer のサポート終了について:IPA 独立行政法人 情報処理推進機構

WordPress においても、v4.7.4 以降では rel="noreferrer noopener" で、WordPress v4.8 から rel="noopener" だけになったようです(出典:HTML 本当は怖い target="_blank" 。rel=“noopener” ってなに? - かもメモ

まとめ

外部ドメインのサイトを新規ウィンドウで開くための Javascript コード例を紹介しました。

最近は Web サイトのレスポンス速度もサイトの品質評価において重要になっていますので、読み込むライブラリは少ないに越したことはありません。そのため jQuery を使うかどうかも慎重に考えましょう。

もちろん、しっかり考えて使うのであれば、強力な助っ人になってくれることでしょう:)