サンプルコード

【JavaScript】 jqueryを使用せずに開閉ボタンを設置する

更新日:2023/02/23

 

仕様的なもの

単純な開閉だけでなく、スライドメニューやアコーディオンメニューなどできるかぎり多くの開閉パターンに対応するために、次のような動作ができるようにします。

できること1:簡単なhtmlで、開閉できるボタンを設置する。

開閉ボタンを設置

html:

詳しくはこちら<span class="opcl_button" data-target-class="opcl_target"></span>
<div class="opcl_target">
開閉したい内容
</div>

設置例:

詳しくはこちら

表示された!?

できること2:複数の開閉ボタンを設置可能

複数の開閉ボタンを設置

html:

詳しくはこちら:その1<span class="opcl_button" data-target-class="opcl_target_1"></span>
<div class="opcl_target_1">
開閉したい内容
</div>

詳しくはこちら:その2<span class="opcl_button" data-target-class="opcl_target_2"></span>
<div class="opcl_target_2">
開閉したい内容
</div>

詳しくはこちら:その3<span class="opcl_button" data-target-class="opcl_target_3"></span>
<div class="opcl_target_3">
開閉したい内容
</div>

設置例:

詳しくはこちら:その1

表示された!?

詳しくはこちら:その2

表示された!?

詳しくはこちら:その3

表示された!?

できること3:ひとつの開閉ボタンで複数のエリアを開閉

仕様3

html:

詳しくはこちら:<span class="opcl_button" data-target-class="opcl_target_4"></span>
開閉エリア1:
<div class="opcl_target_4">
開閉したい内容
</div>
開閉エリア2:
<div class="opcl_target_4">
開閉したい内容
</div>
開閉エリア3:
<div class="opcl_target_4">
開閉したい内容
</div>

設置例:

詳しくはこちら:

開閉エリア1:

表示された!?

開閉エリア2:

私も表示された!?

開閉エリア3:

全部表示された!?

できること4:複数の開閉ボタンと複数のエリアが全て連動

仕様4

html:

詳しくはこちら:その1<span class="opcl_button" data-target-class="opcl_target_5"></span>
開閉エリア1:
<div class="opcl_target_5">
開閉したい内容
</div>

詳しくはこちら:その2<span class="opcl_button" data-target-class="opcl_target_5"></span>
開閉エリア2:
<div class="opcl_target_5">
開閉したい内容
</div>

詳しくはこちら:その3<span class="opcl_button" data-target-class="opcl_target_5"></span>
開閉エリア3:
<div class="opcl_target_5">
開閉したい内容
</div>

設置例:

詳しくはこちら:その1

開閉エリア1:

表示された!?

詳しくはこちら:その2

開閉エリア2:

私も表示された!?

詳しくはこちら:その3

開閉エリア3:

全部表示された!?

具体的な仕様

【開閉ボタン】

<span class="opcl_button opcl_opend" data-target-class="開閉対象エリアのクラス" >開閉ボタンのラベル</span>

  • opcl_buttonクラス

    このクラスが付加されているタグが開閉ボタンとなる。

  • opcl_opendクラス

    省略可能。
    初期状態で開く。

    同じdata-target-classを持つ開閉ボタンが複数ある場合、どれかひとつでもopcl_opendクラスが指定されていたら、全て開く。

  • data-target-class

    開閉対象エリアのクラスを指定する。
    同じクラスを指定した開閉ボタンは、連動する。

【開閉対象エリア】

<div class="開閉対象エリアのクラス">
表示・非表示したい内容
</div>

同じクラスを指定した開閉対象エリアは、連動する。

【内部処理】

  • 初期化時

    開閉対象エリアにopcl_contentクラスを付加。

  • 開く時

    開閉ボタンと開閉対象エリアからopcl_closedクラスを削除。
    同じdata-target-class属性を持つ開閉ボタンと、同じクラスを持つ開閉対象エリア全てにおこなう。

  • 閉じる時

    開閉ボタンと開閉対象エリアからopcl_closedクラスを付加。
    同じdata-target-class属性を持つ開閉ボタンと、同じクラスを持つ開閉対象エリア全てにおこなう。

【表示・非表示】

スクリプトはクラスの付加/削除をおこなうだけです。

実質的な表示・非表示は、cssで設定します。

【制限事項】

対象要素の動的作成・削除には非対応。

※dom要素の参照を内部で保持しているため、他のスクリプトで対象要素を削除しても、メモリ上から解放されない。

 

JavaScriptコード

上の仕様を踏まえて、スクリプトを作成してみました。


(()=>{
    const OPCL_CLASS = "opcl_button";
    const TARGET_ATTR = "data-target-class";
    const FLG_CLASS = "opcl_closed";
    const FLG_INITIAL_OPENCLASS = "opcl_opend";
    const TARGET_CLASS = "opcl_content"

    const apError = m => {throw new Error("opcl:" + m);}

         //  targetCtrlの作成・リスト化
    const targetList = {
        list:{},
        add( className , element){
                // spanタグから開閉エリアのclass取得
            if( !element.hasAttribute(TARGET_ATTR) )
            apError(TARGET_ATTR + " not found");

            const className = element.getAttribute(TARGET_ATTR);
            if( !(className in this.list) ){
                    // listにclassNameが登録されていない
                    //    →targetCtrlを作成してlistに登録
                this.list[className]=new targetCtrl(className);
            }
            const tg = this.list[className];
            tg.addElement( element );

                // クリックイベント登録
            element.addEventListener("click",()=>tg.toggle(element));
        }
    };

        //  同じクラスを持つ開閉ボタン・開閉対象エリア
        //  を管理するオブジェクトを作成するコンストラクタ
    const targetCtrl = function (className ) {
        this.targets = document.getElementsByClassName(className);
            //  開閉対象エリアにopcl_content
            //  とopcl_closedをセット
        this.targetEach( tg => {
                tg.classList.add(TARGET_CLASS);
                tg.classList.add(FLG_CLASS);
        } );

        this.element=[];
        this.initialOpen = false; // opcl_opendクラスが出現したらtrue
    };
    targetCtrl.prototype={
            //  開閉ボタンを追加
        addElement( element ){
            this.element.push( element );
            if( this.initialOpen === false
                && element.classList.contains(FLG_INITIAL_OPENCLASS) ){
                    //  elementにopcl_opendクラスが付加されていた
                this.initialOpen = true;
                this.changeClass( false );
            }
            if( !this.initialOpen ) element.classList.add(FLG_CLASS);
        },
            //  getElementsByClassNameで取得した
            //  オブジェクトを列挙するための小細工
        targetEach( callBack ){
            for(let i = 0; i < this.targets.length ; i++){
                callBack( this.targets[i] );
            }
        },
            //  開閉状態を入れ替え
        toggle( element ){
            this.changeClass( !element.classList.contains(FLG_CLASS) );
        },
            //  開閉処理
        changeClass( closeFlg  ){
            const func = ( closeFlg ) ? this.funcs.tgAdd : this.funcs.tgRemove;
                this.targetEach( func );
                this.element.forEach( func )
        },
        funcs:{
            tgAdd: tg => tg.classList.add(FLG_CLASS),
            tgRemove: tg => tg.classList.remove(FLG_CLASS),
        }
    };

    window.addEventListener("DOMContentLoaded",()=> {
        const element = document.getElementsByClassName(OPCL_CLASS);
        for(let i = 0; i < element.length ; i++){
            targetList.add( element[i] );
        }
    });
})();

コメントで説明しているので、その分コードが長く見えますが、実際は短いです。
次の二つをやっているだけです。

(1) 初期処理で同じdata-target-class属性を持つ要素と、その値をクラスとして持つ要素を一つのオブジェクトにまとめる

(2) 開閉ボタンがクリックされたら、まとめた全ての要素に対してopcl_closedクラスを付加/削除しています。

 

単純な開閉ボタン例

上のスクリプトを使用して、単純な開閉ボタンを設置してみます。

設置イメージ

詳しくはこちら

詳しい内容

html


詳しくはこちら<span class="opcl_button" data-target-class="target1" ></span>
<div class="target1">
詳しい内容
</div>

css

.opcl_button{
            font-size:0.8em;
}
.opcl_button:hover{
            border-bottom:1px dotted blue;
}
.opcl_button:after{
            content: "[×閉じる]";
            margin-left:10px;
}
.opcl_button.opcl_closed:after{
            content: "[↓開く]";
}
.opcl_content{
            display: block;
}
.opcl_content.opcl_closed{
            display: none;
}

.opcl_button:before』と『.opcl_button.opcl_closed:before』で、開閉ボタン
のテキストを変化させています。

補足:全て連動するパターン

設置イメージ

詳しくはこちら

詳しい内容

詳しくはこちら

詳しい内容

詳しくはこちら

詳しい内容

下記のhtmlのように、全て同じクラスを指定すると連動します。

html


詳しくはこちら<span class="opcl_button" data-target-class="target1" ></span>
<div class="target1">
詳しい内容
</div>

詳しくはこちら<span class="opcl_button" data-target-class="target1" ></span>
<div class="target1">
詳しい内容
</div>

詳しくはこちら<span class="opcl_button" data-target-class="target1" ></span>
<div class="target1">
詳しい内容
</div>

 

応用例:アコーディオンメニュー

紹介したスクリプトでアコーディオンメニューを作ってみます。

アコーディオンメニュー 完成例

アコーディオンメニュー html


<div id="acmenu">
    <div class="menu">
        <p class="opcl_button opcl_opend" data-target-class="opcl_target_ac1" >メニュー1</p>
    </div>
    <div class="opcl_target_ac1">
        <div class="submenu">
            <a href="#">サブメニュー1</a>
        </div>
        <div class="submenu">
            <a href="#">サブメニュー2</a>
        </div>
        <div class="submenu">
            <p class="opcl_button" data-target-class="opcl_target_ac1-1" >サブメニュー3</p>
            <div class="opcl_target_ac1-1">
                <div class="submenu">
                    <a href="#">サブメニュー1</a>
                </div>
                <div class="submenu">
                    <a href="#">サブメニュー2</a>
                </div>
            </div>
        </div>
    </div>
    <div class="menu">
        <p class="opcl_button" data-target-class="opcl_target_ac2" >メニュー2</p>
    </div>
    <div class="opcl_target_ac2">
        <div class="submenu">
            <a href="#">サブメニュー1</a>
        </div>
        <div class="submenu">
            <a href="#">サブメニュー2</a>
        </div>
        <div class="submenu">
            <p class="opcl_button" data-target-class="opcl_target_ac2-1" >サブメニュー3</p>
            <div class="opcl_target_ac2-1">
                <div class="submenu">
                    <a href="#">サブメニュー1</a>
                </div>
                <div class="submenu">
                    <a href="#">サブメニュー2</a>
                </div>
            </div>
        </div>
    </div>
</div>

行の終わりまでクリック可能にするため、spanではなくてpタグを使用しています。

アコーディオンメニュー css


#acmenu .opcl_button.opcl_closed:before{
    content: "-";
}
#acmenu .opcl_button.opcl_closed:hover:before{
    animation: rotate-anime 2s linear infinite;
    font-size:3em;
    left:-5px;
}
#acmenu .opcl_button:before{
    content: "+";
    position: absolute;
    left: 10px;
}
#acmenu .opcl_button:hover:before{
    animation: rotate-anime 1s linear infinite;
    font-size:3em;
    left:-5px;
}

@keyframes rotate-anime {
0%{ transform: rotate(0) }
20%{ transform: rotate(360deg) }
40%{ transform: rotate(675deg) }
100%{ transform: rotate(675deg) }
}
#acmenu .menu {
    position: relative;
    background: #004BC8;
    color: white;
    padding: 5px 0 5px 25px;
    border: 1px solid blue;
    margin-bottom: 1px;
}
#acmenu .submenu{
    border-bottom: 1px solid #888;
    margin: 3px 15px;
    padding: 5px 10px;
    position: relative;
}
#acmenu > .opcl_content{
    border:1px solid #888;
}
#acmenu > .opcl_content .opcl_content{
    border: 0;
}
#acmenu .submenu:last-child{
    border:0;
}
#acmenu .submenu{
    margin-left: 15px;
}
#acmenu .submenu > p{
    margin-left: 15px;
}
.opcl_content {
    display: block;
}
.opcl_content.opcl_closed {
    display: none;
}

#acmenu .opcl_button.opcl_closed:hover:before』『#acmenu .opcl_button:hover:before』『@keyframes rotate-anime』で、文字を回転しています。

Webサイトでこんなアコーディオンメニューがあったら、少しウザいですね…

 

応用例:スライドメニュー

次は画面の横からスライドして出てくるメニュー、いわゆるスライドメニューとかハンバーガーメニューと呼ばれるものを作成してみます。

スライドメニュー

html

<div id="panel">
    <div class="menu">
        <span class="opcl_button" data-target-class="opcl_target" ></span>
    </div>
</div>
<div class="opcl_target">
    <span class="opcl_button" data-target-class="opcl_target" ></span>
    <ul>
        <li>メニュー1</li>
        <li>メニュー2</li>
    </ul>
</div>

css

#panel{
width: 600px;
margin: 0 auto;
position: relative;
}
#panel .menu{
background: antiquewhite;
text-align: right;
position: relative;
height: 60px;
}
#panel .menu .opcl_button{
position: absolute;
right: 10px;
top:10px;
}
.opcl_target{
position: fixed;
width: 300px;
top:0;
bottom: 0;
padding: 10px;
background: darkgray;
transition: left .5s ease; // left → right
}
.opcl_target:not(.opcl_closed){
left: 0; // left → right
}
.opcl_target.opcl_closed{
left: -100%; // left → right
}
.opcl_button:before{
content: "×";
cursor: pointer;
font-size: 30px;
font-weight: bold;
border: 1px solid #cecece;
border-radius: 5px;
padding: 2px 10px;
background: #fff;
}
.opcl_button.opcl_closed:before{
content: "≡";
}

※赤字コメント( left → right )に変更すると、右からスライドさせることができます。

更新日:2023/02/23

書いた人(管理人):けーちゃん

スポンサーリンク

記事の内容について

null

こんにちはけーちゃんです。
説明するのって難しいですね。

「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。

裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。

掲載コードについては事前に動作確認をしていますが、貼り付け後に体裁を整えるなどをした結果動作しないものになっていることがあります。
生暖かい視線でスルーするか、ご指摘ください。

ご意見、ご指摘はこちら。
https://note.affi-sapo-sv.com/info.php

 

このサイトは、リンクフリーです。大歓迎です。