しょーごさんの中級課題Exの header と mv(fv) の制作過程

しょーごさんの課題

header

各種要素の並べ方

ロゴ、グローバルナビゲーション、宿泊予約、ハンバーガーメニューの配置については、

headerにdisplay: flex;と各子要素にmargin-leftを使い調整しています。

flex と margin の関係についてはこちらの記事を参考にしてください!

ロゴ

ロゴ部分は、どのデバイスで見ても同じ見た目になるようにimg(png)で作成しました。

最初はsvgにしていたんですが、スマホで確認するとフォントが全然違うものになっていたので、svgのフォント部分はfont-familyの影響を受けるようです。

初めて知りました。。。

キーボードで入力されたテキストはfont-familyの影響を受け、ひとつひとつの文字を手作業?で作られたフォントに関しては影響受けないっぽいです。

宿泊予約

ボタン

スマホサイズの時は、宿泊予約についてはサイズを小さくしアイコンの下に「宿泊予約」の文字が入るようにしました。

モーダルの高さ

  • モーダルのheightを100vh
  • モーダルのコンテンツ部分の max-height を calc(100vh – 10px * 2)
    10px * 2 だけ引いているのは、上下に余白を取るためです。
    今回はremを使っているので、実際のコードではpxはremに変換しています。

としていたのですが、100vhだとスマホのアドレスバーがある時に、モーダルのコンテンツが一部分画面外にはみ出してしまったので、アドレスバーがあってもなくても見た目が統一されるよう、css変数とjavascriptを使って画面の高さを取得し、それを100vhの代わりに使用しました。

↓100vhで指定した場合、コンテンツの下の部分がアドレスバーの長さ分見切れる

↓jsとcss変数で画面幅を取得した場合、アドレスバーがあっても、アドレスバーがない時と同じようにコンテンツの下の部分が見切れなくなる

jsとcss変数を使った画面幅の取得方法についてはこちらの記事が参考になりました!

正直、今はわざわざjsとcss変数を使わず100dvhを使えばいいのですが、

割と最新のブラウザにしか対応していないことと、

jsを使った方法について知っておきたかったので、

今回はこの方法を使いました。

モーダルの位置調整

モーダルのコンテンツの位置を中央にするために、今回はモーダルに display: flex; モーダルのコンテンツに margin: auto; を指定しています。

display:flex;とmargin:auto;の関係については、

ヘッダーの方で参考記事を紹介しているので見てみてください。

モーダルに display: flex; と align-items: center; justify-content: center; を使うことで、中央に配置することもできるのですが、これらの指定を使うと、コンテンツの大きさよりもデバイスの画面が小さくなってしまった時に、コンテンツの中央を映してしまい、フォームの入力を行うことが不可能になったり、モーダルを閉じるためのボタンを押せなくなったりしてしまいます。

今回は高さの最大値をvhを使って指定しているため、画面外にはみ出ることはないので問題ないですが、

高さを固定値で指定しつつ、画面の高さよりも大きくなってしまうと↓の動画のような状態になってしまいます。

バツボタンが右上の方にあるのですが、そこまでスクロールしてくれずモーダルを閉じることができない

flatpickr

CDN経由ではなく、npmからインストールして使いました。

実際に使ったコード↓

import flatpickr from "flatpickr";

// 言語を指定するのに必要
import { Japanese } from "flatpickr/dist/l10n/ja.js";

// flatpickrの公式ページにはcssの読み込みに関して何も書いてなかったのですが
// ↓を指定しないと、ちゃんとカレンダー(ピッカー)が表示されません。
// ただ、jsでcssを読み込む場合は、webpackのcss-loaderなどが必要になります。
// CDN経由の方は多分不要です。
import "flatpickr/dist/flatpickr.min.css";

// #dateの部分は、クラス名(.class)、要素名(input)でもいいらしいです。
// input[type="datetime-local"]とかでも大丈夫でした。
flatpickr('#date', {
  locale: Japanese,  // 日本語をimportしたJapaneseを入れることで日本語になります。Japaneseではなく"ja"でもいいっぽい
  mode: "range",  // range指定で、範囲指定できるようになります
  enableTime: true,  // 時間の指定をできるようになります
  dateFormat: "Y年m月d日 H:i",  // 選択した日時の表示方法を決める
  minDate: "today",  // todayを指定することで、今日よりも前の日を指定できなくなります
  minTime: "14:00",  // 14:00より前の時間を指定できなくなります
  maxTime: "23:00",  // 23:00より後の時間を指定できなくなります
});

公式ページを見て作ったので、参考になったサイトとかは特にありません

すいません!

フォーム

先にコードだけ置いときます

(() => {
  const $doc = document;
  const $form = $doc.querySelector(".js-reservation-modal__form");
  // 入力項目のうち、必須なものを全て取得
  const $requiredInputList = $form.querySelectorAll(
    ".js-reservation-modal__form-input[required]"
  );
  // flatpickrにあたる要素を取得
  const $flatpickrInput = $form.querySelector(
    '.js-reservation-modal__form-input[name="date"]'
  );
  const $submitBtn = $form.querySelector(
    ".js-reservation-modal__form-submit-btn"
  );
  const eventList = ["blur", "change"];

  $requiredInputList.forEach(($requiredInput) => {
    const $inputField = $requiredInput.parentElement;

    eventList.forEach((event) => {
      $requiredInput.addEventListener(event, (e) => {
     // flatpickrがcheckValidity()無効なので、value===""を追加することで
        // 全ての入力項目において、不備がある場合にエラーを表示させることができます。
        if (
          $requiredInput.value === "" ||
          $requiredInput.checkValidity() === false
        ) {
          $inputField.classList.add("is-error");
        } else {
          $inputField.classList.remove("is-error");
        }
      });
    });
  });

  $form.addEventListener("input", (e) => {
    toggleSubmitBtnDisabled();
  });

  // 日付選択後に日付を変更しようとしたとき、
  // ピッカーを出した後、ピッカー以外をクリックしフォーカスを外すことで
  // 日付が選択されていない状態にもかかわらず
  // 送信ボタンが有効になってしまうのを防ぐための処理
  $flatpickrInput.addEventListener("blur", (e) => {
    toggleSubmitBtnDisabled();
  });

  function toggleSubmitBtnDisabled() {
    // flatpickrにあたるinput要素に、readonly属性がついているため、
    // 日付けの入力をしなくても、checkValidity()がtrueになり、
    // 送信ボタンを押せるようになってしまうため、
    // flatpickrのvalueの値が空でない時も条件に加えることで、
    // 日付の入力をしないと送信できないようにしました。
    if ($form.checkValidity() === true && $flatpickrInput.value !== "") {
      $submitBtn.disabled = false;
    } else {
      $submitBtn.disabled = true;
    }
  }
})();

フォームは中級課題の時と同じような作り方にしたのですが、一つだけ問題がありました。

フォームの作り方については、中級課題の記事↓を見てみてください!

それは、flatpickrのinput要素に readonly属性がついていたことで、

そのせいでflatpickrのinput要素のcheckvalidity()の返り値が常にtrueになってしまい、日付を入力しなくても送信ボタンのdisabledが解除されるようになってしまいました。

↓理想

日付の選択をしていないので、送信ボタンを押せない

↓ダメなやつ

日付の選択を指定いないのに、送信ボタンが押せちゃう

なので、送信ボタンのdisabledの解除に、flatpickrのvalueが空でない時も条件に加え、日付が入力されていなくても送信ボタンが解除されないようにしました。

ただ、それだけではまだ足りず、

日付を一度選択してから、もう一度日付を選択するために、ピッカーを開いてから日付を選択せずに、ピッカー以外をクリックしピッカーを閉じることで、日付を選択していない状態にもかかわらず、送信することが可能になっていたので、それを防ぐための処理も書きました。

送信できてしまう
送信できない

headerの切り替え

ここの説明はflatpickrの説明よりもわかりづらいかもしれません。

すみません。。。

もっとちゃんとした文章書けるよう頑張ります。

スクロール前

ページをスクロールしていない時は、headerに以下の指定をしました。(切り替えに必要な指定のみ書き出しています)

  • position: absolute;
  • top: headerの高さと同じ数値 * -1; // header再出現時に動きをつけるための指定
  • margin-top: topと同じ数値(正の値); // topに負の値を指定したことにより、headerが隠れてしまうのを防ぐための指定

header出現時に動きをつけるためには以下の二つの条件を満たす必要があります。(これ以外の方法もあると思います。)

  • transition: top 0.3s; の指定
  • topに負の値をつけて画面外に隠し、topの値を0にすることで出現させるという指定。

ですが、今回は画面をスクロールしていない状態でもmv(fv)の上にheaderを表示させる必要があり、ただtopに負の値をつけるだけではheaderが隠れて見えなくなってしまいます。

なので、これを解決するためにmargin-topを指定することで画面内に映るようにしました。

スクロール後

画面をスクロールしてheaderを再出現させ、それ以降追従させるための指定は以下のとおりです。

  • position:fixed;
  • top:0;
  • margin-top:0;
  • transition: top 0.3s;

transitionにtopのみを指定することで、headerを滑らかに出現させることができました。

色の切り替え

ロゴ部分に関しては画像を使っていて色の変更ができないので、白色のロゴと黒色のロゴの二つを用意しておき、displayを使い切り替えています。

下層ページのヘッダーについて

切り替えとはあまり関係ないですが一応書いておきます。

下層ページのヘッダーは、Modifierを使い指定しています。

.header--sub {
  position: sticky;
  top: 0;
  margin: 0;
  color: var(--base-color);
  background: #fff;
  box-shadow: 0 0 g.rem(2) rgba(0,0,0,0.5);
}

ヘッダーはfixedでと仕様書に書いてあるのですが、IEが使えなくなった今、fixedを使うよりもstickyの方が便利だと思い、positionの指定をstickyに変えました。

実案件の時は、勝手に変更せず、stickyを使ってもいいか提案させていただき、許可を頂けたら変えるつもりです。

今回はしょーごさんに質問できないので、自己判断でstickyを使うことにしました。

mv(fv)

画像の切り替え

ズームインしながら画像を切り替えるのにcssだけで実装しました。

ゴリさんの記事が参考になりました!

画像について

基本的に画像については以下のようにしています。

  • webp変換と圧縮をし、picture要素を使いwebpかjpg(png)の出し分け
  • retinaディスプレイに対応か非対応かで、二倍サイズの画像か通常サイズの画像どちらかを使う
<picture>
   <source srcset="./img/layout/hamburger_menu_bg.webp 1x, ./img/layout/hamburger_menu_bg@2x.webp 2x" type="image/webp" />
   <img class="hamburger-menu__bg-img" src="./img/layout/hamburger_menu_bg.jpg" srcset="./img/layout/hamburger_menu_bg@2x.jpg 2x" alt="" loading="lazy" />
</picture>

picture要素を使うときは、いちいち全て書くのが面倒なのでpugを使って楽をしています。

のせっちさんの記事が参考になります!

mvの画像で用意されていた画像は、容量・サイズ共に大きかったので、スマホでそのまま使ってしまうと、スマホを利用している方の通信量が増えたり、サイトの画面が表示されるまでの時間が長くなったりすると思ったので、

画像を切り取り、切り取った画像をスマホ用の画像に、元の画像をpc用の画像にし、pictureで出し分けました。

今回は特に画像についての指定がなく、また課題を提供してくださった しょーごさん に質問をすることもできない状態だったので、自己判断で画像を切り取りました。

指定があればその通りにし、指定がなかったとしても、なんの相談もせずに勝手に切り取りをするつもりはありません。

縦書きについては、次のセクションについての記事で書きます。

header mv は以上になります!

続きはこちら!

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