【第5回】Twitterボタンの表示を真似してみよう【jQuery講座】

jQuery講座

本シリーズは、jQueryの書き方を勉強し、その結果をまとめたものになる。

今回から、より実践に近い形でjQueryを使っていくことにしよう。

まずは、Twitterの各ボタンを真似してみる

前回までの内容に慣れるという意味でも、しっかり見ていきたい。

スポンサーリンク

前回の内容の復習

前回は、動きをつけるanimateメソッドを紹介した。

また、同じタグに対して複数の処理を繋げるメソッドチェーンという書き方も扱った。

これらは、今回以降もガンガン出てくるので、しっかり復習しておこう。

以下がその記事だ。

今回のサンプルページ

では、いつも通りサンプルの紹介から。

今回のサンプルページはこちらだ。

ディレクトリ構造もいつもと同じで以下の通り。

  • jquery
    • course-05.html
    • js
      • jquery-05.js
    • css
      • style.css
      • style-05.css

サンプルページは、今回は4つの領域に分けている。

一つ目、二つ目はTwitterの左にあるメニューを真似したものだ。

それぞれのボタンにカーソルを重ねてもらうとそれっぽい変化があると思う。

ただし、一つ目はそれぞれのボタンで動いているが、二つ目は一つのボタンにカーソルを重ねると全てが変化している。

これは、前々回解説したthisを使ったパターンと使わなかったパターンだ。

詳細は後で見ていくことにして、ここは次に進んでいく。

三つ目のブロックは、フォローボタンを再現したもの

これはクリックしてもらうと状態が切り替わり、表示も変化する。

四つ目、ここはツイートの領域だ。

最初はボタンがちょっと色が薄く、そのままクリックしても何も変わらない。

で、テキストエリアに文字を打ち込むと、ボタンの色が濃くなって押せるようになるはず。

その状態で押してもらうと、隠していた五つ目の領域が出てきて、数秒すると消えていく。

なお、今回は表示のみで、ここで入力したデータの中身は取得していない

では、サンプルソースを一度全部出しておく。

<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <title>jQuery講座第5回サンプルページ</title>

        <!-- jQuery読み込み -->
        <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
        <script type="text/javascript" src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>

        <!-- 各回ごとのJavaScriptファイル読み込み -->
        <script type="text/javascript" src="./js/jquery-05.js"></script>

        <!-- 必要に応じてcssファイル読み込み -->
        <link rel="stylesheet" type="text/css" href="./css/style.css">
        <link rel="stylesheet" type="text/css" href="./css/style-05.css">
    </head>
    <body>
        <!-- ヘッダー -->
        <div class="header">
            <h1 id="page-title">jQuery講座第5回サンプルページ</h1>
        </div>

        <!-- メインコンテンツ -->
        <div class="main-contents">

            <!-- 一つ目のブロック -->
            <div class="contents">
                <p>ボタン変化その1、Twitterメニュー風</p>
                <div class="buttons buttons-twitter-menu">
                    <button>Button01</button>
                    <button>Button02</button>
                    <button>Button03</button>
                    <button>Button04</button>
                </div>
            </div>

            <!-- 二つ目のブロック -->
            <div class="contents">
                <p>ボタン変化その1、thisを使わない場合の動作</p>
                <div class="buttons buttons-twitter-menu-no-this">
                    <button>Button01</button>
                    <button>Button02</button>
                    <button>Button03</button>
                    <button>Button04</button>
                </div>
            </div>

            <!-- 三つ目のブロック -->
            <div class="contents">
                <p>ボタン変化その2、Twitterフォローボタン風</p>
                <div class="buttons buttons-twitter-follow">
                    <button>フォロー</button>
                </div>
            </div>

            <!-- 四つ目のブロック -->
            <div class="contents">
                <p>ボタン変化その3、ツイートボタン</p>
                <div class="tweet-area">
                    <textarea placeholder="いまどうしてる?"></textarea>
                    <button>ツイートする</button>
                </div>
            </div>
            
            <!-- ツイート送信表示用ブロック -->
            <div class="tweet-result">
                <div>
                    ツイートを送信しました。
                </div>
            </div>
        </div>
    </body>
</html>
$(function(){
    /* ボタンのアニメーション変化時間 */
    var duration = 300;

    /* Twitterメニュー風変化処理、this使用 */
    $(".buttons-twitter-menu button")
        .on("mouseover", function(){
            $(this).stop(true).animate(
                {
                    "background-color": "#CCFFFF",
                    "color": "#0099FF"
                },
                duration
            );
        })
        .on("mouseout", function(){
            $(this).stop(true).animate(
                {
                    "background-color": "white",
                    "color": "black"
                },
                duration
            );
        });

    /* Twitterメニュー風変化処理、this未使用 */
    $(".buttons-twitter-menu-no-this button")
        .on("mouseover", function(){
            $(".buttons-twitter-menu-no-this button").stop(true).animate(
                {
                    "background-color": "#CCFFFF",
                    "color": "#0099FF"
                },
                duration
            );
        })
        .on("mouseout", function(){
            $(".buttons-twitter-menu-no-this button").stop(true).animate(
                {
                    "background-color": "white",
                    "color": "black"
                },
                duration
            );
        });
    
    /* 現在のフォロー状態 */
    var isFollowing = false;

    /* Twitterフォローボタン風変化処理 */
    $(".buttons-twitter-follow button")
        .on("mouseover", function(){
            $(this).stop(true);
            if(isFollowing){
                $(this)
                    .text("フォロー解除")
                    .animate(
                        {
                            "color": "white",
                            "background-color": "#CA2055"
                        },
                        duration
                    );
            }else{
                $(this).animate(
                    {
                        "background-color": "#CCFFFF"
                    },
                    duration
                );
            }
        })
        .on("mouseout", function(){
            $(this).stop(true);
            if(isFollowing){
                $(this)
                    .text("フォロー中")
                    .animate(
                        {
                            "background-color": "#0099FF"
                        },
                        duration
                    );
            }else{
                $(this).animate(
                    {
                        "background-color": "white"
                    },
                    duration
                );
            }
        })
        .on("click", function(){
            $(this).stop(true);
            isFollowing = !isFollowing;
            if(isFollowing){
                $(this)
                    .text("フォロー解除")
                    .css({
                        "color": "white",
                        "background-color": "#CA2055"
                    });
            }else{
                $(this)
                    .text("フォロー")
                    .css({
                        "color": "#0099FF",
                        "background-color": "#CCFFFF"
                    });
            }
        });
    
    /* Twitterツイートボタン風変化処理 */
    $(".tweet-area textarea").on("keyup", function(){
        var text = $(this).val();
        if(text == ""){
            $(".tweet-area button")
                .css("background-color", "#8ED0F9");
        }else{
            $(".tweet-area button")
                .css("background-color", "#0099FF");
        }
    });

    /* ツイートボタンの処理 */
    $(".tweet-area button").on("click", function(){
        if($(".tweet-area textarea").val() == ""){
            return;
        }
        $(".tweet-area textarea").val("");
        $(this).css("background-color", "#8ED0F9");

        $("div.tweet-result")
            .css({
                "display": "table",
                "opacity": "0.0"
            })
            .animate(
                {
                    "opacity": "1.0"
                },
                500,
                function(){
                    setTimeout(function(){
                        $("div.tweet-result").animate(
                            {
                                "opacity": "0.0"
                            },
                            500,
                            function(){
                                $("div.tweet-result").css("display", "none");
                            }
                        );
                    }, 3000);
                });
    });
});
/* 各領域共通の設定 */
.contents {
    margin-top: 1rem;
    margin-bottom: 1rem;
    padding-left: 1rem;
    border: 1px solid black;
}

/* ボタン表示領域用の設定 */
.buttons {
    width: 90%;
    margin-bottom: 1rem;
}

/* ボタンタグ全般の設定 */
button {
    width: 100px;
    height: 50px;
    margin-left: 50px;
    border-radius: 25px;
    background-color: white;
    font-family: "arial black";
    outline: none;
}

/* 上二つのボタンの設定 */
.buttons-twitter-menu button,
.buttons-twitter-menu-no-this button {
    border-style: none;
}

/* フォローボタンの設定 */
.buttons-twitter-follow button {
    border: 1px solid #0099FF;
    color: #0099FF;
    font-weight: bold;
}

/* ツイート領域の設定 */
.tweet-area {
    width: 90%;
    margin-bottom: 1rem;
}

/* ツイートテキストエリアの設定 */
.tweet-area textarea {
    width: 100%;
    height: 5rem;
    font-size: large;
}

/* ツイートボタンの設定 */
.tweet-area button {
    border-style: none;
    background-color: #8ED0F9;
    color: white;
    font-weight: bold;
}

/* 結果表示領域外側の設定 */
div.tweet-result {
    display: none;
    width: 40%;
    height: 3rem;
    margin: 0 auto;
    padding: 0;
    border-style: none;
    border-radius: 5px;
    background-color: #0099FF;
    color: white;
}

/* 結果表示領域内側の設定 */
div.tweet-result div {
    display: table-cell;
    text-align: center;
    vertical-align: middle;
}

では、一つずつ詳細を見ていこう。

メニュー

ここでは、シンプルにボタンにカーソルが重なったら、あるいは離れたらそれぞれ変化を起こすということをしている。

該当部分のHTMLとJavaScriptソースを見てみよう。

<!-- 一つ目のブロック -->
<div class="contents">
    <p>ボタン変化その1、Twitterメニュー風</p>
    <div class="buttons buttons-twitter-menu">
        <button>Button01</button>
        <button>Button02</button>
        <button>Button03</button>
        <button>Button04</button>
    </div>
</div>
/* ボタンのアニメーション変化時間 */
var duration = 300;

/* Twitterメニュー風変化処理、this使用 */
$(".buttons-twitter-menu button")
    .on("mouseover", function(){
        $(this).stop(true).animate(
            {
                "background-color": "#CCFFFF",
                "color": "#0099FF"
            },
            duration
        );
    })
    .on("mouseout", function(){
        $(this).stop(true).animate(
            {
                "background-color": "white",
                "color": "black"
            },
            duration
        );
    });

ここは、前回までの内容を理解していれば問題ないはずだ。

なお、先頭で宣言している変数durationは今後も使う部分があり、それも全てこの定義を使用している。

コメントに書いてある通り、ボタン変化の時間を格納してあるだけ。

さて、一つ注目してもらいたいのは、それぞれのイベントハンドラの先頭

セレクタの部分にthisと指定しており、それに対してstopメソッドやanimateメソッドを使っている。

これで、前々回解説した通りイベントが発生した箇所のみに対し処理を行えている

一括で4つのボタン全てに設定しているのに、動作は別々になっているのがこれだ。

しかし、これでthisを使わないとどうなるか…それを試したのが二つ目のブロックだ。

<!-- 二つ目のブロック -->
<div class="contents">
    <p>ボタン変化その1、thisを使わない場合の動作</p>
    <div class="buttons buttons-twitter-menu-no-this">
        <button>Button01</button>
        <button>Button02</button>
        <button>Button03</button>
        <button>Button04</button>
    </div>
</div>
/* Twitterメニュー風変化処理、this未使用 */
$(".buttons-twitter-menu-no-this button")
    .on("mouseover", function(){
        $(".buttons-twitter-menu-no-this button").stop(true).animate(
            {
                "background-color": "#CCFFFF",
                "color": "#0099FF"
            },
            duration
        );
    })
    .on("mouseout", function(){
        $(".buttons-twitter-menu-no-this button").stop(true).animate(
            {
                "background-color": "white",
                "color": "black"
            },
            duration
        );
    });

HTML側はボタンを入れているdivのクラスを変更しているのみ。

jQuery側も、先ほどと異なる点は二つだけ。

一つ目はイベントを登録する部分が別の対象になっている点で、まあこれは大丈夫だと思う。

二つ目は先ほどthisを使って指定していたところを、今度はもう一度同じタグを指定するような書き方にしている点

この二つ目の書き方に直したことで、イベントが発生したら、再度条件に合致するボタンを探しに行ってしまう。

すると、やはり4つのボタン全てが該当し、それらに対してanimateメソッドが適用される。

その結果、一つのボタンにカーソルを重ねているのに全てが変化してしまう、という状況が出来上がるのだ。

これを防げるのが、thisを使うメリットの一つ。

紹介時は具体的なサンプルを出していなかったので、ここで紹介させてもらった。

それぞれ、どういう動きをしていて、その結果どうなるかをしっかり把握しておこう。

フォローボタン

ではサンプルでいう三つ目のブロックに進んでいこう。

ここではカーソルが重なった時、離れた時、ボタンをクリックされた時の三つのイベント処理を実装している。

該当部分のソースは以下の通り。

<!-- 三つ目のブロック -->
<div class="contents">
    <p>ボタン変化その2、Twitterフォローボタン風</p>
    <div class="buttons buttons-twitter-follow">
        <button>フォロー</button>
    </div>
</div>
/* 現在のフォロー状態 */
var isFollowing = false;

/* Twitterフォローボタン風変化処理 */
$(".buttons-twitter-follow button")
    .on("mouseover", function(){
        $(this).stop(true);
        if(isFollowing){
            $(this)
                .text("フォロー解除")
                .animate(
                    {
                        "color": "white",
                        "background-color": "#CA2055"
                    },
                    duration
                );
        }else{
            $(this).animate(
                {
                    "background-color": "#CCFFFF"
                },
                duration
            );
        }
    })
    .on("mouseout", function(){
        $(this).stop(true);
        if(isFollowing){
            $(this)
                .text("フォロー中")
                .animate(
                    {
                        "background-color": "#0099FF"
                    },
                    duration
                );
        }else{
            $(this).animate(
                {
                    "background-color": "white"
                },
                duration
            );
        }
    })
    .on("click", function(){
        $(this).stop(true);
        isFollowing = !isFollowing;
        if(isFollowing){
            $(this)
                .text("フォロー解除")
                .css({
                    "color": "white",
                    "background-color": "#CA2055"
                });
        }else{
            $(this)
                .text("フォロー")
                .css({
                    "color": "#0099FF",
                    "background-color": "#CCFFFF"
                });
        }
    });

では、jQueryの中身を順番に見ていこう。

まず、最初に準備として現在のフォロー状態を保持する変数isFollowingを用意。

初期値は、フォローしていない状態としてfalseを代入している。

その次からが、イベント処理の実装部分だ。

先ほど書いた通り、今回は3つの処理を実装しており、それらをやはりメソッドチェーンで定義している。

一つ目は、カーソルが重なった時の処理。

まず、現在アニメーション実行中であればstopメソッドでそれを中断。

次に、現在のフォロー状態によって処理を分岐させたいので、isFollowingの中身で分けている。

これがtrue、つまり現在フォロー状態であれば、テキストをフォロー解除に変更し、色をアニメーションで変更。

false、フォロー状態でなければ色のみをアニメーションで変更という感じだ。

二つ目のカーソルが離れた時も、それぞれ設定する文字や色が違うだけで、処理としては同じことをしている。

三つ目、クリックされた時は少し異なる。

先頭はやはり現在アニメーション中であれば、それを中断する処理。

次に、現在のフォロー状態を入れ替える。

そして、その入れ替えた結果フォロー状態になれば、カーソルが重なっていた時と同じ内容に、今度はcssメソッドによる単純なスタイル変更をしている。

逆にフォローが解除されたときは、その時の内容への変化だ。

これで、カーソルの重なりによる変化はアニメーションで、クリック時の変化は即時で行われるようになる。

実際のTwitterのフォローボタンも、こんな見た目になっているはずだ。

さて、中で使っているtextメソッドが初登場なので少し見ていこう。

これには二つの使い方があり、タグに設定されている文字列の取得、あるいは逆に設定を行える。

取得するときの書き方は以下。

text()

非常にシンプルで、取得対象のタグを取得し、それに対して引数なしでメソッドを実行する。

そうすれば、そのタグに入っている文字列を取得できる。

一つ注意で、こちらの形は戻り値が取得した文字列になるので、ここからメソッドチェーンを繋げることはできない

設定時は、引数に文字列でその設定する内容を渡してあげればOKだ。

text(文字列)

これで、指定したタグの中の文字を書き替えることができる。

こちらの戻り値は対象タグのjQueryオブジェクトなので、メソッドチェーンが可能だ。

サンプルも、実際にここからメソッドチェーンで繋げて処理を行っている。

さて、このtextメソッドにはちょっとした注意点がある。

指定したタグの中に、さらに別のタグを入れたい場合だ。

例としては、以下のような状況。

<div class="sample01">

</div>

<script>
    $(".sample01").text("<p>文字列</p>");
</script

これで、divの中にpタグが生成されてほしい…のだが、実はうまくいかない

というのも、このtextメソッドは自動でエスケープ処理がされ、そのまま入力した内容を全て単なる文字列として格納するような動きになっている

つまり、上のサンプルではdivの中に、<p>文字列</p>という文字列が入ることになり、このpタグはタグとして認識されない。

実際にページを表示されると、pタグの部分まで文字列として以下のように表示されている、ということだ。

<p>文字列</p>

しかし、今回はしっかりタグとして入れたい。

その時は、textメソッドの代わりにhtmlメソッドを用いる。

書き方は全く同じで、対象タグの中に設定する文字列を一つ引数で渡す。

動きも対象タグの中身を引数の文字列に変更するのだが、こちらはタグが入っていると、しっかりタグとして認識されるようになる

やってみると以下のような感じ。

<div class="sample01">

</div>

<script>
    $(".sample01").html("<p>文字列</p>");
</script

さっきの内容からtexthtmlに変えただけだが、今度はdivタグの中にpタグができ、その中に文字列が入る、という結果になる。

これを実行すると、文字列とだけ表示されることになる。

構造も、divタグの中にpタグがあり、その中に文字列が入っている、という形になるのだ。

これらは非常に似た動きをするのだが、よく理解せずに使うと誤動作の元になるので、しっかり違いを理解しておきたい。

ツイートボタン

…話が逸れてしまった、元のサンプルの話に戻ってこよう。

解説が残っているのは、4つ目のブロックだった。

ここでは、単にボタンを押せばその内容をツイートした旨を表示する処理だけ…と思いきや、実は色々としている。

ボタンに対するイベント処理は一つだけで、クリック時のみ。

しかし、実はテキストエリアにもイベント処理を実装している

まずはサンプルの該当部分を見てみよう。

<!-- 四つ目のブロック -->
<div class="contents">
    <p>ボタン変化その3、ツイートボタン</p>
    <div class="tweet-area">
        <textarea placeholder="いまどうしてる?"></textarea>
        <button>ツイートする</button>
    </div>
</div>

<!-- ツイート送信表示用ブロック -->
<div class="tweet-result">
    <div>
        ツイートを送信しました。
    </div>
</div>
/* Twitterツイートボタン風変化処理 */
$(".tweet-area textarea").on("keyup", function(){
    var text = $(this).val();
    if(text == ""){
        $(".tweet-area button")
            .css("background-color", "#8ED0F9");
    }else{
        $(".tweet-area button")
            .css("background-color", "#0099FF");
    }
});

/* ツイートボタンの処理 */
$(".tweet-area button").on("click", function(){
    if($(".tweet-area textarea").val() == ""){
        return;
    }
    $(".tweet-area textarea").val("");
    $(this).css("background-color", "#8ED0F9");

    $("div.tweet-result")
        .css({
            "display": "table",
            "opacity": "0.0"
        })
        .animate(
            {
                "opacity": "1.0"
            },
            500,
            function(){
                setTimeout(function(){
                    $("div.tweet-result").animate(
                        {
                            "opacity": "0.0"
                        },
                        500,
                        function(){
                            $("div.tweet-result").css("display", "none");
                        }
                    );
                }, 3000);
            });
});

HTML側で一つ補足。

最後のdivなのだが、これはツイートボタンを押した時に表示する用の部分で、最初はcssでdisplay: noneを指定して非表示にしている

サンプルで出てきていないのはそういった理由だ。

では、jQueryの内容を。

ここでは、大きく二つの処理をしている。

一つ目は、テキストエリアに文字が入力されているかを見て、ボタンの色を変更する処理

イベントタイプにkeyupと指定しており、これでキーボードで何かしら入力したとき…厳密には、そのキーが離された瞬間にイベントハンドラを実行できるようになる。

中では、まずvalメソッドを使い、テキストエリアのテキストを取得。

その文字列が空であればボタンの色を薄く、何でもいいので1文字でも入っていればボタンの色を濃くしている。

さて、先ほどタグの中の文字列を取得するときはtextメソッドを使うと書いた。

しかし、このtextメソッドは今回のtextareaタグや、他にもinputタグなんかでは使うことができない

その代わりに使うのが、このvalメソッドだ。

使い方はtextメソッドと同じで問題ない。

引数なしで使えば文字列の取得、文字列を渡せばそれが対象のテキストエリア等に入る、という動きだ。

ということで、これでテキストが何かしら入力されれば、ボタンが押せるようになるような表示を実現できた。

あとは、そのボタンの処理

ここではクリックされたときに処理を行っているのだが、ちょっとややこしいことをしている。

まず前提として、ボタンが押せるかどうかというのは、上では見た目でしか変化させていない

つまり、見た目では押せないような状況でボタンを押しても、実際にはこの中身が実行されることに気を付けて欲しい。

ではその中身を。

まず、先ほどと同じvalメソッドを使って、中身を取得する。

それが空であれば、何もせずイベントハンドラを終了する

これで、ボタンが押せない時は何も起こらないことを表現しているのだ。

では中身が入っていたらどうするかというと、まずはテキストエリアの中身を空にする。

もし実際にツイートの内容を受け取って処理するのであれば、この前にその内容を書くことになるだろう。

そして、何もなくなってボタンが押せない状態になったので、ボタンの色をそれに変更する。

ここまでは問題ないと思う。

次から、ツイートした時に画面下に出てくるツイートを送信しました。という表示部分の実装だ。

まず、元々の部分は非表示だったので、それを表示できるようcssを変更する。

ここで、浮かび上がってくるようなアニメーションにしたいので、同時にopacityプロパティで透明にしておく。

次に、animateメソッドでopacityを不透明にするよう設定していて、これでフェードインが表現できた。

しかし、表示されっぱなしではいけないので、後処理で再度透明にする内容が必要

とはいえ、ここですぐ透明にするアニメーションを入れると表示が一瞬で消えてしまう。

そこで、JavaScriptのsetTimeout関数を使い、一定時間待った後に処理を行うように指定した。

前回のオマケでも使っていたが、改めて書き方を。

setTimeout(行う処理, 待つ時間)

処理のところにはやはり関数を渡し、待つ時間はミリ秒単位で数字を渡す。

これで、時間に指定されただけ待ち、その後に処理を実行することが可能になる。

これはjQueryではなく、JavaScript側の関数であることにも注意しておこう。

今回は、3000ミリ秒…3秒だけ待ち、そのタイミングで行う処理を無名関数で渡している。

そこでanimateメソッドを使って今度は透明にしていく処理を行う。

で、その後にブロックを再度非表示にしたいのだが、このままメソッドチェーンでcssメソッドを繋げると、アニメーションが行われる暇もなくcssが適用されてしまう

なので、今度はこのanimateメソッドの後処理としてさらに無名関数を渡し、そこでブロックを非表示にする、ということをしている。

無名関数が最大4個も入れ子になっていて分かりづらいかもしれないが、落ち着いて一つずつ処理を追っていこう。

おわりに

今回は、前回までの内容を実際に使ってみようということで、Twitterの表示を幾つか表現してみた。

処理が複雑になってきていて、かつ注意点などもあったので、それぞれしっかり理解しておこう。

なお、あくまでそれっぽい再現なので、細かく見ると実は動作が異なる部分もある

…まあ、実際にTwitter画面のソースを見たわけではないので、そこはご了承願いたい。

さて、次回はボタンで開閉が可能なサイドバーを実現してみよう。

これも今回までの内容に慣れるという意味が強いが、こういった応用例をいくつも知ることで、新たにこれもできるんじゃないかという発想にも繋がっていく

是非、動き方まで理解していっていただきたい。

2021/2/18追記

次回の内容、サイドメニューの実装を公開した。

かなり簡単にできたので、よかったら是非チャレンジしてもらいたい。

コメント

タイトルとURLをコピーしました