ETERNITY DESIGN BLOG

WordPress マルチサイトでのRetina対応を考える

MacBook ProにRetinaモデルの登場で、少なくともAppleに関しては徐々にRetinaモデルが増えていくだろうということで、このブログをRetina対応しました。

ここ1週間ほど、WPのメディア関連のコードを追ってみたり、どのように対応するのがいいのかいろいろ探っていましたので、このブログでどのように対応したのかを紹介します。

WP Retina 2xや、他のプラグインもありますので、条件が合う方はこれらの導入が簡単だと思います。
しかし、今回はプラグインを利用せず(WP Retina 2xが動作しなかった)、自分で実装しました。

Retina画像を表示する

表示に関しては、Retina Imagesを利用しました。
ただし、サイトの実装方法通りでは問題があったので、いくつか実装方法を変更しています。

retinaimages.phpの変更

まずは、必要なscriptの記述をテーマのheader.phpへ追加します。

次にマルチドメインの設定でドメイン(eternitydesign.net)専用のディレクトリを作成し、そこをルートに設定しているため、PHPでの画像パスのディレクトリ設定問題があり、正常に動作しませんでした。
とりあえず、コード11行目だけを修正。

$document_root  = $_SERVER['DOCUMENT_ROOT'] . '/ディレクトリ名';

.htaccessの記述

デフォルトでは以下のように設定するようになっています。

RewriteBase /
RewriteRule \.(?:jpe?g|gif|png|bmp)$ /retinaimages.php [L]

この記述で画像ファイルへのリクエストをretinaimages.phpに変更し、phpで環境に合わせて、画像を取得しデータを返すという処理になります。
しかし、WPをマルチサイトで使用すると、同じく画像の処理で以下のようなコードがhtaccessに必要となり、処理がかぶることになってしまいます。


RewriteRule ^files/(.+) wp-include/ms-files.php?file=$1 [L]

そこで、Retina Imagesのhtaccessを以下のように変更し、WPで管理されるメディアイメージ以外のテーマで利用する画像だけをRetina Imagesに処理されるようにします。

RewriteRule ^wp-content/themes/テーマディレクトリ&画像ディレクトリ/(.+)\.(jpe?g|gif|png)$ retinaimages.php [L]

WPのメディアで管理する画像の処理はms-files.phpで行われるので、Retina判定の箇所だけ追加してやります。
コアファイルを触るのはいやなので、ms-files.phpをテーマディレクトリにコピーして変更します。

<?php
/**
 * Multisite upload handler for Retina Images.
 * edited by eternitydesign.net
 *
 * @since 3.0.0
 *
 * @package WordPress
 * @subpackage Multisite
 */

define( 'SHORTINIT', true );

require_once('../../../wp-load.php');

if( !is_multisite() )
	die( 'Multisite support not enabled' );

ms_file_constants();


error_reporting( 0 );

if ( $current_blog->archived == '1' || $current_blog->spam == '1' || $current_blog->deleted == '1' ) {
	status_header( 404 );
	die( '404 — File not found.' );
}

$file = rtrim( BLOGUPLOADDIR, '/' ) . '/' . str_replace( '..', '', $_GET[ 'file' ] );

if (isset($_COOKIE['devicePixelRatio']) && intval($_COOKIE['devicePixelRatio']) > 1) {
	// Check if retina image exists
	$retina_file = pathinfo($file, PATHINFO_DIRNAME).'/'.pathinfo($file, PATHINFO_FILENAME).'@2x.'.pathinfo($file, PATHINFO_EXTENSION);
	if (file_exists($retina_file)) {
		$file = $retina_file;
	}
}

if ( !is_file( $file ) ) {
	status_header( 404 );
	die( '404 — File not found.' );
}

$mime = wp_check_filetype( $file );
if( false === $mime[ 'type' ] && function_exists( 'mime_content_type' ) )
	$mime[ 'type' ] = mime_content_type( $file );

if( $mime[ 'type' ] )
	$mimetype = $mime[ 'type' ];
else
	$mimetype = 'image/' . substr( $file, strrpos( $file, '.' ) + 1 );

header( 'Content-Type: ' . $mimetype ); // always send this
if ( false === strpos( $_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS' ) )
	header( 'Content-Length: ' . filesize( $file ) );

// Optional support for X-Sendfile and X-Accel-Redirect
if ( WPMU_ACCEL_REDIRECT ) {
	header( 'X-Accel-Redirect: ' . str_replace( WP_CONTENT_DIR, '', $file ) );
	exit;
} elseif ( WPMU_SENDFILE ) {
	header( 'X-Sendfile: ' . $file );
	exit;
}

$last_modified = gmdate( 'D, d M Y H:i:s', filemtime( $file ) );
$etag = '"' . md5( $last_modified ) . '"';
header( "Last-Modified: $last_modified GMT" );
header( 'ETag: ' . $etag );
header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', time() + 100000000 ) . ' GMT' );

// Support for Conditional GET
$client_etag = isset( $_SERVER['HTTP_IF_NONE_MATCH'] ) ? stripslashes( $_SERVER['HTTP_IF_NONE_MATCH'] ) : false;

if( ! isset( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) )
	$_SERVER['HTTP_IF_MODIFIED_SINCE'] = false;

$client_last_modified = trim( $_SERVER['HTTP_IF_MODIFIED_SINCE'] );
// If string is empty, return 0. If not, attempt to parse into a timestamp
$client_modified_timestamp = $client_last_modified ? strtotime( $client_last_modified ) : 0;

// Make a timestamp for our most recent modification...
$modified_timestamp = strtotime($last_modified);

if ( ( $client_last_modified && $client_etag )
	? ( ( $client_modified_timestamp >= $modified_timestamp) && ( $client_etag == $etag ) )
	: ( ( $client_modified_timestamp >= $modified_timestamp) || ( $client_etag == $etag ) )
	) {
	status_header( 304 );
	exit;
}

// If we made it this far, just serve the file
readfile( $file );

?>

編集箇所は、31〜37行目のRetina判定箇所とwp-load.phpをincludeしている箇所。
Retina判定はRetina Imagesから持ってくる。
wp-load.phpはパスを変更。

次に.htaccessのエディット。
filesから始まるURLのリライトしてるマルチサイト用の画像パス処理の箇所を以下のように変更します。

RewriteRule ^files/(.+) wp-content/themes/テーマディレクトリ/ms-files.php?file=$1 [L]

以上で、表示に関してはRetina対応完了です。

WordPressでのRetina画像の管理

Retina画像と通常の画像の2種類をアップするのは面倒なので、自分でアップする画像をRetina用として利用する処理をfunctions.phpに追加することにします。

画像のアップロード

abc.png(Retina画像)をアップし、そのファイル名に@2x(abc@2x.png)を追加。
アップされた画像を半分のサイズに縮小し、その画像が(元のファイル名であるabc.pngとして)メディアライブラリに登録される。
@2x画像は、WordPressに設定されたサムネイル画像などと同じような扱いになります。
(正確には、metaデータをDBに登録する処理は追加していないので異なる点もありますが)

以下のコードをfunctions.phpに追加します。

function generate_retina_image($file) {
	if(file_is_displayable_image($file['file'])) {
		$pi = pathinfo($file['file']);
		$x2_filename = $pi['dirname'] . '/' . $pi['filename'] . '@2x.' . $pi['extension'];
		copy($file['file'], $x2_filename);
		
		$stat = stat( dirname($x2_filename));
		$perms = $stat['mode'] & 0000666;
		@ chmod($x2_filename, $perms);
		
		list($width, $height) = getimagesize($file['file']);
		$new = image_resize($file['file'], $width * 0.5, $height * 0.5);
		if(is_file($new)) {
			rename($new, $file['file']);
		}
	}

	return $file;
}

add_filter('wp_handle_upload', 'generate_retina_image');

メディアライブラリから画像の削除

上記コードでアップロードでの対応は完了ですが、WordPressのメディアライブラリから画像を削除した場合に、@2x画像が削除対象にならずにサーバー上に残っていまいます。

その対策として以下のコードをFunctions.phpへ追加します。

function ed_delete_attachment($attach_id) {
	$meta = wp_get_attachment_metadata($attach_id);
	$sizes = $meta['sizes'];
	if(!sizes || !is_array($sizes)) {
		return $meta;
	}

	$uploads = wp_upload_dir();
	$pi = pathinfo($meta['file']);
	$x2_filename = trailingslashit($uploads['basedir']) . $pi['dirname'] . '/' . $pi['filename'] . '@2x.' . $pi['extension'];

	if(file_exists($x2_filename) && file_is_displayable_image($x2_filename)) {
		unlink($x2_filename);
	}
}
add_action('delete_attachment', 'ed_delete_attachment');

このコードで削除対象の画像に@2x画像があれば削除します。

表示に関しては、Retina.jsのほうが実装は簡単だし、PHPで処理することになるRetina Imagesよりもサーバーに優しいのは間違いないかと思います。
ただし、Retina環境で無駄な画像が読み込まれるというのは致命的な気がするので、今回の実装に関してRetina.jsの採用はまったく考えませんでした。

iPadでしか確認していませんが、やっぱりRetina対応はきれいですね。
しかし、制作サイドとしては単純に考えて倍となる工数への対応という点では、コスト面などへの対応や、googleさんの考えとは真逆となる読み込み完了時間の件など、複雑なところです。

ABOUT ME

名古屋を中心に、フリーランスでホームページ制作してます。 デザイン・サイト制作・WordPress案件・Flash制作、JavaScript(jQuery)と幅広く対応可能です。 案件のご依頼・ご相談はCONTACTよりご連絡ください。

CATEGORIES

ARCHIVES

TAGS