jQuery exFixed を動的な位置移動やリサイズ処理に対応させてみた
更新履歴
一ヶ月以上間のあいた久しぶりの更新ですが、今回もしつこく IE6 の position : fixed ネタです。
最近では HTML でリファレンスマニュアルなどを作る機会もあり、position : fixed で目次一覧を固定表示させたりしてます。そんな時 exFixed の出番となるわけですが、固定表示させた目次エリアに対し、さらにリサイズや位置調整機能などを付加しようとするといろいろと問題が生じます。
今回はそれらの問題を解決し、exFixed で動的な位置移動やリサイズ処理に対応させる方法について考えてみました。(以下の Demo のようなことができるようになります)
exFixed で位置移動やリサイズを行なおうとした場合の問題点
exFixed により固定表示された要素に対し、位置移動やリサイズを行なおうとした場合、以下のような問題が発生します。
top と left を指定した position : fixed の場合
- a) fixed 要素の位置移動後に window をスクロールさせると配置位置が元に戻ってしまう
- b) fixed 要素のサイズ変更後に window のサイズを変更するとサイズが元に戻ってしまう
bottom と right を指定した position : fixed の場合
- c) fixed 要素の位置移動ができない
- d) fixed 要素のサイズ変更をすると、top / left 位置を軸にサイズ変更されしまう
原因と対策
原因と対策を考えてみます。
a) fixed 要素の位置移動後に window をスクロールさせると配置位置が元に戻ってしまう
原因は以下のように expression ベースで固定配置を実現してるため。
element.style.setExpression('top', 'this.exFixedTop+(document.body.scrollTop||document.documentElement.scrollTop) + "px"');
対策としては、移動後の位置座標で再度、expression の指定をし直す必要がある。
b) fixed 要素のサイズ変更後に window のサイズを変更するとサイズが元に戻ってしまう
fixed 要素のサイズ・位置指定が(%)単位の場合、window の width / height に対して実寸を求める必要がある。そのため expression で位置変更される前の初期位置の値を保持しておき、window リサイズにはこの値を基準に算出した実寸値を fixed 要素に適用するという実装になっているので、サイズ変更をしてたとしても、元サイズに戻ってしまう。
対策としては、位置移動やリサイズがあった場合は、初期設定値の値を更新する必要がある。
c) fixed 要素の位置移動ができない
上記エントリで触れてるとおり、スクロール時のガタつきを無くすには top / left ベースで配置する必要があるので、exFixed の実装では bottom / right で指定された場合は、top / left ベースに置換して配置するようにしている。固定位置の配置処理は expression ベースで行なっているため、後から css メソッド等で bottom / right ベースで位置移動しようとしても、expression による top / left ベースの位置指定の方が強く作用し、位置移動ができない。
対策としては以下の2つが考えられる。
top / leftベースに置換しない方法
top / left 方式のように expression による位置調整は行わず、下記のような filter 指定で固定表示を実現する。
element.style.setExpression('', 'this.style.filter=""');
たったこれだけで bottom / right ベースな position:fixed が実現できてしまう!ガタつきの方も多少ちらつく程度!
サンプルページ
注意点としては、上記のような記述だと $(element).css('opacity',0.5) 等の透過指定が無効になってしまうということ。透過指定を使用したい場合は下記のように filter で指定する必要がある。
element.setExpression('', 'this.style.filter="alpha(opacity=50 ,finishopacity=50 ,style=1)"');
リサイズや位置移動の際にのみ right / bottom 配置に戻す方法
前述の方法だと多少とはいえスクロール時にちらつきが発生するので、リサイズや位置移動をさせないケースなどでは性能劣化に等しい。
なので基本 top / left 置換方式でガタつきを抑止しておき、リサイズや位置移動の開始時に bottom / right 配置に変換し、処置完了後に top / left 置換方式に戻すという方法がより良い。
ネックなのは「処置開始前後に配置変換処理の呼び出しを忘れず記述しなければならない」というコーディングルールをプラグイン利用者に強要してしまうこと。
d) fixed 要素のサイズ変更をすると、top / left 位置を軸にサイズ変更されしまう
これは c) で述べてる通り top / left 置換方式を採用してるため。c) が解決すれば d) も解決する。
exFixed を動的な位置移動やリサイズ処理に対応させてみた
exFixed で動的な位置移動やリサイズ処理を行なうには、以下の前提があります。
具体的には以下のように記述します。
var api = $('div.fixed').exFixed({ api : true, dynamicFixed : true, right : 100, bottom : 100 });
api パラメータに true を指定すると exFixed() メソッドの返却値が API オブジェクトになります。また、dynamicFixed パラメータを指定することで、動的な位置移動やリサイズ処理が行なえるようになります。
ちなに API オブジェクトは jQuery exAPI という jQuery プラグインをベースに生成してます。API オブジェクトの基本的な使い方については、以下を参照ください。
jQuery ライクなプラグイン API の定義方法を考えてみる - Cyokodog::Diary
単純な リサイズ / 位置移動
api.getTarget().click(function(){ api.fixedOpen(function(){ api.fixedClose({ right : '25%', bottom : '25%', width : '50%', height : '50%' }); }) })
getTarget() メソッドで exFixed の適用対象となった要素が取得できます。例では対象要素をクリック時に、リサイズと位置移動を行なっています。fixedOpen() メソッドで fixed 要素を変更可能な状態にし、fixedClose() メソッドで fixed 要素の位置、サイズを確定し、変更可能状態を閉じます。
Demo
animte メソッドを併用する
api.getTarget().click(function(){ api.fixedOpen(function(){ var size={ right : '25%', bottom : '25%', width : '50%', height : '50%' }; api.getTarget().animate(api.getFixedSize(size),function(){ api.fixedClose(size); }); }) })
getFixedSize() メソッドを使用し animate メソッド用に実寸値を求めます。fixed 要素の変更処理が終わるタイミングで fixedClose() メソッドを実行する必要があるので、animate のコールバック関数内で fixedClose() メソッドを実行してます。fixedClose() メソッドには論理値サイズを渡します。
Demo
複数要素に対して処理する(CSS でサイズ指定)
#a{ width:250px; height:250px; } #b{ width:200px; height:200px; } #c{ width:150px; height:150px; } #d{ width:100px; height:100px; }
$('#a,#b,#c,#d').exFixed({ api : true, dynamicFixed : true, right : 100, bottom : 100 }) .each(function(idx,api){ api.getTarget() .css('z-index',idx) .click(function(){ api.fixedOpen(function(){ var size = { right : '25%', bottom : (idx * 25) + '%', width : '50%', height : '24%' }; api.getTarget().animate(api.getFixedSize(size),function(){ api.fixedClose(size); }); }) }) })
複数要素に対して処理する(パラメータ でサイズ指定)
$('#a,#b,#c,#d').each(function(idx){ var api = $(this).css( 'z-index' , idx ).exFixed({ api : true, dynamicFixed : true, width : 250 - ( idx * 50 ), height : 250 - ( idx * 50 ), right : 100 + ( idx * 25 ), bottom : 100 + ( idx * 25 ) }); api.getTarget().click( function(){ api.fixedOpen( function(){ var size = { right : '25%', bottom : ( idx * 25 ) + '%', width : '50%', height : '24%' } api.getTarget().animate( api.getFixedSize(size) , function(){ api.fixedClose( size ); }); }) }); });
再クリックで元の位置に戻るようにする
$('#a,#b,#c,#d').each(function(idx){ var orgSize; var size = orgSize = { width : 250 - (idx * 50), height : 250 - (idx * 50), right : 100 + (idx * 25), bottom : 100 + (idx * 25) } var api = $(this).css('z-index', idx).exFixed($.extend({ api : true, dynamicFixed : true },size)); api.getTarget().click(function(){ api.fixedOpen(function(){ size = (size == orgSize ? { right : '25%', bottom : (idx * 25) + '%', width : '50%', height : '24%' } : orgSize); api.getTarget().animate(api.getFixedSize(size), function(){ api.fixedClose(size); }); }) }); });
初期値をキャッシュしておきクリックの都度、配置位置を切り替えてます。
Demo
四隅に配置したパターン
$('#a,#b,#c,#d').each(function(idx){ var orgCss = {}, newCss = {}; var hPos = (idx % 2) ? 'right' : 'left'; var vPos = (idx < 2) ? 'top' : 'bottom'; orgCss[hPos] = orgCss[vPos] = 0; newCss[hPos] = newCss[vPos] = ((idx + 1) * 10 ) + '%'; orgCss['width'] = orgCss['height'] = 100; newCss['width'] = newCss['height'] = (80-idx*20)+'%'; var css = orgCss; var api = $(this).exFixed( $.extend({ api : true, dynamicFixed : true }, css )); api.getTarget().click(function(){ css = ( css == orgCss ? newCss : orgCss ); api.fixedOpen(function(){ api.getTarget().animate(api.getFixedSize(css),function(){ api.fixedClose(css); }); }); }); });
最後に
fixed 要素の変更の際には fixedOpen() や fixedClose() 、値の指定には fixedSize()メソッドをとコーディングに制約が付きますが、クロスブラウザな記述を考慮する必要がなくなるので、動的に position:fixed を扱いたい場合はそれなりに便利かと思います。