Targetを指定しないリンク操作のあれこれ

HTMLのコーディングをしたことのある方なら、リンクをクリックした際に新しいウィンドウで開くようにするアトリビュート『target=”_blank”』はご存じだと思います。このアトリビュートですが、初期のXHTML1.1やHTML4.01/XHTML1.0のStrictでは非推奨だったり廃止されていたりします。メリットもデメリットもあるのでしょう。HTML5では復活しているとか何とか…。

Strictを使う人は少ないのも事実だし、実際は非推奨のDOCTYPEであろうとブラウザ側の互換機能が働いて『target=”_blank”』は動作するのですが、閲覧者がソースを簡単に見ることの出来るHTMLと言うこともあって、出来るだけ仕様にそったValidなコードを書きたいですよね。以前書いていたブログではJavascriptで以下のような関数を読み込ませておいて、新しいウィンドウで開く箇所のaタグのアトリビュートに『onclick=”return o(this)”』を付与していました。これでStrictでもValidなコーディングとなります。

[javascript lang_name=”true”]
function o(target) {
window.open(target.href);
return false;
}
[/javascript]
[xml lang_name=”true”]
affetti.jp
[/xml]

ところが毎度毎度このアトリビュートを付け加えるのが面倒臭い!どうせなら自動判別出来ないか、と言うことで考えてみました。アルゴリズムは簡単で、リンク先URLが現在のページのホストと同じなら同一ウィンドウで、異なれば新しいウィンドウで開くというものです。コードが短くて済むjQueryを使用しています。

細かい解説は以下のコード内コメントを参照します。

[javascript lang_name=”true”]
// Open on New Window for Strict Type Document
function o(obj) {
window.open(obj.href || obj);
return false;
}

// Move Location by Javascript
function m(obj) {
window.location.href = obj.href || obj;
return false;
}

// Window open fixes
function aTagExpand(){
// hrefアトリビュートのあるaタグのクリックイベントを設定
jQuery(‘a[href]’).click(function(){
// すでにonclickイベントがある場合は手を加えない
if(jQuery(this)[0].onclick == null){
// 同一ホストかどうか判定
if(jQuery(this)[0].hostname == location.hostname){
// 同一ホストならそのウィンドウで開く
return m(this);
// ホストが違うものは外部ページと判断
} else {
// 外部ページなので新規ウィンドウで開く
return o(this);
}
}
});
}
// onloadイベントに登録
jQuery(aTagExpand);
[/javascript]

if分による条件分岐で簡単に実装できました。target属性の使用可不可に関わらず、コーダーが一つずつ判別することなく外部サイトは新しいウィンドウで表示させることの出来る便利な関数だと思います。

これだけでは面白くないので、今回はアンカーリンクの処理まで実装してみました。折角jQueryを使っているので、アンカーによるページ内ジャンプもスクロールにて行うようにしています。

仕様としてはこんな感じ。

・リンク先が同一ホストであれば同じウィンドウで開く
・リンク先が異なるホストであれば新しいウィンドウで開く
・同一ホストでも手動で設定することで新しいウィンドウで開くことが出来る
・アンカーにはスクロールでジャンプする
・異なるページのアンカー付きリンクも、リンク先でスクロールしてジャンプする

[javascript lang_name=”true”]
// Open on New Window for Strict Type Document
function o(obj) {
window.open(obj.href || obj);
return false;
}

// Move Location by Javascript
function m(obj) {
window.location.href = obj.href || obj;
return false;
}

// Anchor link and Window open fixes
function aTagExpand(){
// hrefアトリビュートのあるaタグのクリックイベントを設定
jQuery(‘a[href]’).click(function(){
// すでにonclickイベントがある場合は手を加えない
if(jQuery(this)[0].onclick == null){
// 同一ホストかどうか判定
if(jQuery(this)[0].hostname == location.hostname){
// アンカーが設定されているかチェック
if(jQuery(this)[0].hash){
// 同一ページ内でのアンカーか、他ページでのアンカーか判別
if(((jQuery(this)[0].pathname || ‘/’ + jQuery(this)[0].pathname) == location.pathname) && jQuery(jQuery(this)[0].hash)){
// 同一ページ内アンカーなのでスクロールする
var target =jQuery(jQuery(this)[0].hash).offset().top;
jQuery(‘html,body’).animate({scrollTop: target}, 500);
return false;
// 他ページでのアンカーありリンク
} else {
// ページ読み込み時にスクロールするリンクへ変換 (直飛び回避)
if(!/_/.test(this.hash)) this.hash += ‘_’;
return m(this);
}
// アンカーがないのでそのままページ移動
} else {
return m(this);
}
// 外部ページなので新規ウィンドウで開く
} else {
return o(this);
}
}
});
}
// onloadイベントに登録
jQuery(aTagExpand);

// スクロール用リンクを読み込んでアンカーまでスクロール
function scrollByHash(){
// アンカーの最後の文字がアンダースコア”_”であるかどうか
if(/_/.test(location.hash) && jQuery(location.hash.split(‘_’)[0]) != “” && jQuery(location.hash.split(‘_’)[1]).length == 0){
// アンダースコアを省いた実際のアンカー位置までスクロールする
var target = jQuery(location.hash.split(‘_’)[0]).offset().top;
jQuery(‘html,body’).animate({scrollTop: target}, 400);
}
}
// onloadイベントに登録
jQuery(scrollByHash);
[/javascript]

これで、基本リンクにはonclickアトリビュートを付ける必要はなくなりました。内部ページでどうしても新しいウィンドウで開きたいものにだけ、『onclick=”return o(this)”』を付け加えれば良いことになります。

(1) 外部ホストなので新しいウィンドウで開く(サンプルリンク)

[xml]Google[/xml]

(2) 同一ホストなので同じウィンドウで開く(サンプルリンク)

[xml]affetti.jp[/xml]

(3) 同一ホストだがonclickが指定してあるので新しいウィンドウで開く(サンプルリンク)

[xml]affetti.jp[/xml]

少し工夫をしてある部分は、同一ホスト他ページへのリンクでアンカーがついているもの。新しいウィンドウでアンカー付きリンクを開いた場合、URLからジャンプ先のターゲットにスクロールしようとしても、ブラウザのアンカージャンプ機能が働いてうまくスクロールしません。この機能を無効にする方法が見当たらなかったため、アンカーの文字列の最後にアンダースコア”_”のついているものをページ読み込み時に自動スクロールする専用アンカーとして決め、処理をしています。これでブラウザ側でジャンプすることもなく、またクリックした直後にアンカー書き換えを行うので、検索エンジンなどにも間違ったリンクを伝えないで済みます。全てにおいて言えることですが、Javascriptがオフになっていてもhref属性にリンク先が入っておりページ遷移はするので、動作自体に大きな問題がないのも特徴ですね。

jQueryを使わない場合は上記の書き方だとonloadイベントに複数関数を登録できないので、そちらは機会があれば作ってみようと思います。