外部リンクをクリックしたときだけ別タブを開く方法について考える
概要
HTMLで別ウィンドウを開くリンクを張る場合、<a target='_blank'>
のように、<a>
要素にtarget
属性を設定する必要があります。
よくあるパターンとして、内部リンクは同じタブ内で、外部リンクは別タブ(ウィンドウ)で開きたいという状況があります。
ですが内部リンクか外部リンクかを判断しながらHTMLを書いていくのはどうにも面倒くさい。
そんなずぼらな人向けのJavaScriptを考えてみました。
経緯
考えたきっかけはGitea(Gitのリモートサーバー)をセルフホストしたこと。
Wikiに記載したリンクの「内部リンクは同じタブ」で、「外部リンクは別タブ」で開きたいと思ったので考えてみました。
サンプルコード
HTMLファイルの先頭あたりに追加しましょう。
document.addEventListener('click', event => {
if (event.target.tagName.toUpperCase() === 'A' && event.target['target'] === '') {
let href = event.target['href'];
if (href !== '' && new URL(href, window.location.href).host !== new URL(window.location.href).host) { // ※
window.open(href, '_blank');
event.stopPropagation();
event.preventDefault();
}
}
});
前提として、今回のサンプルコードは「異なるドメイン」の場合を外部リンクとみなして別ウィンドウを開くコードです。
つまりhttps://xxxx.yyyy.jp:8080/AAAA/bbbb/ccc.html
のxxxx.yyyy.jp:8080
が一致しているかどうかで判断しています。
サブディレクトリまで含んだURL(xxxx.yyyy.jp:8080/AAAA
)までを内部リンクとする場合は、サンプルコードの※印部分を改造する必要があることをご注意ください。(改善案の項にて解説しています)
解説
new URL
のURL
とは
今回のサンプルコードでは、URL APIを使用しています。
これはURL文字列を雑に引数として放り込むとURLとして分析してくれるという便利なAPIで、今回はドメインを比較する工程で使用しています。
- 比較左辺:
new URL(href, window.location.href).host
- 比較右辺:
new URL(window.location.href).host
比較式左辺では「<a>
要素がリンク先としているURL」を分析しています。
第1引数がリンク先、第2引数が現在のURLを指定することで、href
要素が相対参照(./page.html
)やハッシュ(#~~
)であった場合に現在のURLが基準であることを示しています。
ここで第1引数が絶対参照(https://
や//
で始まるURL)であった場合、URL()
の第2引数は無視される仕様になっています。
比較式右辺は「現在のURL」を分析しています。
window.location.href
は絶対参照のURLを返すので、第2引数の指定はしません。
また、.host
プロパティはURL APIで解析したURLのドメインとポート番号を返します。
つまりhttps://xxxx.yyyy.jp:8080/AAAA/bbbb/ccc.html
のxxxx.yyyy.jp:8080
が取得できるものです。
私が今回の課題に直面したGiteaでは、「https://xxxx.yyyy.jp:8080/AAAA/bbbb/ccc.html
のhttps://xxxx.yyyy.jp:8080/AAAA
をルートとする!」というようにサブディレクトリの指定が原則できません。(推奨されておらず、リバースプロキシなどを設定するしかありません)
そのため今回以上の実装は必要がありませんでした。
通常レンタルブログなどは、サブドメイン(https://xxxx.yyyy.jp:8080/
でいうxxxx
の部分)が変わることが多いので大抵の場合はこれで問題ないと思います。
改善案、サブディレクトリにも対応するためには
ですが、それでも今回のコードをサブディレクトリに対応したい場合はどうすればいいか。
試していないので確実なことは言えませんが、まずサブディレクトリを含んだURLを取得する必要があると思います。
これには「JavaScriptで<a>
要素のhref
属性にURLを代入すると絶対参照のURLが設定される」という仕様を使ってみるのがよいでしょう。
let elmn = document.createElement('a');
elmn.href = '/'; // ←ルートとなるルート相対パス(つまり「/」)を代入
console.info(elmn.href); // ←elmn.hrefにはルートとなる絶対参照のURLが設定されている
あとは比較右辺new URL(window.location.href).host
をこのサンプルコードから得られるURLに変更して、左辺はスラッシュの数を数えて切り抜くなり、文字列操作のstartsWith()
を使うなりして比較すればうまくいくのではないかなと思います。
試していないのでサンプルコードは掲載しませんが、おおよそうまくいく気がします。
おまけ
document.addEventListener('click')
?
なんでHTMLがJavaScriptによって動的に変わるため、<a>
要素に直接イベントを実装しても、あとから追加された<a>
要素にイベントが反映されないため。
こういった場合は「ドキュメントそのものにイベントを張り、event.target
(クリックされた要素)がなんなのか」を判定します。
toUpperCase()
?
なんで精神衛生上、文字列の比較は「大文字か小文字に明示的に変換」してやりたかったので……
JavaScriptの仕様上、明らかに不要なコードではあるので消して使用するのがよいでしょう
Discussion