【jQuery】Lightbox風の単純な画像の拡大表示機能を自作する


プラグインほどの多機能は要らず、単純に画像クリックで拡大画像を表示するという Lightbox 風の機能が欲しかったので作ってみました。

以前も作ったことはあったのですが、その際にブログに残すなどをしていなかったため、どこかに紛失してしまいました。
ということで、今回新たに作ったものを簡単な説明とともに残しておきます。


欲しい機能

・サムネイル画像クリックで、大きな画像を手前に表示

・画像表示時に、周囲を透過処理で暗くする

・画像は画面の中央に表示

・画像を閉じる際は、画面内のどこをクリックしても閉じる
(☓ボタンで閉じるなどは面倒)

・フェードイン、フェードアウトのような余分な処理は要らない

 

ちなみにフェードイン、フェードアウトなどの処理を入れたい場合は、表示を切り替えている部分を改造すれば実装可能です。

「画像は画面の中央に表示」の機能を実装するには、大きな画像の縦横のサイズを予め把握している必要があります。
縦横のサイズが不明の場合は、決め打ちで大体中央に表示させることになります。

 

画像が「サムネイル画像」と「大きな画像」の2枚あるパターンと、「大きな画像」1枚のみの2パターンについて書きます。

 

サムネイル画像と大きな画像の2枚あるパターン

■HTML

<a class="img_popup" width="大きな画像の横幅" height="大きな画像の高さ" href="大きな画像のURL"><img src="サムネイル画像のURL"></a>

aタグに独自の属性 width と height を追加していますが、これを JavaScript から参照して画像を中央に表示するために使用します。
大きな画像の縦横のサイズを取得できない場合は、aタグに記載している width と height を削除します。

 

■CSS

.overlay {
position: absolute;
display: none;
top: 0;
width: 100%;
z-index: 1;
background-color: rgba(0,0,0,0.6);
}
.overlay img {
display: inline-block;
position: absolute;
border: 5px solid #fff;
}

background-color で拡大画像表示時の背景色を指定しています。
rgba は RGBカラーに、透明度(alpha)を追加したもので、0.0~1.0の間で指定できます。
0 が透明、1 が不透明となります。

border: 5px solid #fff; で、画像の周囲を白い線で囲っています。
この辺はお好みで変えてください。

 

■JavaScript

$(function() {
  $('body').prepend('<div class="overlay"></div>');

  $('a.img_popup').click(function() {
    var left = ($(window).width() / 2) + $(window).scrollLeft() - ($(this).attr('width') / 2);
    var top = ($(window).height() / 2) + $(window).scrollTop() - ($(this).attr('height') / 2);

    $('div.overlay').css('height', $(document).height());
    $('div.overlay').empty().append('<img src="' + $(this).attr('href') + '" />').css({display: 'block'});
    $('div.overlay img').css({left: left, top: top, opacity: '1'});
    return false;
  });

  $('div.overlay').click(function() {
    $('div.overlay').hide();
  });
});

各項目の簡単な説明

$('body').prepend('<div class="overlay"></div>');

大きな画像を表示する際の背景となる divタグを body に追加しています。

 

$('a.img_popup').click(function() {

クラス名が img_popup の aタグがクリックされたら 5~11行目の処理を行います。

 

var left = ($(window).width() / 2) + $(window).scrollLeft() - ($(this).attr('width') / 2);
var top = ($(window).height() / 2) + $(window).scrollTop() - ($(this).attr('height') / 2);

(ブラウザのウィンドウサイズ ÷ 2) + body のスクロール位置 ー (aタグに指定した大きな画像の縦横 ÷ 2)
これにより、ページがスクロールしていても、常にブラウザの中央に画像が表示されます。

※大きな画像の縦横の値が分からない場合は、マイナスする値に適当な数値を入れます。
ただし、この場合は画像のサイズによって表示位置を変えることはできないので、画面のバランスが悪くなります。

 

$('div.overlay').css('height', $(document).height());

divタグの高さをドキュメントの高さと同じにしています。

 

$('div.overlay').empty().append('<img src="' + $(this).attr('href') + '" />').css({display: 'block'});

divタグの中身(html)を一度空にしてから、imgタグを追加しています。
また、その際に src に aタグの href の内容を指定しています。

 

$('div.overlay img').css({left: left, top: top, opacity: '1'});

imgタグの表示位置を指定し、非表示から表示に切り替えています。

 

return false;

aタグの href のリンクが起動しないように false を返しています。

 

$('div.overlay').click(function() {
  $('div.overlay').hide();
});

divタグがクリックされたら divタグを非表示にしています。

これで、簡単な Lightbox 風の画像拡大表示機能が実装できます。

 

大きな画像1枚のみで実装するパターン

■HTML

<a class="img_popup" width="大きな画像の横幅" height="大きな画像の高さ"><img style="width: 100px" src="大きな画像のURL"></a>

imgタグに width を指定して縮小表示し、サムネイル画像にします。

 

■CSS
css は同じものを使用します。

 

■JavaScript
JavaScript も基本は同じですが、9行目だけ書き換えます。

$('.overlay').empty().append('<img src="' + $($(this).children('img')).attr('src') + '" />').css({display: 'block'});

src に指定する値を imgタグの src に変えています。

以上です。

 

単純な画像拡大機能であれば、わざわざプラグインを入れなくても簡単に実装できますね。
そう言いつつ、当ブログの画像拡大機能は、プラグインを使っていたりしますが・・・。

ちなみに、この前アップした「【PHP】画像データ文字列から画像の横幅と高さを取得する」という記事は、今回の中央表示をしたいための処理だったりします。

 


【PHP】配列の添字と文字列のオフセット指定


文字列のオフセット指定について、ちゃんと理解しないまま使用していたら、ちょっとハマッた箇所があったので整理しました。

配列の添字

$array = array('Yamada', 'Tarou',);

echo $array[0];		// 「Yamada」が出力される

文字列のオフセット指定

$string = 'Yamada';

echo $string[0];	// 「Y」が出力される

 

まあ、この辺の違いは今更書くまでもないことだろうけど・・・。

処理の高速化について調べていたところ、下記のような記述を見つけました。

 

33. if (strlen($foo) < 5) を調べたいなら if (!isset($foo{5})) と書くと速い。

参考サイト PHPコード最適化高速化TIPSまとめ

 

この $foo{5} という記述を見慣れていなかった私は「おぉ、便利だなぁ」と、PHPマニュアルをちゃんと読んだりすることなく、整数を扱う変数にも使ってしまっていました。

if (isset($number['a']{0}))
{
 // 処理
}

ところが、期待したような結果を得られず、ちゃんと調べてみたところ、この {0} という記述は文字列のオフセット指定 [0] と同じ処理を行っており、変数が文字列型でない場合は当然ながら期待した動作を行いません。

 

注意:
その他の型の変数 (配列や、適切なインターフェイスを実装したオブジェクトを除く) に対して [] や {} でアクセスすると、何もメッセージを出さずに単に NULL を返します。

参考サイト PHP: 文字列 – Manual

 

また、波括弧 {}を使用したオフセット指定方法は、PHP 6で廃止予定とのことなので、角括弧 []に統一しておいた方が良いです。

ということで、整数値の変数が入っているかチェックを行いたい場合は

if (isset($number['a']))
{
 // 処理
}

通常通り、単にこれだけの記述になりますね。
何を今更な内容ですが、マニュアルをちゃんと読まなかったため、勘違いをしてしまいました。

数値か調べたい場合は is_numeric がありますが、今回の用途では「数値が入る」か「何も入らない」かの2パターンしかないため isset を使用しています。

 

17. $row[‘id’] は $row[id] より7倍速い。
参考サイト PHPコード最適化高速化TIPSまとめ

ついでといってはなんですが、配列の添字指定時に’(シングルクォーテーション)で括った場合の処理速度を確認してみました。

処理は単純に、配列の要素を変数に代入するだけという物を10万回実行。

$test = array(
	'a' => 'Yamada',
	'b' => 'Tarou',
);

$max = 100000;

for ($i=0; $i<$max; $i++)
{
	$x = $test['a'];
}

for ($i=0; $i<$max; $i++)
{
	$y = $test[a];
}

結果は下記のとおりとなりました。

$x = $test[‘a’]; 0.0095689296722412 秒
$y = $test[a]; 0.83886814117432 秒

また、添字が数字だった場合がどうなるかも試してみました。

$x = $test[‘0’]; 0.0065720081329346 秒
$y = $test[0]; 0.0066301822662354 秒

こちらは何回か試してみましたが、[‘0’]が早い時もあれば、[0]が早い時もあったので、処理速度に大きな差はないようです。
コーディングスピードを上げるという観点から考えると、添字が数字の場合は[0]で良さそうです。
といっても、数字の添字を直接指定して使う機会ってほとんどない気がします。

 

文字列のオフセット指定時の’(シングルクォーテーション)
また、文字列のオフセット指定についても’(シングルクォーテーション)のあり・なしで処理速度が変わるのか試してみました。

$test = 'Yamada Tarou';

実行回数を10回に増やして比較してみました。
早かった方が赤色です。

回数 $x = $test[‘0’]; $y = $test[0];
1 0.010056972503662 秒 0.012347936630249 秒
2 0.010039806365967 秒 0.009335994720459 秒
3 0.010890007019043 秒 0.0094809532165527 秒
4 0.0096769332885742 秒 0.012487173080444 秒
5 0.012174844741821 秒 0.0098400115966797 秒
6 0.0097651481628418 秒 0.0092430114746094 秒
7 0.0090720653533936 秒 0.0089559555053711 秒
8 0.012573003768921 秒 0.010463953018188 秒
9 0.0095231533050537 秒 0.0098400115966797 秒
10 0.012336015701294 秒 0.010035037994385 秒

大きな差は見られず誤差範囲のようです。
こちらもタイピング量を減らし、コーディングスピードを上げるという観点からシングルクォーテーション無しの [0] で良さそうでした。

結論としては、$a{0}のような波括弧を使用した記述は撲滅して欲しいです(笑)
ネットはどうしても古い記事がそのままメンテナンスされず残ってしまうので、非推奨な書き方がいつまでも出回るのが難しいところですね。

私も、古い記述の記事を掲載し続けないように気をつけねば。。。


【PHP】画像データ文字列から画像の横幅と高さを取得する


APIなどで取得した画像のURLから、画像データを取得した際に画像の横幅と高さも取得する方法。

画像取得部分は既に実装済みだったのですが、新たに画像の横幅と高さが必要になったため、よい方法がないか調べた結果、GDライブラリの imagecreatefromstring、imagesx、imagesy を使用することにしました。

getimagesize を使えば、下記のように簡単にURLから横幅と高さを取得することができます。

list($width, $height) = getimagesize($url);

が、この場合「実データの取得」と「横幅、高さの取得」の2回 URL を叩く必要がでるため、速度が遅くなるのでこちらは却下。

実装済みのurlより画像を取得プログラムはこんな感じ。

// 初期化する
$curl = curl_init($url);

// タイムアウト
curl_setopt($curl, CURLOPT_TIMEOUT, $timeout);

// 返り値を文字列で取得
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);

// 実行する
$data = curl_exec($curl);

$error_number = curl_errno($curl);
$error_message = curl_error($curl);

// 閉じる
curl_close($curl);

// エラーが発生したら
if ($data === FALSE)
{
	// $error_number、$error_message、$urlなどをログに保存
}

画像ファイルの取得に、file_get_contents などではなく、cURL を使用しているのはこちらの方が動作が早いからです。
この辺は「file_get_contents cURL」などで検索すると色々と出てくるので、そちらに任せます。

今回追加した画像の横幅と高さを取得するプログラムがこちら。

$image = imagecreatefromstring($data);

// 幅
$width = imagesx($image);

// 高さ
$height = imagesy($image);

curl_exec で取得した $data を imagecreatefromstring関数に渡して、画像を新規作成し、その横幅と高さをそれぞれ imagesx と imagesy で取得しています。

折角なので、どの程度速度に差が出るのか調べてみました。
画像のURLを10個用意し、それぞれの取得にかかった時間と、その平均時間です。

◆ imagecreatefromstring
1 width:940 height:198 time:0.057245016098022
2 width:687 height:619 time:0.069268941879272
3 width:503 height:421 time:0.055033206939697
4 width:504 height:421 time:0.040925025939941
5 width:504 height:423 time:0.039738893508911
6 width:504 height:422 time:0.050230979919434
7 width:504 height:422 time:0.050395011901855
8 width:609 height:436 time:0.040589809417725
9 width:605 height:461 time:0.039741992950439
10 width:581 height:480 time:0.055176973342896
ave:0.049834585189819
◆ getimagesize
1 width:940 height:198 time:0.20524716377258
2 width:687 height:619 time:0.21880984306335
3 width:503 height:421 time:0.20294904708862
4 width:504 height:421 time:0.18598985671997
5 width:504 height:423 time:0.1845760345459
6 width:504 height:422 time:0.2015380859375
7 width:504 height:422 time:0.20126390457153
8 width:609 height:436 time:0.18370890617371
9 width:605 height:461 time:0.18281006813049
10 width:581 height:480 time:0.20241594314575
ave:0.19693088531494

10回では試行回数が少ないかと思いましたが、思ったよりも差が開きました。
「imagecreatefromstring」が平均で 0.05秒程度だったのに対し、「getimagesize」では 0.2秒程度かかりました。

getimagesize を使用して URL より直接画像サイズを取得するのは、GDライブラリが使えないなどの理由が無い限り、避けたほうが懸命ですね。

他にも、取得した画像データを一度ファイルに書き出して、getimagesizeを使用する方法が考えられます。(というか普通はこちらを先に試すよね)

・・・が、ちょっとプログラムが面倒なので試してません。
で終わろうかと思ったのですが、ここまできてそれは無いか・・・と思い試してみました。

画像データをファイルへ書き出す部分は、普通にこんな感じ。

// テンポラリファイルの作成
$temp_file = tempnam(sys_get_temp_dir(), 'Temp');

// テンポラリファイルに書き込み
$handle = fopen($temp_file, "w");
fwrite($handle, $data);
fclose($handle);

// テンポラリファイルから画像の幅と高さを取得
list($width, $height) = getimagesize($temp_file);

// テンポラリファイルの削除
unlink($temp_file);

計測結果がこちら。

◆ ファイル書き出し
1 width:940 height:198 time:0.047371864318848
2 width:687 height:619 time:0.055737018585205
3 width:503 height:421 time:0.046814918518066
4 width:504 height:421 time:0.0384681224823
5 width:504 height:423 time:0.036808013916016
6 width:504 height:422 time:0.047918081283569
7 width:504 height:422 time:0.049361944198608
8 width:609 height:436 time:0.037344932556152
9 width:605 height:461 time:0.037187814712524
10 width:581 height:480 time:0.046723127365112
ave:0.04437358379364

え~っと・・・。

うん、こっちの方が早かったです。。。

ファイルに書き出すのが面倒なので、画像データ文字列から直接、横幅と高さを取得出来る方法がないかな~と、探し始めたのが今回の事の発端だったのですが、最初から素直にファイルに書き出して取得していれば良かったですね。。。

「0.0498秒」と「0.0443秒」、一見すると大した差に見えないかもしれませんが、APIなどで複数回アクセスをすることを考えると早いに越したことはないですね。

テストに使用した画像のサイズが小さいこともあり、あまり差がないように見えるということもあるだろうと思い、大きいサイズで試してみました。
今度は、「cURL で画像データを取得」の部分を別個に分けて計測し、単純に「imagecreatefromstring」と「ファイル書き出し」での処理時間の差を比較できるようにしました。

まずは、cURL で画像取得にかかった時間がこちら。

◆ cURL
01 time:0.090460062026978
02 time:0.086920976638794
03 time:0.076326131820679
04 time:0.077839136123657
05 time:0.078439950942993
06 time:0.068161010742188
07 time:0.076191902160645
08 time:0.071757078170776
09 time:0.085734128952026
10 time:0.076704978942871
ave 0.078853535652161

画像サイズを大きくしたので、画像取得自体にかかる時間も増えています。

取得した画像データから imagecreatefromstring で画像を作成し、imagesx と imagesy で縦横を取得するのにかかった時間がこちら。

◆ imagecreatefromstring
01 width:1396 height:996 time:0.056331157684326
02 width:1920 height:1056 time:0.075820922851562
03 width:1680 height:940 time:0.050305128097534
04 width:1600 height:980 time:0.055998086929321
05 width:1840 height:980 time:0.062910079956055
06 width:1780 height:980 time:0.054359197616577
07 width:1830 height:960 time:0.064152002334595
08 width:1590 height:950 time:0.047601938247681
09 width:1920 height:1056 time:0.071624040603638
10 width:1650 height:1000 time:0.053611993789673
ave 0.059271454811096

取得した画像データをファイルに書き出し、getimagesize で縦横を取得するのにかかった時間がこちら。

◆ ファイル書き出し
01 width:1396 height:996 time:0.00076794624328613
02 width:1920 height:1056 time:0.00046300888061523
03 width:1680 height:940 time:0.00035810470581055
04 width:1600 height:980 time:0.00033402442932129
05 width:1840 height:980 time:0.00039386749267578
06 width:1780 height:980 time:0.0003209114074707
07 width:1830 height:960 time:0.00033903121948242
08 width:1590 height:950 time:0.00030994415283203
09 width:1920 height:1056 time:0.00043201446533203
10 width:1650 height:1000 time:0.00038290023803711
ave 0.00041017532348633

爆速ですね・・・。
画像サイズが大きくなるほど差が開くようです。

ということで、一度ファイルに書き出して縦横を取得する方法が一番早いということがわかりました。

面倒臭がらずに、一番単純な方法で実装するのが、一番動作が軽いという良い例になりました。

最終的な検証に使ったプログラムは下記です。
cURL で画像を取得する部分は、関数名「getFile」で関数化して「func_file.php」に保存してあります。

require_once 'func_file.php';

$img_array = array(
	1  => '画像のURL',
	2  => '画像のURL',
	3  => '画像のURL',
	4  => '画像のURL',
	5  => '画像のURL',
	6  => '画像のURL',
	7  => '画像のURL',
	8  => '画像のURL',
	9  => '画像のURL',
	10 => '画像のURL',
);

$time_total = 0;

echo '◆ cURL<br>';

foreach ($img_array as $img_key => $img_value)
{
	// 開始マイクロタイム
	list($msec, $sec) = explode(" ", microtime());
	$start = (float)$msec + (float)$sec;

	// 画像データを取得
	$data_image[$img_key] = getFile($img_value);

	// 終了マイクロタイム
	list($msec, $sec) = explode(" ", microtime());
	$end = (float)$msec + (float)$sec;

	$time = $end - $start;
	$time_total += $time;

	echo sprintf('%02d',$img_key),' time:',$time,'<br>';
}

$time_ave = $time_total / count($img_array);

echo 'ave ',$time_ave,'<br><br>';

$time_total = 0;

echo '◆ imagecreatefromstring<br>';

foreach ($img_array as $img_key => $img_value)
{
	// 開始マイクロタイム
	list($msec, $sec) = explode(" ", microtime());
	$start = (float)$msec + (float)$sec;

	// 画像データより新規イメージを作成
	$im = imagecreatefromstring($data_image[$img_key]);

	// 幅
	$width = imagesx($im);

	// 高さ
	$height = imagesy($im);

	// 終了マイクロタイム
	list($msec, $sec) = explode(" ", microtime());
	$end = (float)$msec + (float)$sec;

	$time = $end - $start;
	$time_total += $time;

	echo sprintf('%02d',$img_key),' width:',$width,' height:',$height,' time:',$time,'<br>';
}

$time_ave = $time_total / count($img_array);

echo 'ave ',$time_ave,'<br><br>';

$time_total = 0;

echo '◆ ファイル書き出し<br>';

foreach ($img_array as $img_key => $img_value)
{
	// 開始マイクロタイム
	list($msec, $sec) = explode(" ", microtime());
	$start = (float)$msec + (float)$sec;

	// テンポラリファイルの作成
	$temp_file = tempnam(sys_get_temp_dir(), 'Temp');

	// テンポラリファイルに書き込み
	$handle = fopen($temp_file, "w");
	fwrite($handle, $data_image[$img_key]);
	fclose($handle);

	// テンポラリファイルから画像の幅と高さを取得
	list($width, $height) = getimagesize($temp_file);

	unlink($temp_file);

	// 終了マイクロタイム
	list($msec, $sec) = explode(" ", microtime());
	$end = (float)$msec + (float)$sec;

	$time = $end - $start;
	$time_total += $time;

	echo sprintf('%02d',$img_key),' width:',$width,' height:',$height,' time:',$time,'<br>';
}

$time_ave = $time_total / count($img_array);

echo 'ave ',$time_ave,'<br><br>';