Firefox3.5のposition:fixedとアメブロのユーザー定義CSS

これは2009/10/16にAmebloに投稿した記事です。

アメブロ(ameblo)でのブログ記事の執筆/編集

amebloでは、入力された記事から改行を全て取り除く(標準エディタでのHTMLタグ編集)ので、
<pre>タグを使う場合には多少注意が必要ですが、
利用可能なタグの種類の多さとユーザー定義CSSが利用可能という事で、意外にも結構な自由度があります。
このブログデザインもBlogara:ブロガラと同様な外見にする事が出来るなど、
カスタマイズ好きなユーザーにもそこそこ面白いブログでは無いかと思われます。


今回ブログを書くにあたっては、amebloの編集フォームに書きプレビューで確認するのではさすがにエライ面倒なので、
実際のブログ記事のHTMLを保存し、それから不要な要素を取り除いたファイルをブログテンプレートとし、
それをローカルの環境で編集/表示確認する事でブログ記事を書いています。

これにより、使い慣れたエディタ(aptana)上での作業が可能という事で、
入力フォームへの記述と比較すると、エディタの各種支援機能が使える為作業効率が格段に上がり、
HTML/CSSの編集結果も素早く確認出来ますので、非常に快適な執筆が可能になります。

が、ローカル環境でameblo上での表示状態を完全に再現出来るとは限らず、
実際にamebloで表示させてみたら意外な個所に違いが出てくる場合も有ります。
そこで今回はその差異から生じた問題に対応します。

画面をスクロールさせると非常に重い

Webブラウザ

現在、開発環境と通常のWebブラウジングにはFirefox3.5を利用しています。
Firefoxは、FirebugNoScript,Greasemonkeyなどの有用プラグインがあり、
CSS3HTML5の仕様(候補)のいくつかを既に実装し、
IEのように遅くは無く、またGoogleChromeのような速過ぎて開発環境としては逆に微妙なものでも無いという、
非常にバランスが取れ使い勝手の良いブラウザです。

このブログもFirefox3.5でチェックを行い、その後Chrome3.0,Opera10,IE6/8などで確認を行っていますので、
前回の記事も、問題無い事をローカル環境のFirefox3.5上で確認してからamebloに投稿するという流れでした。

が、いざameblo上にアップされた記事をFirefox3.5で見てみると、
明らかに画面スクロールが遅くガタガタします(要するに描画が重い)。
IEなど他のブラウザで確認してみると問題無いという事で、
それに関しては完全に思い当たる節があるので、まずその点を疑います。


border-radius/box-shadow

Blogaraやこのブログでは、
border-raduis(-moz-border-radius)、box-shadow(-moz-box-shadow)、text-shadowという、
CSS3の仕様採用候補(text-shadowはCSS2の仕様)であり、
今後各ブラウザでサポートが進むであろう機能を多用しています。

こちらで確認出来るいくつかのブラウザの中では、
それらを全くサポートしていないブラウザ(IE6/8)や、一部サポートするブラウザが有りますが、
Firefox3.5ではその3つの全てが表示されます。
# というよりFirefox専用の記述を追加して対応させているというのが実際の所であって、
# -webkitをprefixとして指定すればChromeでも全て表示されます。

その為、それらの表示による追加処理が発生するFirefox3.5で重いというのは予想出来た事でもあります。
そこでこれら3つのプロパティをユーザー定義CSSから削除して確認してみたところ、
スクロールした時の表示速度がある程度改善し、
さらに、明らかに描画が遅れていた<pre>を、
overflow:autoでは無く、overflow:hiddenに設定する事でも幾分の改善が見られます。

が、やはりまだローカル環境より明らかに遅い状態が明確に判ります。


ローカル環境との違い

と、この時点になってようやく、何故ローカル環境では問題無いが、
ameblo上では重いのかという点に注目し、それの原因を追求する事にします。

ローカルで編集する為のブログテンプレート作成では、不要な要素(画像/JavaScript)を削除しましたが、
それと同時に、特に必要では無いだろうと思われるファイル(CSS)はローカル環境では読み込んでいませんでした。

そこで改めて、ローカル環境の表示とameblo上の表示を見比べてみると、
明らかな違いと、いかにもスクロールに影響を及ぼすであろう要素があります。
それが、ul#amebaBarで指定されるposition:fixedな最上部固定のアメブロメニューバーです。

試しに最上部固定のメニューバーをローカル環境でも表示してみた所、明らかなスクロール速度低下が発生します。
原因は判明しましたので、次はその対策を行います。


position:fixedとFirefox3.5

という訳で早速、firefox fixed positionでググってみたところ、
position:fixedなdivエレメントが表示されていると、スクロールした時にチラつく現象が起こる問題が認識されており、
既に解法も判明しているがFirefoxの安定版にはまだ実装されていない模様です。
Fixed Positioned DIV flickers on page-scroll when set for Overflow: Visible

こちらでも、fixedなdivエレメントを含むページを作成し、
上記ページに対処法として書かれている当該エレメントにoverflow:autoを設定してみましたが、
チラつく症状は改善しましたが、速度的には劇的には改善しません。

アメブロメニューバーはdivでは無くulですが、同じブロックレベル要素という事で、
同じ様な問題があるのでは無いかと勝手に判断し、
今回の問題に関しては、簡単で安直な対応としてposition:fixed指定の変更を行う事とします。

CSSセレクタの詳細度

position:fixedが指定されているのは、ユーザー定義CSSでは無くameblo設定のCSSですので変更は出来ません。
が、CSSではより詳細度の高い記述を優先するという特性を利用し、
ユーザー定義のCSSでamebloシステム設定のCSSを上書きします。

#mainIndex ul#amebaBar {
position: absolute;
top: 0px;
margin-top: 0px;
}

これを利用すればアレやコレや不要と思われるエレメントを非表示にするような事も可能ですが、
アカウント停止になる恐れが高いので止めておいた方が無難です。
今回は固定表示のメニューバーを非固定表示にしただけなので、さすがに大丈夫だとは思いますが。

結果

position:fixed設定を変更する事で、ameblo上でもストレス無いブログ閲覧が可能になりました。

CSS3に関しては、
レイアウトの為にdivエレメントを多用する事は残念ながら暫く続きそうですが、
特に、所謂角丸デザインの為に無駄なエレメントや画像を追加する事を回避出来るborder-radiusは、
早いところ多くのブラウザでサポートしてもらいたいものです。


余談ですが、
ユーザー定義CSSを許可する場合には、システム的に必ず表示されるべきエレメントを保護する為に、
ユーザー記述の!important宣言は許可しない/強制的に削除すべきなのではないかと思われます。

便利ツール

Firebug - 指定のエレメントに何のCSSプロパティが適用されているかが簡単に判り、変更してみる事も可能

参考資料

O'Reilly CSS完全ガイド - Amazon(+associate)
Fixed Positioned DIV flickers on page-scroll when set for Overflow: Visible

PHPでのカタカナ/ひらがなのローマ字変換

これは2009/10/15にAmebloに投稿した記事です。

背景

ブログ情報サイトBlogara:ブロガラでは、あるテーマに関連するブログや関連サイトの情報の管理を行いますが、
当然のように、まず数多くのブログの情報をDBに登録する必要があります。

勿論、キーボードで名前やブログURLなどを一文字づつ入力していたのでは気の遠くなるような作業になりますので、
WebAPIを使った情報取得や、管理画面での入力フォームの項目へのコピペでの記入がメインとなりますが、
フォームの入力値の自動変換でも書いている通り、
情報ソースの文字形式とBlogaraでの文字形式が異なるケースも有り、そのままでは利用出来ない事が多々あります。
そのような場合には、可能であればサーバー/クライアント側スクリプトで文字列変換処理を行い、
Blogaraでの内部管理に適した形式に変換していますが、
機能として実装されていない変換が必要な場合には手間の掛かる手動での入力で行う事になります。

そこでここでは、その実装されていなかった変換機能の1つである
名前のふりがな(カタカナ又は平仮名)からローマ字表記の名前への変換を、
WebAPIと連携し易いサーバー側のPHPでの実装を行います。

指針

  • 諸般の都合から、動作環境はクロージャが使えるPHP5.3.xでは無くPHP5.2.xとなる。
  • 文字列はUTF-8として扱う。
  • 変換前の文字列は全角カタカナ(又全角カタカナに変換出来る文字)と空白で構成された文字列であり、それ以外の文字は考慮しない。
  • 変換した後のローマ字文字列は、現状では単に補足情報としてのみ利用する。
  • ローマ字文字列を、変更する事で他に追加作業を要するような影響を及ぼす用途には使わない。
  • 変更は、管理画面の入力フォームからいつでも可能。

このように、今回は氏名のフリガナの文字列をローマ字(基本的にはヘボン式)に変換する事を目的とした処理であり、
また上記のような特徴がある為、クリティカルな用途には利用しない事に加え、
カタカナとalphabetが混在するような処理が複雑化するケースには対応しないので、処理はかなり簡素化出来ます。

今回はPHPでの実装となりますが、
既にBlogaraでは各種文字列操作をstaticメソッドとして持つクラスが存在しますので、
カナ->ローマ字変換関数も、そのクラスのstaticメソッドとして追加する事にします。
その為、独立したクラスでは無く、他の多数の機能を抱えるクラス内メソッドとしての実装になりますので、
正直あまりコード量が多くなるのは望ましくありません。

という訳で、要するに今回は多少アレでも(コード量的に)軽い方が良いという事になります。

文字列変換

PHPでの文字列変換となると、
豊富に用意された各種文字列操作関数や正規表現(preg/ereg)を使う事が考えられます。

ここでBlogaraの文字列操作クラスに既に実装している機能とコードを振り返って確認してみると、
記号(%$など)の半角/全角相互変換メソッドが有り、
その変換では半角記号と全角記号を配列として持ち、str_replaceを利用した一括変換を行っていますので、
ローマ字変換でも、それと同様の処理を行う事を第一候補とします。

まず、日本語処理という事で、
ひらがなカタカナ変換や半角/全角カタカナ変換もあるmbstring系の関数を見てみましたが、
残念ながらローマ字変換は無い模様です。

となると次に行うのは調べ物の大定番である検索サイト利用ですが、
やはりそこそこ需要がある機能のようで、PHP ローマ字で検索を行うといくつかヒットします。
が、一文字づつparseしていくという丁寧でより正確性を求めた実装が多いようですので、
今回目指すstr_replaceによるお手軽路線とは方向性が微妙に異なる模様です。

という訳で早速str_replaceによる実装に移ります。

str_replaceを使った一括変換と配列の並び

str_replaceについての解説はネット上に腐るほど有りますので割愛しますが、
今回のような処理では、変換候補となる第一引数の配列の要素の並びが重要となります。

str_replaceでは配列の先頭から順に変換を行う為に、
例えば、変換配列において"キ"が"キャ"より前にある場合には、
"キャット"という文字列が"kyatto"では無く"kiャtto"と変換され、"ャ"が中途半端な文字として残ってしまいます。
よって一文字単位の変換候補は配列の最後に押しやってしまう事が必要です。

と、このような注意点がある以外は、上記指針で書いたような前提が有り単純な変換しか行わない今回の実装では、
変換元のカナと変換後のローマ字の変換配列を作成するのが多少面倒なだけで、
str_replaceの一行で済む処理ですが、
実際には後述する前処理や、次節に書くような個別の特殊ケースに対応する必要があります。


情報ソースによって異なる文字形式に対応する為に、まず変換前の前処理を行います。
変換元の文字中の半角カタカナを全角カタカナに変換し、またひらがなから全角カタカナへの変換処理を行い、
手間が掛からない範囲で可能な限り出来る自動変換を適用します。

その後に、str_replaceでの変換。今回はコードの分量を少なくするという目的の為に、
ここで利用する変換候補配列が長大(要素数169)になっていますが敢えて気にしません。:)
str_replaceを使った変換はこれだけでOKですが、
追加処理としてローマ字表記特有のケースを考慮する必要もあります。

// カタカナ->ローマ字変換
// $kkはカタカナの変換候補配列、$kkrはそれに対応したローマ字の文字配列
$kk = array( 'キャ','キュ','キョ',... // 長いので省略
$kkr = array( 'kya','kyu','kyo',... 
$str = str_replace( $kk, $kkr, $str );

カタカナ->ローマ字変換での追加処理

オ段の長音

人名のローマ字表記を行う時に考えるのは、
例えば太田(オオタ)という名前は、Oota/Ōta/Ohta/Otaのどれになるのか、
あるいは太郎(タロウ)はTarou/Tarō/Taroのどれかという点ですが、
今回の変換ではOta/Taroのような表記に統一します。

が、統一すると一言で言っても実際は中々面倒な処理となります。
単純に下記のような変換を適用する事でも一応は望む結果を得られますが、
望まない副作用のような変換も行ってしまう事が問題となります。

$str = preg_replace( '/o[ou]/u', 'o', $str );
// "おおた たろう[oota tarou]" -> "ota taro" となり、このような単純なケースでは望む結果になりますが、
// ouが連続した文字列の場合には、
// "とおの よおう[toono yoou]" -> "tono you"
// "おうおおおじ[ouoooji](王大叔父)" -> "oooji"
// "おうむをもううがおそう[oumuwomouugaosou](オウムを猛雨が襲う)" -> "omuwomougaoso"
// "かおをおおう おううさん[kaowooou ouusan](顔を覆う 奥羽さん)" -> "kaowoo ousan"
// と、訳判らない文字列となるので、
// とりあえずの大味な回避策として下記のような変換にしていますが、
// とても一般文章に適用出来るものでは有りません。
$str = preg_replace( '/(\b|[^ou])o[ou](\b|[^ou])/u', '$1o$2', $str );

撥音(ン)の扱い

ヘボン式のローマ字表記では、基本的に"ん"はnですが、その後に続く子音がbpmの場合には、
nでは無くmとなります。

// トンボ->tombo、タンポポ->tampopo
$str = preg_replace( '/n([bpm])/u', 'm$1', $str );

促音(ッ)の扱い

カタカナ"ッ"で表記される促音のローマ字表記は、直後に置かれる子音を重ねて書く事で表現します。
が、カタカナの変換配列に"ッ"を含む変換候補を記述すると、さらにアホのように変換候補配列が巨大化しますので、
これは処理的に解決する事にします。

$str = preg_replace_callback( '/ッ+(.)?/u', 'kk2rProc', $str );

function kk2rProc( $ma ) {
  $char = $ma[1];
// 直後の文字がアルファベットの子音の小文字の場合だけが処理対象
  if ( !preg_match('/^[a-z]$/u',$char) || preg_match('/^[aiueo]$/u',$char) ) return $char;

// chiなどcの場合には例外的にcchiでは無くtchiとなる
  if ( $char == 'c' )
    return 'tc';
  else
    return $char.$char;
}

長音符(ー)の扱い

今回は、長音符(ー)や単独の捨て仮名(ァィゥェォ)は無視するので、それらの文字は捨てるだけになりますが、
ちなみに長音符に対応するとしたら下記のようなコードになります。

// 母音の後にhを置く表記。 オオタ->ohta
$str = preg_replace( '/([aiueo])ー/u', '$1h', $str );

// 伸ばす母音にマクロンを使う表記。 オオタ->ōta、フーン->fūn
$macron = array( 'a'=>'ā', 'i'=>'ī','u'=>'ū','e'=>'ē','o'=>'ō');
$str = preg_replace( '/([aiueo])ー/ue', '$macron["$1"]', $str );

元の文字列に半角英字小文字が含まれている場合の対応

前提として、カタカナ/空白以外の文字が含まれる事を考慮していませんが、
半角英字小文字が含まれた文字列も対象とするならば、
下記のような処理をカタカナ->ローマ字変換のstr_replaceと追加処理の前後に行います。

ただ、このような処理をフリガナという文字数の限られた文字列に対してでは無く、
一般文章のように長文になる事も十分予想出来る文字列を対象に行うのなら、str_replaceでの一括変換では無く、
for(あるいはforeach)ループ内で1文字づつparseする処理にした方が良いと思われます。
# 既に追加処理中で何度も使っていますが、
# str_replace/preg_replaceという文字列全体を対象とする処理を多数行うのは効率的にかなり微妙なので。

// 半角英字小文字を退避
// 実際にはこの処理の前に、元の文字列中に%##abc##%のようなフレーズが(偶然)存在する場合の前処理が必要。
$str = preg_replace( '/([a-z]+)/u', '%##$1##%', $str );

// カタカナ->ローマ字変換と追加処理
$str = str_replace( self::$kk, self::$kkr, $str );
$str = preg_replace( '/n([bpm])/u', 'm$1', $str );

// 元の文字列の半角英字小文字を復元
$str = preg_replace( '/%##([a-z]+)##%/u', '$1', $str );

# 2010/05/19追記
# わざわざ####を付けて退避させるくらいなら、/[ァ-ヴ]+/のように変換対象となる部分文字列を抜き出し、
# それに対してpreg_replace_callbackで指定した関数で変換するようにした方が良いのでは。
# と、半年振りに自分で書いたブログ記事を見て思った次第。

実装

カタカナ->ローマ字変換処理だけを抜き出した物を付記します。
コメントアウトをしている前処理のひらがな->カタカナ変換や、後処理の特定文字コードの抽出は、
各利用者の方で好き勝手に実装して下さい。
特定文字コードの抽出処理を行っていない状態では、ァィゥなどの変換非対象カナ文字がゴミとして残ります。

このコードはMITライセンス形態で配布しています。
言わずもがなですが、これを利用した事により不利益を被ったとしても当方は一切関知しません。

// copyright (c) 2009 ksy AT blogara.jp <http://blogara.jp/>
// This script is freely distributable under the terms of an MIT-style license.

define( 'CNV_MD_ALPHA', 0x02 );
define( 'CNV_MD_SPACE', 0x08 );
define( 'CNV_MD_ALSP', 0x0a );

class TextUtil {
  protected static $kk = array( 'キャ','キュ','キョ','シャ','シュ','ショ','チャ','チュ','チョ','ニャ','ニュ','ニョ','ヒャ','ヒュ','ヒョ','ミャ','ミュ','ミョ','リャ','リュ','リョ','ギャ','ギュ','ギョ','ジャ','ジュ','ジョ','ヂャ','ヂュ','ヂョ','ビャ','ビュ','ビョ','ピャ','ピュ','ピョ','イェ','ウァ','ウィ','ウェ','ウォ','ウュ','キェ','クァ','クィ','クェ','クォ','クヮ','シェ','チェ','ツァ','ツィ','ツェ','ツォ','ツュ','ティ','テュ','トゥ','ニェ','ヒェ','ファ','フィ','フェ','フォ','フィェ','フャ','フュ','フョ','ミェ','リェ','ヴァ','ヴィ','ヴェ','ヴォ','ヴィェ','ヴャ','ヴュ','ヴョ','ヴヰ','ヴヲ','ギェ','グァ','グィ','グェ','グォ','グヮ','ゲォ','ゲョ','ジェ','ディ','デュ','ドゥ','ビェ','ピェ','ア','イ','ウ','エ','オ','カ','キ','ク','ケ','コ','サ','シ','ス','セ','ソ','タ','チ','ツ','テ','ト','ナ','ニ','ヌ','ネ','ノ','ハ','ヒ','フ','ヘ','ホ','マ','ミ','ム','メ','モ','ヤ','ユ','ヨ','ラ','リ','ル','レ','ロ','ワ','ヲ','ン','ガ','ギ','グ','ゲ','ゴ','ザ','ジ','ズ','ゼ','ゾ','ダ','ヂ','ヅ','デ','ド','バ','ビ','ブ','ベ','ボ','パ','ピ','プ','ペ','ポ','ヰ','ヱ','ヲ','ヴ','・' );
  protected static $kkr = array( 'kya','kyu','kyo','sha','shu','sho','cha','chu','cho','nya','nyu','nyo','hya','hyu','hyo','mya','myu','myo','rya','ryu','ryo','gya','gyu','gyo','ja','ju','jo','ja','ju','jo','bya','byu','byo','pya','pyu','pyo','ye','wa','wi','we','wo','wyu','kye','kwa','kwi','kwe','kwo','kwa','she','che','tsa','tsi','tse','tso','tsyu','ti','tyu','tu','nye','hye','fa','fi','fe','fo','fye','fya','fyu','fyo','mye','rye','va','vi','ve','vo','vye','vya','vyu','vyo','vi','vo','gye','gwa','gwi','gwe','gwo','gwa','geo','geyo','je','di','dyu','du','bye','pye','a','i','u','e','o','ka','ki','ku','ke','ko','sa','shi','su','se','so','ta','chi','tsu','te','to','na','ni','nu','ne','no','ha','hi','fu','he','ho','ma','mi','mu','me','mo','ya','yu','yo','ra','ri','ru','re','ro','wa','wo','n','ga','gi','gu','ge','go','za','ji','zu','ze','zo','da','ji','zu','de','do','ba','bi','bu','be','bo','pa','pi','pu','pe','po','wi','we','wo','vu',' ' );

  public static function convertKKtoR( $str, $bHGtoKK=TRUE, $mode=CNV_MD_ALSP ) {
    if ( !$str ) return $str;

// 半角カタカナから全角カタカナへの変換
//    $str = self::convertHtoF( $str, CNV_MD_KKANA );

// ひらがなから全角カタカナへの変換
//    if ( $bHGtoKK )
//      $str = self::convertHK( $str, CNV_HGTOKK );
    $str = str_replace( self::$kk, self::$kkr, $str );
    $str = preg_replace( '/(\b|[^ou])o[ou](\b|[^ou])/u', '$1o$2', $str );
    $str = preg_replace( '/n([bpm])/u', 'm$1', $str );
    $str = preg_replace_callback( '/ッ+(.)?/u', 'TextUtil::convKK2toR', $str );

// 有効とする文字コード以外を削除
// 半角英字小文字を含む文字列を対象にする場合には、カタカナ->ローマ字変換の前にも行う方が良い
//    $mode |= CNV_MD_ALPHA;
//    return self::extractCode( $str, $mode );
	return $str;
  }

  protected static function convKK2toR( $ma ) {
    if ( !$ma || !isset($ma[1]) ) return '';
    $char = $ma[1];
    if ( !preg_match('/^[a-z]$/u',$char) || preg_match('/^[aiueo]$/u',$char) ) return $char;

    if ( $char == 'c' )
      return 'tc';
    else
      return $char.$char;
  }
}

今回は、単語のみで構成された名前の振り仮名の変換を目的とした処理ですので、
これを助詞などが含まれてくる一般文章に適用した場合には、かなり精度の怪しい文字列となると思われます。

ちなみに実際にBlogaraに実装されているフリガナからローマ字に変換する処理では、
この後処理として、文字列前後のホワイトスペースを削除し、連続するスペースを1つの半角スペースに変換、
Ota Taroのように単語境界の先頭を大文字化、スペースを挟んで2単語がある場合には、
Taro OTAというように苗字と思われる単語を全て大文字にし順序を逆にするまでが自動変換処理となります。

参考資料

PHPマニュアル: str_replace
PHPマニュアル: preg_replace_callback - staticメソッドの利用法
Wikipedia: ローマ字