Accordion Class v1.4.0
CSS3アニメーションに対応したアコーディオンです。optionでアコーディオンのネストに対応。 対応ブラウザはモダンブラウザ(IE10は以上、IE8,9はアニメーション以外は動く)
※スタイルは別途用意してください。
/**
* Accordion Class Description v1.4.0
* @fileoverview CSS3アニメーションに対応したアコーディオンです。optionでアコーディオンのネストに対応
* 対応ブラウザはモダンブラウザ(IE10は以上、IE8,9はアニメーション以外は動く)
*/
var Accordion = (function () {
/** @constructor */
var constructor = function () {
this.$introBlock = {};
this.$detailBlock = {};
this.$openTrigger = {};
this.$closeTrigger = {};
this.introBlockParentClass = {};
this.isLoading = false; /** ボタンを押せる状態かどうか */
this.isInnerDetail = false; /** 開く要素が初期要素を中にあるかどうか */
this.isNested = false; /** 入れ子のアコーディオンかどうか */
this.introHeight = 0;
this.speed = 0; /** optionsで指定可 */
this.childAccordion = {
introBlock : '.js-accordion-introBlock',
detailBlock : '.js-accordion-detailBlock',
openTrigger : '.js-accordion-openTrigger',
closeTrigger : '.js-accordion-closeTrigger'
};
};
var proto = constructor.prototype;
/**
* Entry point
* @param {Object} el - 全ての要素を含んだラッパーDOM要素
* @param {Object} options - 任意でoptionの指定
* {Number} options.speed - アニメーションスピードの指定
* {Object} options.childAccordion - アコーディオンのネストが必要な場合、ここで子要素{introBlock, detailBlock, openTrigger, closeTrigger}のクラス名を直接指定
*/
proto.set = function (el, options) {
this.setOptions(options);
if(el){
this.$el = $(el);
this.setEl();
}else{
throw new Error('el is Required');
}
this.setEl();
this.setStyle();
this.setEvents();
return this;
};
proto.setOptions = function (options) {
if(options){
//** optionsを一つでも指定した場合 */
this.speed = options.speed || this.speed;
if(options.childAccordion){
this.isNested = true;
this.introBlockParentClass = this.childAccordion.introBlock;
this.childAccordion = options.childAccordion;
}
}
};
proto.setEl = function () {
this.$introBlock = this.$el.find(this.childAccordion.introBlock);
this.$detailBlock = this.$el.find(this.childAccordion.detailBlock);
this.$openTrigger = this.$el.find(this.childAccordion.openTrigger);
this.$closeTrigger = this.$el.find(this.childAccordion.closeTrigger);
this.introHeight = this.$introBlock.outerHeight(true);
if(this.isNested){
this.$introBlockParent = this.$el.closest(this.introBlockParentClass);
}
return this;
};
proto.setStyle = function () {
var that = this;
if(this.introHeight <= 0){
/** introがタブで初期表示が非表示の場合、表示されている先頭タブの要素の高さに合わせる */
/** iOSでheight()が取れないのでcss('max-height')を取る */
var maxH = this.$introBlock.closest('.hide').parent().find(this.childAccordion.introBlock).eq(0).css('max-height');
if(!this.isNested){
this.introHeight = parseInt(maxH, 10);
}else{
/** Accordionが入れ子になっている場合は親のintroBlockの高さを取得 */
maxH = this.$introBlockParent.eq(0).css('max-height');
this.introHeight = parseInt(maxH, 10);
}
}
var defaultStyle = {
'min-height': that.introHeight +'px',
'max-height': that.introHeight +'px',
'overflow-y': 'hidden'
};
var setTransition = {'transition': 'all ' +(this.speed)/1000 +'s ease-in-out'};
var isInnerDetail = this.$introBlock.find(this.childAccordion.detailBlock).length > 0;
if(isInnerDetail){
/** Introの中にdetailが存在する場合 */
this.isInnerDetail = true;
this.$introBlock.css(defaultStyle);
if(this.speed > 0){
this.$introBlock.css(setTransition);
}
}else{
/** Introとdetailが別々に存在する場合 */
this.$detailBlock.css(defaultStyle);
if(this.speed > 0){
this.$detailBlock.css(setTransition);
}
}
return this;
};
proto.setEvents = function () {
var that = this;
this.$openTrigger.click(function (e) {
if(!this.isLoading){
e.preventDefault();
that.open();
}
return this;
});
this.$closeTrigger.click(function (e) {
if(!this.isLoading){
e.preventDefault();
that.close();
}
return this;
});
return this;
};
proto.open = function () {
var that = this;
var defaultOpen = function () {
that.$detailBlock.removeClass('hide');
if(!that.isInnerDetail){
that.$introBlock.addClass('hide');
}else{
that.$openTrigger.addClass('hide');
that.$closeTrigger.removeClass('hide');
}
return this;
};
var setMaxHeight = function () {
var maxHeight = 0;
if(!that.isInnerDetail){
/** introとdetailが分離している場合 */
var detailHeight = 0;
var detailMarginPadding = that.$detailBlock.outerHeight(true) -that.$detailBlock.height();
that.$detailBlock.children().each(function() {
detailHeight = detailHeight +$(this).outerHeight(true);
});
maxHeight = detailHeight +detailMarginPadding;
that.$detailBlock.css('max-height', maxHeight +'px');
}else{
/** detailがinnerの中にある場合 */
var introHeight = 0;
var introMarginPadding = that.$introBlock.outerHeight(true) -that.$introBlock.height();
that.$introBlock.children().each(function() {
introHeight = introHeight +$(this).outerHeight(true);
});
maxHeight = introHeight +introMarginPadding;
that.$introBlock.css('max-height', maxHeight +'px');
if(that.isNested){
/** 入れ子のアコーディオンの場合 */
var pareantMaxHeight = that.$introBlockParent.outerHeight(true) +maxHeight;
that.$introBlockParent.css('max-height', pareantMaxHeight +'px');
}
}
return this;
};
if(this.speed > 0){
/** speedを指定した場合はCSS3でスライドダウンアニメーション */
this.isLoading = true;
defaultOpen();
setTimeout(function () {
setMaxHeight();
setTimeout(function(){
that.isLoading = false;
}, that.speed);
}, 0); /** 1msずらさないとアニメーションできない */
}else{
defaultOpen();
setMaxHeight();
}
return this;
};
proto.close = function () {
var that = this;
var defaultClose = function () {
that.$detailBlock.addClass('hide');
if(!that.isInnerDetail){
that.$introBlock.removeClass('hide');
}else{
that.$openTrigger.removeClass('hide');
that.$closeTrigger.addClass('hide');
}
return this;
};
var setMaxHeight = function () {
if(!that.isInnerDetail){
that.$detailBlock.css({'max-height': that.introHeight});
}else{
that.$introBlock.css({'max-height': that.introHeight});
}
};
if(this.speed > 0){
/** speedを指定した場合はCSS3でスライドアップアニメーション */
this.isLoading = true;
setMaxHeight();
setTimeout(function(){
that.isLoading = false;
defaultClose();
}, that.speed);
}else{
setMaxHeight();
defaultClose();
}
return this;
};
return constructor;
})();
<div id="js-accordion">
<div class="js-accordion-introBlock">
<div>introBlock</div>
<div class="js-accordion-openTrigger">open</div>
</div>
<div class="js-accordion-detailBlock hide">
<div>detailBlock</div>
<div class="js-accordion-closeTrigger">close</div>
<div>content<br>content<br>content<br>content<br>content<br>content<br>content<br>content<br></div>
</div>
</div>
<!-- Anmation & Nested -->
<div id="js-nestedAccordionParent">
<div class="js-accordion-introBlock">
<div>introBlock</div>
<div class="js-accordion-openTrigger">open</div>
<div class="js-accordion-detailBlock hide">
<div>detailBlock</div>
<div class="js-accordion-closeTrigger">close</div>
<div>content<br>content<br>content<br>content<br>content<br>content<br>content<br>content<br></div>
<div id="js-nestedAccordionChild">
<div class="js-nestedAccordion-introBlock">
<div>introBlock</div>
<div class="js-nestedAccordion-openTrigger">open</div>
<div class="js-nestedAccordion-detailBlock hide">
<div>detailBlock</div>
<div class="js-nestedAccordion-closeTrigger">close</div>
<div>content<br>content<br>content<br>content<br>content<br>content<br>content<br>content<br></div>
</div>
</div>
</div>
</div>
</div>
</div>
// Default
var accordionDefault = new Accordion();
accordionDefault.set('#js-accordion');
// Anmation & Nested
var nestedAccordionParent = new Accordion();
nestedAccordionParent.set('#js-nestedAccordionParent', { speed: 300 });
var nestedAccordionChild = new Accordion();
var childAccordion = {
introBlock : '.js-nestedAccordion-introBlock',
detailBlock : '.js-nestedAccordion-detailBlock',
openTrigger : '.js-nestedAccordion-openTrigger',
closeTrigger : '.js-nestedAccordion-closeTrigger'
};
nestedAccordionChild.set('#js-nestedAccordionChild', { speed: 300, childAccordion: childAccordion });