Vanilla JSでドロワーメニューのオプション機能を作成して、左右どちらから出てくるか&スピードを選べるようにしました。
こんにちは。Webコーダーのはるです。
現在、所属している「もりけん塾」でJavaScriptのハンズオン課題 に取り組み、レビューをいただいています。
今日は、課題31 についてアウトプットをしていきます。
(前回までのアウトプットは、こちらです。)
実装する過程で学んだことを、学習ノートとして記録していきます。
認識の間違えている箇所がありましたら、お問い合わせからご指摘いただけるとうれしいです。
JavaScript課題31の仕様
(もりけんさんのJavaScriptハンズオン課題より)
JavaScript課題31の学習記録
主にレビューいただいた箇所の復習として記録を残します。
制作物
前回作成した基本のドロワーメニューにオプショナルをつけました。
drawer-menu.js の 以下のオブジェクトの値を変更すると、実際にメニューの動きを編集できます。
(メニューは、ログイン画面・会員登録画面のハンバーガーボタンを押すと開閉します)
const menuOption = {
direct : "left", // left or right
speed : 400 // number
};
オプション機能 完成版
前回作成した基本のドロワーメニューに追加したJavaScriptです。
// JavaScript
const option = {
direct: "left", //left or right
speed: 400 // number(ms)
};
const setDirect = (menu, direct) => {
switch(direct){
case "right":
menu.classList.add("right");
break;
case "left":
default:
menu.classList.add("left");
break;
}
}
const convertMillisecondsToSeconds = speed => `${speed / 1000}s`;
const formatSeconds = (speed = 400) => {
if(typeof speed !== "number"){
throw new Error(`speed expect number type. but got ${typeof speed}`)
}
return convertMillisecondsToSeconds(speed);
}
const setSpeed = (menu, speed) => {
menu.style.transitionDuration = formatSeconds(speed);
}
const initMenu = (menu, option = {}) => {
setDirect(menu, option?.direct);
setSpeed(menu, option?.speed);
}
initMenu(drawerMenu, option);
オプション① ドロワーメニューの方向を変える
いただいたレビューも記録しながら、コードを分解していきます。
まず、オプションとはそれ自体undefinedになる想定であって、あってもなくても良いもの -> あれば設定を行うもの だと教えていただきました。(よく考えれば、swiperとかsplideとか他のライブラリでもそうだった…)
オプションがセットされている時は、以下のように使用しますが
initMenu(drawerMenu, option);
オプションがない時は、以下でも動くコードにする必要があります。
initMenu(drawerMenu);
レビュー
はじめにPR提出した時のコードです(ドロワーメニューの方向を変える部分のみ)
const menuOption = { direct : "left" }; //left or right
const setMenuOption = () => {
const {direct} = menuOption;
changeMenuDirect(direct, drawerMenu);
}
const changeMenuDirect = (direct, menu) => {
switch(direct){
case "right":
menu.classList.add("is-right");
break;
case "left":
menu.classList.add("is-left");
break;
default:
menu.classList.add("is-left");
break;
}
setMenuOption();
optionは第二引数にすべき(optionがないときもあるので)
関数名、クラス名の修正
- optionは任意なので毎回セットするわけではない :
setMenuOption
-> initMenu()に修正 - 方向は毎回変えるわけではない :
changeMenuDirect() -> setDirect()に修正
- is-**クラスはJavaScriptではbooleanを扱うことが多いので、CSSの命名がわかりにくい。-> .left .rightに修正
initMenu()でoptionをまるっと取得して、任意のoptionが存在すれば引数で渡す
// 修正前
const setMenuOption = () => {
const {direct} = menuOption;
changeMenuDirect(direct, drawerMenu);
}
// 修正後
const initMenu = (menu, option = {}) => {
setDirect(menu, option?.direct);
}
この時、initMenu()に渡す引数は
- menu -> ドロワーメニューのHTML要素
option = {}
-> オプションをまるっと取得。デフォルト引数を設定することで、optionがundefinedだったときには空の{ } を渡すことができる。
setDirect() 関数に option?.direct とすることで、
optionオブジェクトの中にdirectキーが存在しないときはエラーにならずにundefinedを返します。
setDirect(menu, option?.direct);
?. はオプショナルチェーンと呼ばれる演算子です。
これは、オブジェクトのプロパティやメソッドにアクセスする際に、そのオブジェクトが存在しない場合でもエラーを回避できる機能です。
switch文のcaseとdefaultをまとめて記述できる
const setDirect = (menu, direct) => {
switch(direct){
case "right":
menu.classList.add("right");
break;
case "left":
default: //一緒に記述可能
menu.classList.add("left");
break;
}
}
ケース defaultは、direct引数にundefinedが渡った時 = optionが存在しない時 に実行されます。
メニューを右表示に設定すると、メニューの移動変更が見えてしまう
optionのdirectをrightにした時、メニューが移動するのが見えてしまう(デフォルトをleftにしているためと思われる)
(GitHub PR画面より動画引用)
動作確認でご指摘をいただき、メニュー移動が見えないように配慮しました。
(ちょっと強引感ある…ベストアンサーがあればぜひご教授ください…)
記述量が多いので、変更した部分だけ載せてます..
.header__nav{ // ドロワーメニュー本体
opacity: 0;
top : 0;
}
.header__nav.left{
left : -100%; // transformではなくleftで移動
opacity: 1; // leftの値をセットしてからopacity: 1にする
will-change: left;
transition: left ease-in-out;
}
.header__nav.right{
right : -100%; // transformではなくrightで移動
opacity: 1; // rightの値をセットしてからopacity: 1にする
will-change: right;
transition: right ease-in-out;
}
.header__nav.left.is-open{
left: 0;
}
.header__nav.right.is-open{
right: 0;
}
もっといい方法がありそう・・・。
オプション② スピードを変更する
swiperやsplideのオプション設定を見ると、スピードの単位にミリ秒を使用していました。
JavaScriptでは(その他プログラミング言語でも)、標準的な時間管理の単位としてミリ秒が採用されていることを知りました。
- プログラム間で時間の表現や計算が統一され互換性が保たれる
- ミリ秒単位で時間を扱うことで、秒単位や分単位などの他の単位へ容易に変換できる
そこで、オプションではミリ秒を指定して、transition-duration(CSS)を設定するときに秒数に変換するという練習をしてみることにしました。(ミリ秒でも動くらしいが..)
レビュー
はじめにPRした時のコードです。
const option = {
direct: "left", //left or right
speed: 400, // number(ミリ秒)
};
const convertMillisecondsToSeconds = speed => `${speed / 1000}s`;
const changeSpeed = (menu, speed) => {
const defaultSpeed = "0.4s";
const judgedSpeed = typeof speed === "number"? convertMillisecondsToSeconds(speed): defaultSpeed;
menu.style.transitionDuration = judgedSpeed;
}
const initMenu = (menu, option = {}) => {
changeDirect(menu, option?.direct);
changeSpeed(menu, option?.speed);
}
initMenu(drawerMenu, option);
formatする関数と、setする関数を分けた方がわかりやすい
下記のコードを分割することとしました。
const changeSpeed = (menu, speed) => {
const defaultSpeed = "0.4s";
const judgedSpeed = typeof speed === "number"? convertMillisecondsToSeconds(speed): defaultSpeed;
menu.style.transitionDuration = judgedSpeed;
}
この関数でやっていたこと
- number型であれば、引数のスピードを秒数に変換する
- number型でなければ、デフォルトのスピードを使用
- これらのスピードをtransition-durationにセットする
ちょっとやっている内容が多かった..
以下のように、formatとsetの関数を分けました。
// formatする
const formatSeconds = (speed = 400) => {
if(typeof speed !== "number"){
throw new Error(`speed expect number type. but got ${typeof speed}`)
}
return convertMillisecondsToSeconds(speed);
}
// setする
const setSpeed = (menu, speed) => {
menu.style.transitionDuration = formatSeconds(speed);
}
デフォルトのスピードは別途定義するのではなく、デフォルト引数として設定することでundefinedに対応することができます。-> undefinedが渡ってくることが想定できている場合は、デフォルト引数が有用だと知りました。
また、number型以外が渡ってきた時は、エラーをthrowすることで、使用側にそれを伝えることができるようにしました。
throw New errorは、console.errorと違い、その後の動きを全てストップさせます。
そのため、今回の場合はconsoleにエラーが表示されて、transition-durationもセットされません。
(仕様にはなかったのですが、number型以外の場合も考えたかったので、レビューで教えていただいたこちらの方法で実装することとしました。とても勉強になりました。)
> 関数のデフォルト引数は、関数に値が渡されない場合や undefined
が渡された場合に、デフォルト値で初期化される形式上の引数を指定することができます。
//
学習に使用している本は、JavaScript本格入門・独習JavaScriptです。
あとがき
もりけんさん(@terrace_tec)、さえさん(@sae_prog)、まいさん(@mai2022web) お忙しい中レビューいただきありがとうございました!
初っ端からオプションの意味をうまく捉えられておらず、出発点からの修正となりました🙇♀️
デフォルト引数やオプショナルチェーンなどを学び、修正を重ねることで、スマート&undefinedにも対応できるコードになりました。
ありがとうございました!
教えていただいたことを生かして、次の課題に進みたいと思います!
今日は以上です。
//
【もりけん塾で勉強しています】
もりけんさん(@terrace_tec)のHPはこちら