現在、もりけん塾にてJavaScriptを勉強させていただいております。その記録。
今回は課題18についての備忘録です。
考え方が間違っている箇所もあるかと思いますが、お気づきの際はご指摘いただければと思います。
課題18について
仕様
①スライドショーにドットのページネーションを作りましょう。それぞれのドットはクリッカブルになっていて、押下するとその画像に切り替わります。それとともに1/5も切り替わります。
②また、3秒毎に次のスライドに自動で切り替わるauto
機能を提供してください。
制作物
コードはこちらから(codesandbox)※codesandboxではエラーが出る場合がありますが、リロードすれば動きます。
実装内容
今回は課題17で作成したスライドショーへの追加仕様の実装なので課題17で作成したコードへ追加しました。
課題17から変わった点
①letでの宣言をconstでオブジェクト代入へ変更
前回までの実装では、letでimgNum(スライドで何枚目の画像が表示されているかを取得するための変数)を宣言し、再代入させていました。それをconstでオブジェクトに代入してはどうかとレビューをいただきました。constを使うことで予期せぬ再代入での不具合等を防ぐことができると感じ、こちらを採用させていただきました。(自分では思いつかないアイディアです・・!)
このくらいの実装であればどこで不具合が起こっているか発見しやすいかもしれないですが、これが大きなプロジェクトになると追うのは大変になるので、なるべく不具合の原因になりそうなことは潰しておくのがいいのかなと思いました、今後も意識したいところです。
②似ている関数を共通化
前回まで、prev/nextボタンをそれぞれの関数に分けて作成していたのですが、工程がほぼ一緒なので共通化できるのではとレビューいただきました。prev/nextの配列を作成し、forEachで回す。これは本当にスッキリして嬉しかった。アイディアいただき感謝です。こういう発想もできるようになりたい。。
三項演算子もまだ苦手かな。
//let imgNum = 0;
const imgNum = { count : 0 };
// 呼び出し側では
//imgNum.count -= 1;
//としてあげる
const imgList = document.getElementsByClassName("imgList");
const createBtnElements = (imgData) => {
const btnDirections = ["next", "prev"];
btnDirections.forEach((btnDirections) => {
const btn = document.createElement("button");
btn.classList.add(`${btnDirections}`);
btn.id = btnDirections === "prev" ? "js-prevbtn" : "js-nextbtn";
btn.textContent = btnDirections === "prev" ? "◀︎" : "▶︎";
imgListsWrapper.appendChild(btn);
btn.disabled = btnDirections === "prev";
btn.addEventListener("click", function () {
btnDirections === "prev" ? (imgNum.count -= 1) : (imgNum.count += 1);
document.querySelector(".is-show").classList.remove("is-show");
imgList[imgNum.count].classList.add("is-show");
switchPagenation(imgNum.count);
switchDisableForBtn(imgData);
setNumberOfPage(imgData);
resetAutoPlay(imgData);
});
});
};
追加仕様①:ページネーション の追加
renderPagenation() にて、ページネーション の要素を作成する。一番初めの要素にはis-activeのクラスを追加する。
switchPagenation() は、prev/nextボタンを押した時のページネーション切り替えをするための関数の作成。
clickedPagenation() は、それぞれのページネーションをクリックした際の動き。クリックされたページネーションのインデックスを取得し、is-active(ページネーション切り替え)/is-show(画像切り替え)を付与してあげる。以前、塾生でthisを使用しているのを見て、スッキリしていていいなあと思い真似して使ってみました。ここはスッキリしていると思う。たぶん。
const renderPagenation = (imgData) => {
const pagenation = createElementWithClassName("ul", "pagenation");
const pagenationsFragment = document.createDocumentFragment();
for (let i = 0; i < imgData.length; i++){
const pagenations = createElementWithClassName("li", "pagenations");
const pagenationItems = createElementWithClassName("span", "pagenation-item");
pagenationItems.dataset.index = i;
i === 0 && pagenationItems.classList.add("is-active");
pagenationsFragment.appendChild(pagenations).append(pagenationItems);
}
imgListsWrapper.appendChild(pagenation).appendChild(pagenationsFragment);
}
const pagenationItems = document.getElementsByClassName("pagenation-item");
const switchPagenation = (imgNum) => {
document.querySelector(".is-active").classList.remove('is-active');
pagenationItems[imgNum].classList.add('is-active');
}
const clickedPagenation = (imgData) => {
for (const pagenationItem of pagenationItems) {
pagenationItem.addEventListener("click", function () {
const arrayPagenationItems = Array.from(pagenationItems);
imgNum.count = arrayPagenationItems.indexOf(this);
document.querySelector('.is-active').classList.remove('is-active');
arrayPagenationItems[imgNum.count].classList.add('is-active');
document.querySelector(".is-show").classList.remove('is-show');
imgList[imgNum.count].classList.add('is-show');
switchDisableForBtn(imgData);
setNumberOfPage(imgData);
resetAutoPlay(imgData);
})
}
}
追加実装②:3秒毎に切り替わるatuo再生機能の追加
setIntervalにて3秒毎にimgNumを更新する。最後の画像になったら0へ戻る。
当初autoImgSwitch()のみで実装していたのですが、setIntervalがずっと走り続けているせいでクリックした際に変なタイミングで次へ移ってしまう現象があるとのご指摘を受け、resetAutoPlay() を追加しました。clearIntervalで一旦停止し、また再度autoImgSwitch() を起動させるという関数です。この関数をクリックイベントの中に入れました。
let autoPlay;
const autoImgSwitch = (imgData) => {
autoPlay = setInterval(() => {
imgNum.count++;
if (imgNum.count === imgData.length) {
imgNum.count = 0;
}
document.querySelector(".is-show").classList.remove('is-show');
imgList[imgNum.count].classList.add('is-show');
switchPagenation(imgNum.count);
switchDisableForBtn(imgData);
setNumberOfPage(imgData);
}, 3000)
}
const resetAutoPlay = (imgData) => {
clearInterval(autoPlay);
autoImgSwitch(imgData);
};
その他レビューPOINT
戻り値の変数名と関数名に相違があったので揃えました。
コードが進むにつれこういう細かい箇所を見落としがちなので気をつけたいところです。
//const getImgListsFragment = (imgData) => {
const getFragmentImglists = (imgData) => {
const fragmentImglists = document.createDocumentFragment();
for (let i = 0; i < imgData.length; i++) {
const imgList = createElementWithClassName("li", "imgList");
const img = createElementWithClassName("img", `img_0${[i + 1]}`);
imgList.dataset.index = i;
img.src = imgData[i].img;
img.alt = imgData[i].description;
i === 0 && imgList.classList.add("is-show");
fragmentImglists.appendChild(imgList).appendChild(img);
}
return fragmentImglists;
};
後記
自分だけで実装していると、仕様を満たした時点で満足してしまいがちですが、ユーザー視点に立つと色々見落としがあるもんだなと思いました。また、setInterval/clearIntervalがいまいちスッと動いてくれずつまづいたり、基本的な書き方がまだ理解不足だということも実感しました。
似ている実装はまとめられるということも勉強になったので、今後意識したいところだなと思います。今回もとても勉強になりました。動くものができるのは楽しいです。
c.sakyou