作成者別アーカイブ: kazy

[JS] 配置済み要素のbackground-image読み込みハンドリング

HTML/CSS


<style type="text/css">
	#bg {
		background: url("bg.jpg") no-repeat;
	}
</style>

<div id="bg">Lorem ipsum<div>

JavaScript



var getBgImage = (function() {

	var func;

	if ( document.documentElement.currentStyle ) {
		func = function( element ) {
			var val = element.currentStyle.backgroundImage;
			return val.replace( /(url\(|\)|")/g, '' );
		}
	} else if ( window.getComputedStyle ) {
		func = function( element ) {
			var val = document.defaultView.getComputedStyle( element, null ).getPropertyValue( 'background-image' );
			return val.replace( /(url\(|\)|")/g, '' );
		}
	}

	return func;
})();

var img,
src = getBgImage( document.getElementById( 'img1' ) );

if ( src !== '' ) {
	img = new Image();
	img.onload = function() {
		alert( '読み込み完了' );
	};
	img.src = src;
}

こういうことでいいのかな。

画像読み込み用のクラスみたいなのを作っておいて、そこに上記で得たパスやdocument.imagesの中身も合わせて全部ブチ込んで、ページ全体の画像読み込みの進捗を調べるってのはさすがに乱暴だろうか。

なんでこんなことで悩まなきゃならんのだ。



[JS] パラパラマンガ用JSを作ってみました

2013/02/16: 改修したバージョンがこちらにあります。
2012/10/16: スクリプトと記事の内容を修正しました。

さるお方の情報により初めて知ったのですが、jQueryの.animate()などで一度に複数の要素をアニメーションさせると、IE8環境では配置済みのgifアニメが停止しちゃうらしい。しかもIE7でもIE TesterでもIE9の8モードでもなく、ネイティブIE8でしか再現しないというなんともややこしい症状。

自宅にネイティブIE8環境が無いので自分の目では確認できていませんが、原因が多重に発生する再配置や再描画にあるのなら、jQueryだと$.fx.intervalを調整するなどしてその処理を減らしてやれば、ある程度までは緩和できそうです。しかし、それでもダメならこれはもうgifアニメを何か他のもので代替するしかないような…?負荷や処理落ちは別として、あくまでアニメーションの停止を回避する策としては。

前置きが長くなりましたが、ここでようやっと記事タイトルに繋がります。
簡単かつアナクロなスクリプトですが、ネタも無いことだし以下に公開します。


Flipbook.js – デモFlipbook.js – ダウンロード

使い方とか


まず flipbook.min.js を読み込んでおき、CSSでposition(relative or absolute)、width、heightを設定したID付き要素を配置。
<style>
	#screen {
		position: relative;
		width: 500px;
		height: 250px;
	}
</style>

<div id="screen"></div>

以下はJS記述例。
// 画像パスを格納した配列。格納した順で表示します。
var imgList = [ 'img0.jpg', 'img1.jpg', 'img2.jpg' ];
 
// インスタンスを作成。第一引数にターゲットID名、第二引数に画像のリストを渡します。
// 要素を確実にゲットできるよう、DOM構築完了後である必要があります。
var flipbook = new Flipbook( 'screen', imgList );
 
// 第一引数は要素を渡してもいいですし、
// var flipbook = new Flipbook( document.getElementById('screen'), imgList );
 
// jQueryオブジェクトを渡してもOKです。
// var flipbook = new Flipbook( $('#screen'), imgList );
 
// 必要に応じてハンドラメソッドを定義して下さい。
var handler = function( data ){
	console.log( data.type ); // イベント名
	console.log( data.frameNumber ); // 現在のフレームを示す数字
}
flipbook.onFirstFrame = handler; // 最初のフレームに達した
flipbook.onLastFrame = handler; // 最後のフレームに達した
flipbook.onUpdate = handler; // フレームが更新された
 
// 再生
flipbook.play();


メソッド


再生:
.play()
一時停止:
.pause();
停止:
.stop();
逆再生:
.reverse()
最初から再生(逆再生フラグが立ってる場合は最終フレームから逆再生):
.replay()
任意フレームにジャンプ:
.setFrame( frameNumber:Integer );

ゲッター / セッター etc.


再生スピード(ミリ秒。デフォルトは33):
.getSpeed():Integer
.setSpeed( milliSecond:Integer )
ループ再生(デフォルトはtrue):
.getLoop():Boolean
.setLoop( value:Boolean )
現在のフレーム:
.getCurrentFrame():Integer
フレーム総数:
.getTotalFrames():Integer
再生中?:
.isPlaying():Boolean
逆再生フラグ?
.isReverse():Boolean


アニメーション(パラパラマンガ)部分については、配置したimgのsrcを書き換えたりなどいろいろ試しましたが、スタイルシートでbackgroundを書き換えるという方法が一番安定していたのでそれで実装しました。

が、改めて確認するとIEでまともに動いてなかったため、imgを枚数分position:absoluteで重ねて配置してvisibilityを切り替える方法に修正しました。

ちょっと扱いにくくなるので採用しませんでしたが、負荷の軽さで言えばスプライトシートを用意してbackground-positionでアレする方法が多分一番じゃないかと思います。



[JS/CSS3] JavaScriptによるCSS3の実装判定とトランジション制御

CSSアニメーションで画像を切り替えるjQueryプラグインをちくちくと作り進めていて、現在はとりあえずの試作版が出来たところです。

DEMO

このプラグインの特徴のひとつに「CSS3に対応しない環境では、シンプル(=クロスブラウザ)なトランジションにフォールバックする」というのがあるのですが、これが少しやっかいで、試作版では姑息的な実装でお茶を濁しています。重要な部分なのでしっかりと作っておかないとならないのですが、いかんせんまだ知識に乏しいため、とりあえず現状で分かっている事などについて以下にまとめておこうと思います。

CSS3プロパティ実装の判定方法


前述の特徴を達成するには、まずtransitionやtransformなどといったCSS3プロパティの実装を確かめなければなりません。
そこで、環境に応じてベンダープレフィックスを付与したプロパティ名から機能テスト的に実装を判定するのですが、問題はブラウザやそのバージョンによっては、同じプロパティでも実装の範囲や仕様が異なるということです。(例: transformは2D変形のみ対応 など)

そのため、たとえばtransformの実装を調べる場合、
var supported = 'transform' in document.createElement( 'div' ).style;

といったような、プロパティを持つかどうかのみを判定する方法では、前述の例のような2D変形のみ対応している環境において、’rotateY(100deg)’など3D変形の値が使用された場合に正しく動作しなくなってきます。
なのでそれを回避しようとするなら
var style = document.createElement( 'div' ).style,
    supported = false;
if( 'transform' in style ) {
    style.transform = 'rotateY(100deg)';
    supported = !(style.transform === '');
}

みたいにして実際に値を入れてみる必要があります。
しかしこれだと、2D変形しか使わない場合でも(3D変形に対応していなければ)非対応と見なす処理になるため、それを考慮する場合、

var style = document.createElement( 'div' ).style,
    supported = 0;
if( 'transform' in style ) {
    style.transform = 'rotateY(100deg)';
    supported = style.transform === '' ? 1 : 2;
}

if( supported === 0 ) {
    // 非対応
} else if ( supported === 1 ) {
    // 2Dのみ対応??
} else {
    // 完全対応??
}

みたいにするなどして、プロパティごとにさらに切り分ける必要があります。
しかしこれでもまだやはり100%確実なものではないですし、あくまで「対応していると見なす」という程度でしかありません。

判定するプロパティとその値を限定すればもう少し信頼性を高くできそうですが、制作中のプラグインには「トランジションの種類を選択できる(バージョンアップで追加していく)」という仕様もあり汎用性を必要とするため、それも厳しいところです。

実用性を維持した上で確実に判定することは難しそうなので、色々な環境で調べたりテストしたりしながら、少しずつ理想に近づけていく必要がありそうです。


CSSトランジションの適用


試作版の実装判定とプロパティ名の取得は、前回の記事で作った関数を改修したものを使用しています。これに関しては、最近リリースされたjQuery v1.8.0ではcss()やanimate()にベンダープレフィックスが自動で付与されるようになったのでそれを利用すれば楽そうですが、最新版のjQueryが前提になるのもどうかと思うので、やはり拙くても自力でなんとかしておきたいところです。(といっても試作版はon()とかoff()とか使っているのでv1.7.0以上ありきなんですが)

以上を踏まえて、だいぶ端折ってますがCSSトランジションの適用は現状、以下のようになっています。

~
// 引数に取ったプロパティ名から実装を判定、
// 必要に応じてベンダープレフィックスを付与し文字列を返す
var cssProp = function( name, camelcase ) {
    ...
};

// 引数に取ったプロパティ名から実装を判定、真偽値を返す
// cssProp()のラッパー関数
var checkCSSSupport = function( name ) {
    ...
}
~
// 実装を判定
if ( !checkCSSSupport( ['transform-3d', 'transform-style', 'transition', 'opacity'] ) ) {
    // 非対応ブラウザ用トランジションへフォールバック
    return;
}

// CSSトランジションのコールバックと適用
$( elm ).on( cssProp( 'transitionEnd' ), function(){
    console.log( 'transition finished.' );
});
elm.style[cssProp( 'transform-style' )] = 'preserve-3d';
elm.style[cssProp( 'transition' )] = 'all 1s linear 0s';
// 状況によってうまく動かないのを回避するため遅延して適用
setTimeout(function(){
    elm.style[cssProp( 'transform' )] = 'rotateX(90deg)';
}, 1);
~

かなり泥臭く、そして面倒ですね…。
非対応ブラウザのフォールバックがある以上、CSS3の実装判定はどうしても必要ですが、その適用に関しては判定とは別に考えた方が良さそうです。たとえば以下のように適用できるようjQueryを拡張しておくとか。

~
// 実装の判定
~
// CSSトランジションを適用
$( elm ).nbAnimate({
    transform: 'rotateX(90deg)'
    }, 1000, 'ease-in', function(){
        console.log( 'transition finished' );
    }
});
~

jQuery.cssAnimateとかjQuery.transitとかくらい高い完成度で実現しようとすると大変そうだし荷が重いですが、コードの良し悪しは別として、制作中プラグインの用途に事足りる程度であればなんとかなりそうな気がします。


まとめ


やはりポイントはカオスな実装を如何にスマートかつ確実に判定するか、というところですね。CSS3アニメーションの扱いにくさに関しては、JS側で吸収できる部分が多いので大した問題ではない印象です。面倒臭いですけど。

とりあえずは好奇心が先行しているので気にしていませんが、実際のところこの手のコンテンツって、あくまで一般的な商用サイトにおける使用では、やはり時期尚早だと思いました。
最近の小難しい用語で言う、プログレッシブ・エンハンスメントやポリフィルといった考え方を前提に構成されている物であれば問題無いと思いますが、グレイスフル・デグラデーションみたいな「非対応環境には最低限の演出」という考え方では、クライアントの理解を得るのは中々難しいような気がしますし、第一多くの場合そこまでする必要が無いっていう。

そもそもCSS3自体まだ完成しておらず、実験的に実装されているに過ぎないわけですから、あまり完全を求め過ぎないようにしながら、今後もより良いアプローチを模索していきたいと思います。

この記事の続きはこちら→


[CSS/JS] CSSのプロパティ名をベンダープレフィックス付きで取得

※2012.05.19 : 記事の内容とスクリプトを修正しました。
※2012.08.27 : 以下のコードを改修したものがこちらにあります→


表題のような事がしたかったので、そのような関数を書いてみました。

var cssProp = function( name, camelcase ) {

	var index = (typeof camelcase === 'undefined') ? 0 : camelcase ? 0 : 1,
		cacheObj,
		element,
		prop;

	if ( !cssProp.cache ) {
		cssProp.cache = {
			element: document.createElement( 'div' ),
			vendor: (/webkit/i).test( navigator.appVersion ) ? ['webkit', '-webkit-'] :
				(/firefox/i).test( navigator.userAgent ) ? ['Moz', '-moz-'] :
				(/msie/i).test( navigator.userAgent ) ? ['ms', '-ms-'] :
				'opera' in window ? ['O', '-o-'] :
				'',
			transitionEvent: {
				webkit: 'webkitTransitionEnd',
				Moz: 'transitionend',
				ms: 'MSTransitionEnd',
				O: 'oTransitionEnd'
			}
		}
	}

	cacheObj = cssProp.cache;

	if ( cacheObj.hasOwnProperty( name ) ) {
		return cacheObj[name][index];
	}
	if ( name.toLowerCase() === 'transitionend' ) {
		return cacheObj.transitionEvent[cacheObj.vendor[0]];
	}

	element = cacheObj.element;
	prop = name.replace( /-./g, function( m ) {
		return m.charAt( 1 ).toUpperCase();
	} );

	if ( prop in element.style ) {
		cacheObj[name] = [prop, name];
		return cacheObj[name][index];
	}

	prop = cacheObj.vendor[0] + prop.replace( /./, function( str ) {
		return str.toUpperCase();
	} );

	if ( prop in element.style ) {
		cacheObj[name] = [prop, cacheObj.vendor[1] + name];
		return cacheObj[name][index];
	}

	cacheObj[name] = [undefined, undefined];
	return undefined;
};

サンプル

引数にプロパティ名を渡して呼び出すと、対応している場合は環境に応じたプロパティ名が、対応していない場合はundefinedが返ります。以下はIE9の場合の例。

alert( cssProp( 'transform' ) ); // 'msTransform';
alert( cssProp( 'transition' ) ); // undefined

キャメル記法でなく、ハイフン区切りな文字列で取得したい場合は、第二引数にfalseを渡します。

alert( cssProp( 'transform', false ) ); // '-ms-transform';

ちなみに「3D系のtransformに対応していない場合」とかは考慮してません…(perspectiveの実装を調べればOK?)。あと、transitionEndは実装の判定方法が分からなかったので、そこはすっ飛ばして常にベンダープレフィックスが付与された文字列が返ります。transitionに対応していればイコールtransitionEndも対応とみなして良いものなんでしょーか(‘A’ ?)

とりあえず自分で作ってみたかったというのが第一にあったので挑戦してみましたが、特に理由が無い限りはModernizrというHTML5やCSS3の実装に関する強力なライブラリがあるようなので、そちらを使わせてもらった方がいいですね。

うーん、もっと勉強しないとなー。