jQueryを使わないでアニメーションしたくはないでしょうか
別に興味ないですか。私は興味あります。
ブレイクスルーJavaScript フロントエンドエンジニアとして越えるべき5つの壁―オブジェクト指向からシングルページアプリケーションまでを注文しました。
jQueryその先へ、とかそういう文言が今熱いのか。
積ん読たまってて読めるかまったくわからないんですけど。
書籍とは離れますが、jQuery を使用しないでどこまでアニメーションに耐えられるのか、ちょっとやってみました。
https://github.com/tkdn/postjQueryAnimate
※ jQuery は2~使ったので、IE10~,Chrome,Safari,Firefoxいずれも最新くらいがターゲット
Velocity.js という選択
最初はCSSアニメーションでなんとかと思ってたんですが、途中で挫折、jQueryの.animate
を使わないでFPSを設定、数値の変更なんか自前でやってられないのと、パフォーマンスが優れているということで、Velocity.jsを選びました。
jQueryのプラグインであるかのような誤解が生まれていますが、スタンドアローンとしても使えます。今回は公式にもあるような使い方で、browserify で。
2つのソース
jQueryだとこんな感じ
$(function() {
var action = (function(jqObject) {
var content = jqObject.next('.content');
var contentParent = content.parent();
if(contentParent.hasClass('active')){
content.slideUp('250',function(){
contentParent.removeClass('active');
});
} else {
contentParent.addClass('active');
content.slideDown('250');
}
});
$('.jq-accordion').on('click', function(event) {
event.preventDefault();
action($(this));
});
});
action
でアコーディオンに関する動きをひとまとめにしてます。動きをバインドさせたい要素(jQueryオブジェクト)を引数として、
- 隣接する次要素
.content
をアコーディオンの内容とし、これをアニメーション(slideUp,slideDown) - その親要素(リポジトリでは
li
)に活性非活性のクラスactive
をtoggleさせ、UIとしての状態を明示、アクションのshow/hideを決めてる - 最後に
.jq-accordion
の要素にactionをバインドさせてる
みたいな感じです。ありがちですね。
これをVelocity.jsとブラウザネイティブでやる
Velocity.js をモジュールとしてロード、グローバル空間にexportしてやる。npm で検索して出てくる、velocity.jsは全く別物なので注意。velocity-animate
が本物。
リポジトリではnpm installしたら必要なの全部落ちてきます。
var Velocity = require('velocity-animate');
魔法のrequireでOK。あとはbrowserifyしてあげるだけ。
ソース
document.addEventListener('DOMContentLoaded', function(event) {
var accodionContents = this.querySelectorAll('.js-accordion + .content');
for (var i = 0; i < accodionContents.length; i++) {
var height = accodionContents[i].offsetHeight;
accodionContents[i].setAttribute('data-height', height);
accodionContents[i].style.height = 0;
}
var action = (function(elemObject){
var content = elemObject.nextElementSibling;
var contentParent = elemObject.parentElement;
var contentHeight = content.getAttribute('data-height');
if(contentParent.classList.contains('active')){
contentParent.classList.remove('active');
Velocity(content, { height: 0 }, 250);
} else {
contentParent.classList.add('active');
Velocity(content, { height: contentHeight }, 250);
}
});
var actionElem = this.querySelectorAll('.js-accordion');
for (var i = 0; i < actionElem.length; i++) {
actionElem[i].addEventListener('click',function(event){
event.preventDefault();
action(this);
});
}
});
jQuery使ってるとおまじないのように、$(function(){...});
でDOMREADYで実行させてるのもちゃんとやる。documentのDOMContentLoaded
イベントで実行です。
あらかじめ隠しておいた要素に height: auto; を適用するのは無理
jQueryの時とちょっと違うのは、高さを考えないとうまくいかないってことでした。
Velocity(elem, { height: 'auto' }, 250);
みたいなことは出来ない。jQueryの時は要素をdisplay:none;
していてもアニメーションできていたけど、Velocity.jsではできない。
ちなみに、CSS3 transition はlength, percentageしか受け付けないのでもちろんauto
は値として無理、max-height
やmargint-top
のネガティブマージンでなんとかしてるのを見たんだけど、もうcssでごちゃごちゃやりたくないんですよ。
というわけで、jsでなんとかした。
var accodionContents = this.querySelectorAll('.js-accordion + .content');
for (var i = 0; i < accodionContents.length; i++) {
var height = accodionContents[i].offsetHeight;
accodionContents[i].setAttribute('data-height', height);
accodionContents[i].style.height = 0;
}
そもそもdisplay:none;
しない、DOMloaded時には高さがある状態を作って、そいつらの高さをdata-height
属性としてDOMに埋め込み、style
属性にheight:0;をセット、をforで回す。全然スマートじゃない気がするけど。
あとは、IE8.9 あたりじゃ使えないのを使います。classListとかそうですね。
ハマりかけたaddEventListenerのところ
document.querySelectorAll('.js-accordion').addEventListener('click',function(){
...
});
みたいなことしたらできないですよね。document.querySelectorAll('.js-accordion')
が返してくるのdocumentにある要素全ての配列だから。なので、回して各要素にバインドさせる。
ちょっと気になってるところ
jQueryでもVelocityでもdurationは250msのはずなんだけど、Velocityの方が早い気が…。
やってみて思ったんですが、ベストはjQuery+Velocityで今のところはいいじゃないか、とも。本末転倒ですかそうですか。
本日は以上です。