本シリーズは、jQueryの書き方を勉強し、その結果をまとめたものになる。
今回も、具体的な題材を組んでいく形でjQueryを使ってみよう。
ボタンによって開閉するサイドメニューを作ってみる。
前回は実際のページを参考に真似するような形だったが、今回はそうではない。
また、ちょっとHTMLのページレイアウトも複雑になってくるので、どんな見た目や構造などにしていくか、先に決めていこう。
設計というほど厳密なものではないが、サンプルの構造を分かりやすくするためのものだ。
その点はご了承の上、ご覧いただきたい。
前回の内容の復習
前回は、Twitterの各ボタンの表示を真似してみた。
とりあえず、ボタンの作り方はなんとなくわかったのではないかと思う。
もちろん、色や時間、アニメーションのイージングなどを変化させれば雰囲気を変えることもできるので、色々試してみよう。
以下がその記事だ。
今回作る内容
では、先に今回作る内容を出していこう。
今回、大きく3つの領域に分けて考える。
- タイトル部分
- サイドメニュー部分
- メインコンテンツ部分
タイトル、メインコンテンツ部分は前回までのサンプルとほぼ同じ形だ。
そこにかぶせるように、今回サイドメニュー部分を実装していく。
で、考えていく順序としては、まずサイドメニューの見た目を決める。
次に、その中の動作を決めてから、全体のレイアウトがどうなるかを見ていこう。
本来であれば全体レイアウトも先に決めるべきだろうが…まあ、細かいところは気にしないでほしい。
サイドメニューの見た目
では、先にサイドメニューの見た目をどうするか考えてみよう。
左に縦長の領域を作り、トップには「サイドメニュー」という表示を。
その下に、箇条書きでコンテンツの見出しをそれぞれ並べる、という感じだろうか。
そして、その領域のすぐ右にくっつけるように、開閉用のボタン領域を用意する。
実際の文字とは異なるが、大まかなイメージ図は以下のような感じだ。
さて、画面をスクロールした時のことを考えてみよう。
サイドメニューはスクロールしてもついてきてほしいので、画面に対する位置で配置することにする。
そうしなければ、サイドメニューを開くために一番上までスクロールを戻さなければいけなくなり、恩恵が薄くなってしまう。
以上が、ごく簡単ではあるが見た目の内容だ。
サイドメニューの動作
では、次に動作を考えていこう。
まず、サイドメニュー内の各コンテンツをクリックしたら、メインの表示をその内容に切り替えたい。
同時に、サイドメニューも隠してしまおう。
メイン表示の切り替えは瞬時で、サイドメニューを隠すのはアニメーションの方がいいだろう。
また、実際に中身が用意されているものについては、マウスオーバーで表示を強調するようなこともしてみよう。
こうすれば、どの項目がクリックできるか分かりやすくなるだろう。
この部分に限定すれば、前回の内容と非常に似ている。
その他、最初からサイドメニューが表示されているのもちょっとどうかと思うので、最初は画面外に隠しておく。
その状態で開閉ボタンを押せば、メニューが出てくるような動きも実装してみよう。
また、サイドメニューが出ている時はメインコンテンツ部分を暗くして、サイドメニューを強調する。
この暗くなっている部分は、クリックされるとコンテンツの中身を変更せずにサイドメニューを閉じるような動きにしたい。
このサイドメニューの動きは、全てアニメーションで実装していく。
サイドメニューに限定すれば、動きはこんなものだろう。
ページ全体の構造
さて、サイドメニューを出すことにより、各領域が重複する部分が出てくる。
これは何も設定しないと書いた順番で、下のものが手前に表示される仕組みになっている。
しかし、それだと分かりづらいので、cssのz-index
プロパティで調整していくことにしよう。
これにより、順番を気にしなくてもよくなる。
以上で、ざっと概要レベルではあるが組む内容は大体定まった。
サンプルページ
では、そのサンプルページを紹介しよう。
まずリンクはこちら、ディレクトリ構造は以下の通りだ。
- jquery
- course-06.html
- js
- jquery-06.js
- css
- style-06.css
今回は、前回まで共通で使っていたstyle.cssをやめにして、全てstyle-06.cssで作成している。
そこだけ普段と異なるので、ちょっと注意してほしい。
今後ももしかしたらこの形にする…かもしれない。
では、サンプルの動作を見てみよう。
基本は上で説明した通りの動きになっているはず。
左側にサイドメニューのボタンがあり、それをクリックするとサイドメニューが出てくる。
それぞれのコンテンツで押せる部分はカーソルを重ねると変化する。
そして、それを押してもらうとコンテンツの中身が切り替わり、サイドメニューが閉じる。
また、サイドメニューを開いた状態で暗いところをクリックしても、サイドメニューが閉じるはずだ。
最後、コンテンツ1の中身をスクロールできるようにしておいたので、それでサイドメニューがついてくることも確認できるだろう。
中身がほぼ何もないが…そこは実際にページを作成するときに考えることなので、今回はパスさせてもらった。
サンプルソースは以下の通り。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>jQuery講座第6回サンプルページ</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-06.js"></script>
<!-- 必要に応じてcssファイル読み込み -->
<link rel="stylesheet" type="text/css" href="./css/style-06.css">
</head>
<body>
<!-- ヘッダー -->
<div class="header">
<h1 id="page-title">jQuery講座第6回サンプルページ</h1>
</div>
<!-- サイドメニュー領域 -->
<div class="side-menu">
<p>サイドメニュー</p>
<ul>
<li class="contents1">メニュー1</li>
<li class="contents2">メニュー2</li>
<li class="has-child-menu">メニュー3
<ul>
<li class="contents3-1">メニュー3-1</li>
<li class="contents3-2">メニュー3-2</li>
<li class="contents3-3">メニュー3-3</li>
</ul>
</li>
</ul>
<div class="side-menu-button">サイドメニュー</div>
</div>
<!-- サイドメニュー表示時の他領域を隠す用 -->
<div class="hide-contents"></div>
<!-- メインコンテンツ -->
<div class="main-contents">
<!-- コンテンツ1 -->
<div class="contents contents1 default-contents">
<!-- スクロールできるよう改行を大量に入れる -->
<p>コンテンツ1</p>
<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
<p>あああ</p>
<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
<p>あああ</p>
<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
<p>あああ</p>
</div>
<!-- コンテンツ2 -->
<div class="contents contents2">
<p>コンテンツ2</p>
</div>
<!-- コンテンツ3-1 -->
<div class="contents contents3-1">
<p>コンテンツ3-1</p>
</div>
<!-- コンテンツ3-2 -->
<div class="contents contents3-2">
<p>コンテンツ3-2</p>
</div>
<!-- コンテンツ3-3 -->
<div class="contents contents3-3">
<p>コンテンツ3-3</p>
</div>
</div>
</body>
</html>
$(function(){
/* 現在サイドメニューを開いているか */
var isOpenSideMenu = false;
/* サイドメニュー開閉にかける時間 */
var durationSideMenu = 300;
/* サイドメニュー項目変化にかける時間 */
var durationSideMenuContents = 100;
/* サイドメニューを開く処理 */
function showSideMenu(){
$(".side-menu").animate(
{
"left": "0px"
},
durationSideMenu
);
$(".hide-contents")
.css("display", "block")
.animate(
{
"opacity": "0.5"
},
durationSideMenu);
isOpenSideMenu = true;
}
/* サイドメニューを閉じる処理 */
function hideSideMenu(){
$(".side-menu").animate(
{
"left": "-300px"
},
durationSideMenu
);
$(".hide-contents").animate(
{
"opacity": "0"
},
durationSideMenu,
function(){
$(".hide-contents").css("display", "none");
});
isOpenSideMenu = false;
}
/* コンテンツを切り替える処理 */
function changeContents(contentsClass){
$(".main-contents .contents").css("display", "none");
$(".main-contents ." + contentsClass).css("display", "block");
}
/* サイドメニューボタンをクリックした際のイベント登録 */
$(".side-menu-button").on("click", function(){
if(!isOpenSideMenu){
showSideMenu();
}else{
hideSideMenu();
}
});
/* 非表示領域をクリックした際のイベント登録 */
$(".hide-contents").on("click", hideSideMenu);
/* サイドメニューの項目に関するイベント登録 */
$(".side-menu ul li:not(.has-child-menu)")
/* カーソルを重ねた時 */
.on("mouseover", function(){
$(this).stop(true).animate(
{
"background-color": "aqua"
},
durationSideMenuContents
);
})
/* カーソルを離した時 */
.on("mouseout", function(){
$(this).stop(true).animate(
{
"background-color": "#EEEEEE"
},
durationSideMenuContents
);
})
/* クリックした時 */
.on("click", function(event){
var contentsClass = event.target.className;
changeContents(contentsClass);
hideSideMenu();
});
});
/* ページ全体の設定 */
body {
margin: 0;
font-family: "Avenir Next";
}
/* ヘッダー領域の設定 */
.header {
padding-top: 1rem;
padding-bottom: 1rem;
background-color: black;
color: white;
text-align: center;
}
/* メイン表示領域の設定 */
.main-contents {
width: 50%;
margin: 0 auto;
}
/* サイドメニュー領域の設定 */
.side-menu {
position: fixed;
top: 0;
left: -300px;
width: 300px;
height: 100%;
background-color: #EEEEEE;
z-index: 999;
}
/* サイドメニュー内、上部テキストの設定 */
.side-menu p {
font-size: large;
font-weight: bold;
text-align: center;
}
/* サイドメニュー内、箇条書きの設定 */
.side-menu ul {
list-style: none;
width: 80%;
margin: 0 auto;
}
/* サイドメニュー内、子要素を持たないリスト項目の設定 */
.side-menu ul li:not(.has-child-menu){
padding: 0.2rem 0.5rem;
border-left: 3px solid blue;
}
/* サイドメニュー内、子要素を持つリスト項目の設定 */
.side-menu ul li.has-child-menu {
padding: 0.2rem 0.5rem;
border-left: 3px solid #EEEEEE;
}
/* サイドメニューの開閉ボタンの設定 */
.side-menu .side-menu-button {
position: absolute;
top: 123px;
left: 300px;
margin: 0;
padding: 0.5rem;
background-color: #EEEEEE;
writing-mode: vertical-rl;
}
/* サイドメニュー表示時に隠すためのブロックの設定 */
.hide-contents {
display: none;
position: fixed;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
background-color: black;
opacity: 0;
z-index: 998;
}
/* 最初に表示しない項目の設定 */
.contents:not(.default-contents) {
display: none;
}
HTML、CSSでの設定内容
では、ここから見ていこう。
タイトルの部分はこれまで通り。
サイドメニューと、それが出てきた時に他を隠す部分は以下の通り。
<!-- サイドメニュー領域 -->
<div class="side-menu">
<p>サイドメニュー</p>
<ul>
<li class="contents1">メニュー1</li>
<li class="contents2">メニュー2</li>
<li class="has-child-menu">メニュー3
<ul>
<li class="contents3-1">メニュー3-1</li>
<li class="contents3-2">メニュー3-2</li>
<li class="contents3-3">メニュー3-3</li>
</ul>
</li>
</ul>
<div class="side-menu-button">サイドメニュー</div>
</div>
<!-- サイドメニュー表示時の他領域を隠す用 -->
<div class="hide-contents"></div>
/* サイドメニュー領域の設定 */
.side-menu {
position: fixed;
top: 0;
left: -300px;
width: 300px;
height: 100%;
background-color: #EEEEEE;
z-index: 999;
}
/* サイドメニュー内、上部テキストの設定 */
.side-menu p {
font-size: large;
font-weight: bold;
text-align: center;
}
/* サイドメニュー内、箇条書きの設定 */
.side-menu ul {
list-style: none;
width: 80%;
margin: 0 auto;
}
/* サイドメニュー内、子要素を持たないリスト項目の設定 */
.side-menu ul li:not(.has-child-menu){
padding: 0.2rem 0.5rem;
border-left: 3px solid blue;
}
/* サイドメニュー内、子要素を持つリスト項目の設定 */
.side-menu ul li.has-child-menu {
padding: 0.2rem 0.5rem;
border-left: 3px solid #EEEEEE;
}
/* サイドメニューの開閉ボタンの設定 */
.side-menu .side-menu-button {
position: absolute;
top: 123px;
left: 300px;
margin: 0;
padding: 0.5rem;
background-color: #EEEEEE;
writing-mode: vertical-rl;
}
/* サイドメニュー表示時に隠すためのブロックの設定 */
.hide-contents {
display: none;
position: fixed;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
background-color: black;
opacity: 0;
z-index: 998;
}
こんな感じになっている。
まず、サイドメニュー領域全体をdiv
タグで用意。
配置をposition: fixed;
で設定することにより、画面がスクロールされてもついてくるような動きになる。
厳密には、スクロールの影響を受けない、と言った方がいいだろうか。
で、最初は表示されてほしくないので、幅の大きさの分だけマイナスでleft
を指定し、画面外に隠している。
その中、先頭にサイドメニュー部分のタイトルをp
タグで設置し、cssで表示を大きくしている。
次に、各項目を箇条書きのul
タグを使って列挙。
この時、各li
については、以下のような決まりでクラス名をつけている。
- コンテンツがあるものは、そのコンテンツと同じクラス名をつける
- コンテンツがないものは、子要素を持たないことを表すクラス名をつける
前提として、子要素を持たない項目はコンテンツがあり、逆に子要素を持つ項目はコンテンツがないという条件を持たせている。
これで、コンテンツがあるものについては左側に青でスタイルを調整している、というわけだ。
ここでcssに関する補足を。
今回、疑似クラスというものを使っている。
これは、セレクタに対して特定の条件で適用対象を絞るために使うものだ。
具体的には、今回は:not
という疑似クラスを使用している。
使い方は以下の通り。
セレクタ:not(除外する条件) {
適用内容
}
こう書くことで、まずセレクタの中身が対象となる。
次に、not
の後ろの小括弧内に除外する条件を書き、これでその条件に合致しないような対象のみに適用内容を反映させることができる。
今回使っているものの一つを出してみよう。
/* サイドメニュー内、子要素を持たないリスト項目の設定 */
.side-menu ul li:not(.has-child-menu){
padding: 0.2rem 0.5rem;
border-left: 3px solid blue;
}
これについて、まず.side-menu ul li
までで、とりあえずクラス名に.side-menu
とあるタグの中のul
タグ、さらにその中に入っているli
タグが対象となる。
直下だけではなく、孫要素以下も対象になる点に注意。
そして、:not(.has-child-menu)
がついていることで、クラス名にhas-child-menu
が指定されているタグがこの設定の対象から外れる、という動きだ。
これ以降もガンガン使うので、覚えておいて欲しい。
ではHTMLの続きで、今度は開閉ボタンの設置だ。
ボタンと言いながらdiv
要素で定義しているが、これに対してイベントを登録すれば問題ない。
で、配置はこの領域のすぐ右にくっつくような形だ。
position
にはabsolute
を指定し、これで親要素…サイドメニュー部分の左上からの位置で配置できる。
この状態でleft
にサイドメニューの幅と同じ数値を設定することで、この部分だけはみ出して右にくっつくような表示をしている。
最後、サイドメニューが出ている時に画面を隠す部分について。
ここもHTML側では単にdiv
タグで領域を用意しているだけ。
cssでは、最初は表示をOFFにするためにdisplay: none;
を設定。
その他、やはりこれも画面のスクロールに影響されたくないのでposition
をfixed
に。
サイズは画面全体を覆いたいので、上と左の位置は0
、縦横の長さは100%
を指定している。
また、完全に黒塗りではなく、少し暗くすることを表現するために、不透明度のopacity
を使う。
ここもアニメーションでだんだん暗くしたいので、最初は完全に透明にする0
を設定。
ちなみに、この透明処理をしていればdisplay: none;
は不要なのでは?と思われるかもしれない。
しかし、これを設定していないと後述する重ね合わせの影響で、メインコンテンツにボタン等を配置してもクリックすることができなくなる。
もしご自身の環境で真似しているのであれば、実際にそうなることを試してみて欲しい。
さて、ここまででとりあえずの見た目はOKだ。
あとは、領域の重ね合わせの部分について、前述の通りz-index
プロパティを使用している。
これに設定する数値が大きい要素が前に出てくるので、サイドメニューに999
、黒塗りの領域に998
を指定。
他は設定せず、これでこの二つが前面に出てくるようになった。
ちなみにだが、このz-index
はposition
で初期値のstatic
以外を設定してある要素にしか使えない。
設定しても反映されない場合には、position
を設定していないか、あるいはstatic
になっているかのどちらかの場合があるので、そこを確認しよう。
以上がサイドメニュー絡みの内容。
あとはコンテンツ部分だが、ここはシンプルでとりあえずコンテンツを全てdiv
タグで囲み配置し、それ全体もmain-contents
というクラスをつけたdiv
で囲んでいる。
それぞれのコンテンツに、先ほどメニュー項目につけたものと同じクラス名をつけておき、また最初に表示されていて欲しい一番上のコンテンツにはdefault-contents
というクラス名をつけてある。
css側では、やはり先ほど解説した疑似クラスの:not
を使い、default-contents
がついていないような要素を全て非表示にしている。
さて、ここまではHTMLやcssの復習。
今回のjQueryの操作では基本的にクラス名を使うので、どの領域にどんなクラス名がついているかを確認しておこう。
jQueryでの設定内容
では、そのjQueryの内容を。
ソースの上から順番に見ていこう。
まず、三つの情報を保持する変数をそれぞれ定義。
/* 現在サイドメニューを開いているか */
var isOpenSideMenu = false;
/* 変化にかける時間 */
var durationSideMenu = 300;
/* サイドメニュー項目変化にかける時間 */
var durationSideMenuContents = 100;
それぞれコメントに書いている通りだが、一つ目は今サイドメニューを開いているかのフラグ。
true
ならサイドメニューを開いており、false
なら閉じていることを表しており、デフォルトは閉じているのでfalse
だ。
二つ目はサイドメニュー開閉のアニメーションにかける時間で、今回は300ミリ秒を設定している。
そして三つ目はサイドメニューにカーソルを重ねたり離したりした時にかける時間、こちらは100ミリ秒。
たった0.1秒ならほぼ変わらないのでは?と思うかもしれないが、これでも雰囲気は変わる。
まあ、そこまで凝らなくてもいいという場合は、後でこの部分をanimateではなくcssで処理すればいい。
これらを後の処理で使うので、覚えておいて欲しい。
次は、ここから三つほど関数を用意している。
一つずつ見ていこう。
/* サイドメニューを開く処理 */
function showSideMenu(){
$(".side-menu").animate(
{
"left": "0px"
},
durationSideMenu
);
$(".hide-contents")
.css("display", "block")
.animate(
{
"opacity": "0.5"
},
durationSideMenu);
isOpenSideMenu = true;
}
一つ目はサイドメニューを開く際の処理。
まず、サイドメニューが画面外にあるはずなので、animateメソッドでその左端であるleft
プロパティを0
にすることにより、画面内にスライドインさせている。
同時に、他領域を隠す部分も行う。
ここでは、まず非表示だった領域を表示するようcssメソッドで指定。
そのままanimateメソッドを繋げて、半透明に変化させている。
最後、今サイドメニューが表示されたので、フラグ変数を更新、という流れだ。
なお、今回は監視対象となる要素とイベントで処理をする要素が異なるので、this
によるセレクタは使用していない。
一つ目は以上で、二つ目に進んでいこう。
/* サイドメニューを閉じる処理 */
function hideSideMenu(){
$(".side-menu").animate(
{
"left": "-300px"
},
durationSideMenu
);
$(".hide-contents").animate(
{
"opacity": "0"
},
durationSideMenu,
function(){
$(".hide-contents").css("display", "none");
});
isOpenSideMenu = false;
}
二つ目は一つ目の逆で、今度はサイドメニューを隠す関数だ。
サイドメニュー領域は上と同じく左の位置を調整しているだけ。
画面を隠す部分は、今度は先に不透明度を0
…つまり完全に透明にし、その後処理として非表示にしている。
ここで、メソッドチェーンでcssメソッドを繋げると、透明になっていくアニメーションが表示されないことに気を付けよう。
最後、これでサイドメニューが閉じたのでフラグをfalse
に更新して完了だ。
三つ目も見ていく。
/* コンテンツを切り替える処理 */
function changeContents(contentsClass){
$(".main-contents .contents").css("display", "none");
$(".main-contents ." + contentsClass).css("display", "block");
}
ここは、メインで表示しているコンテンツ部分の調整をしている。
引数として今表示させたいコンテンツのクラス名を受け取る。
先にコンテンツの中身を全て非表示にして、その後受け取ったクラス名のコンテンツだけを表示する、という動き。
これで、関数定義部分は終了。
ここからは、イベントの登録になる。
ここも順番に見ていこう。
/* サイドメニューボタンをクリックした際のイベント登録 */
$(".side-menu-button").on("click", function(){
if(!isOpenSideMenu){
showSideMenu();
}else{
hideSideMenu();
}
});
最初の部分は、サイドメニューの開閉ボタンをクリックした際の処理だ。
条件分岐をして、もし元々サブメニューが開いていなければ開き、開かれていれば閉じるというだけ。
フラグの変更も呼び出している関数内で実行しているので、ここでその更新を行う必要はない。
次は、隠した領域をクリックされた時の処理。
/* 非表示領域をクリックした際のイベント登録 */
$(".hide-contents").on("click", hideSideMenu);
非常にシンプルで、クリックされたら単にサイドメニューを閉じることだけをしている。
この部分がクリックできる状況というのは、サイドメニューが開かれている時のみだ。
そのため、ここでは分岐の必要がないので、呼び出すだけで済んでいる。
そして、サイドメニューの各項目に関する処理。
厳密にはコンテンツが用意されているものに対してイベント処理を実装している。
ここはマウスオーバー、マウスアウト、クリックの3つをメソッドチェーンで定義だ。
/* サイドメニューの項目に関するイベント登録 */
$(".side-menu ul li:not(.has-child-menu)")
/* カーソルを重ねた時 */
.on("mouseover", function(){
$(this).stop(true).animate(
{
"background-color": "aqua"
},
durationSideMenuContents
);
})
/* カーソルを離した時 */
.on("mouseout", function(){
$(this).stop(true).animate(
{
"background-color": "#EEEEEE"
},
durationSideMenuContents
);
})
/* クリックした時 */
.on("click", function(event){
var contentsClass = event.target.className;
changeContents(contentsClass);
hideSideMenu();
});
ここで使っているので分かると思うが、cssで使っていた疑似クラスは、今回の:not
に限らずやはりここでも使えるので覚えておくといい。
カーソルによる変化は前回のものとほぼ同様、シンプルに背景色を変更しているだけ。
クリックした時が、ちょっとこれまでと変わっているのでよく見てみよう。
まず、イベントハンドラの無名関数で、event
という引数を受け取っている。
これはイベントオブジェクト、第3回のオマケで解説したものだ。
これにはイベントに関する情報が格納されており、例えばクリックされた際のカーソルの座標や、イベントが発生した要素の情報などがここから取得できる。
もちろん、その要素のクラス名も格納されており、今回の書き方、event.target.className
でそれを取得することができる。
で、前半で説明した通り、今回は各項目にコンテンツが設定されていれば、同じクラス名をつける約束にしていた。
ということで、このクラス名を持つコンテンツを表示するよう関数を呼び出してあげれば切り替えが可能になる。
最後、この時もサイドメニューを閉じたいので、それを呼び出している、という流れだ。
以上、これだけでサイドメニューとそれに関する処理が実装できた。
おわりに
今回は、見え隠れするサイドメニューを実装してみた。
もっと手間がかかると思っていたので、私自身ちょっとびっくりしている。
これがあるだけでだいぶ見た目をすっきりさせることができるので、是非チャレンジしてみよう。
さて、次回は…現状どうするか迷っているところだ。
幾つか案はあるが、どの順番でやるかは未定。
あるいは、先にサンプルを全て組んだ後、一つずつ解説、という形にするかもしれない。
とはいえ、今回までの内容を応用させればできることが多いので、是非自分でも色々チャレンジしてみてほしい。
2021/2/21追記
次回の内容、スライドショーを実装してみた。
結局、今回のように一つの内容を解説という形だ。
難易度を分けて二つ用意したが、できれば上級編にもチャレンジしてみてもらいたい。
コメント