中級課題でやってきたことを、忘れないようにするためにブログに残しておきます。
自分が作った、中級課題のサイトはこちらです
- ユーザー名: mothi001
- パスワード: mothi001
しょーごさんの課題についてはこちらをどうぞ!↓
ご意見・ご質問等ございましたら、お気軽にお問い合わせください。
また、おかしな点などございましたら、教えていただけると幸いです。
コンテンツ一覧
前提条件
- FLOCSS を参考にクラス名をつけています
(FLOCSS が何かわからなくても参考にしていただけると思います。) - pug, scss を gulp を使ってコンパイルしているため途中で pug や scss の記法が
出てくるかもしれませんがご容赦ください
(今回は html と css だけで済みました!) - webpack を使い js を圧縮しています
(今回は特に関係ないかも)
制作するのに、2日ほどかかってしましました。(一日あたり12時間くらいかけて。。。)
目標の20時間に到達できなかったので、もっと早くなれるようがんばります。
全体構造
<div class="l-container">
<header class="l-header">
...
</header><!-- .l-header -->
<main class="l-main">
...
</main><!-- /.l-main-->
<footer class="l-footer">
...
</footer><!-- .l-footer -->
</div><!-- .l-container -->
全体を「l-container」で囲み、max-width paddingを指定し、
必要に応じて、
margin: 0 calc(50% – 50vw);
padding: 0 calc(50vw – 50%);
を使い、画面いっぱいに要素が広がるようにしたり、背景色だけ広げたりしています。
上記に関するくわしいことは、はにわまんさん が書かれたこちらのサイトをどうぞ!
header の実装方法
<header class="l-header">
<h1 class="l-header-logo">
...
</h1>
<nav class="c-gNav" aria-label="メインメニュー">
...
</nav><!-- .c-gNav -->
<a class="c-btn _header" href="#contact">お問い合わせ</a>
<button class="c-hamburger js-hamburger" aria-label="メニューの開閉" aria-expanded="false" aria-controls="hamburgerMenu"><span class="c-hamburger-line"></span></button><!-- /.c-hamburger-->
<nav class="c-hamburgerMenu" id="hamburgerMenu" aria-hidden="true" aria-label="メインメニュー">
...
</nav><!-- .c-hamburgerMenu -->
</header><!-- .l-header -->
FLOCSS 関連
gNav や hamburgerMenu が project (接頭辞が .p-) ではなく
component (接頭辞が .c-) なのは、
どのページでも使いまわす前提のものを component
特定のページだけで使いまわすものを project としているからです。
logo などそれぞれのパーツについては割愛させていただきます。
すいません!
簡単に実装する方法
logo などを囲っている親要素(今回の場合は、.l-header)に以下を指定すればいい感じになります。
(height などの指定は省いています。)
.l-header {
display: flex;
align-items: center;
justify-content: space-between;
}
デザイン通りに実装する方法
ヘッダーを作るにあたり、一番苦労したのは
ロゴ・メインメニュー・ボタンの余白の取り方でした。
どうすればカンプ通りに見た目を整えられるかに少し時間がとられました。
下の画像は、chrome の拡張機能 PerfectPixel をつかい
justify-content: space-between で形を整えたときの
カンプとブラウザでどれだけデザインがずれているかを確認したものです。
PerfectPixel については じゅんぺいさん が書かれたこちらのサイトをどうぞ!
黒い方がじぶんでスタイルを指定したもの、白い方がカンプのデザインになります。
上の画像は justify-content: space-between で実装しています。
ロゴとお問い合わせボタンはあわせられても、メニューが少しずれてしまいます。
なので、space-between は使わず、
メニューとお問い合わせボタンに margin-left を使ってデザイン通りにすることにしました。
まず、お問い合わせボタンに margin-left: auto; をつけて、ボタンを右端に寄せます。
flexItem は、margin: auto; を指定することで上下左右どこでも自動的に伸びれる分だけ伸びてくれます。
詳しくはコリスさんのこちらのサイトをどうぞ!
次にカンプに合うようメニューに margin-left をつけるのですが、ここで問題になってくるのが
固定値で(px とか)margin-left をつけてしまうと、幅を縮めたときに
ボタンの margin-left だけが縮んでしまい、
ロゴ・メニュー・ボタンの余白の間隔がだいぶ変わってしまいます。
これでも問題は特にないのかもしれませんが、できれば
space-between の時のように、余白の間隔をきれいに保ちたかったので
少し時間がかかってしまいました。
margin-left に % を指定してみても、
ボタン横の余白が縮むスピードとメニューの % 指定した余白の縮むスピードが違いすぎて、
結局あまり満足できず。
なので、フォントの拡大縮小にも使えるテクニックを使って、
margin-left の値を指定することにしました。
フォントの拡大縮小に使えるテクニックはこちらのサイトが参考になります!
上記のサイトを参考に
margin-left: clamp(
25px,
25px + (137 - 50) * (100vw - 768px) / (1022 - 768),
137px
);
と指定するとそれなりにいい感じになってくれました。
特徴(FEATURES)
features はこのメディア部分について書いていこうと思います。
こういう形式のデザインを 「メディア」と呼ぶらしいです。
そのほかよく使われるデザインの名称を知りたい方は
株式会社 LIG さんの書かれたこちらの記事をご覧ください!
<div class="p-featureMedia">
<figure class="p-featureMedia-pic">
<picture>
<source srcset="./img/top/feature_pic00.webp 1x, ./img/top/feature_pic00@2x.webp 2x" type="image/webp" />
<img src="./img/top/feature_pic00.jpg" alt="特徴1" srcset="./img/top/feature_pic00.jpg 1x, ./img/top/feature_pic00@2x.jpg 2x" loading="lazy" />
</picture>
</figure>
<div class="p-featureMedia-body">
<p class="p-featureMedia-ttl"></p>
<p class="p-featureMedia-txt"></p>
</div>
</div><!-- .p-featureMedia -->
簡単な実装方法
.p-featureMedia {
display: flex;
align-items: center;
justify-content: space-between;
}
featureMedia-body もしくは featureMedia-pic に
margin-right か margin-left をつけて画像とテキストがくっつかないようにしておく
デザイン通りに実装する方法
features は一番時間をかけてしまったかもしれません。
理由は、今まで練習中はずっと、画像を padding hack を使って大きさの調整をしていたからです。
そのせいで、どのメディアの画像も同じ大きさになってしまい、全然カンプ通りにできませんでした。
padding hack についてはこちらのサイトをどうぞ!
今では、 aspect-ratio という便利なプロパティがあるので、padding hack でなくていいかもです。
can i use をみて、対象となるブラウザが aspect-ratio を使えない時などに使うくらいでよさそうです。
画像について
それぞれの画像のサイズをカンプ通りにするには、
<img> に max-width: 100%; を指定するだけでよかったのですが、
それに気づくのに時間がかかってしまいました。
.p-featureMedia-pic {
position: relative;
width: 40%;
overflow: hidden;
}
.p-featureMedia-pic::before {
content: "";
display: block;
padding-top: calc(300 / 400 * 100%);
}
.p-featureMedia-pic img {
position: absolute;
top: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
↑これではどのメディアも同じサイズに
.p-featureMedia-pic img {
max-width: 100%;
}
↑これだけ指定すればよかった
ただし、max-width: 100%; だけでは、横幅がでかい画像などの場合
テキストと画像がくっついてしまうので、featureMedia-body か featureMedia-pic に margin をつけてくっつかないようにしておきましょう。
これで、画像についてはカンプ通りにすることができました。
テキスト(ボディ)について
今回は、.l-container で max-width を指定してしまっているため、
このままでは featureMedia の長さが足りなくなりテキストをカンプの位置まで
持っていくことができません。
これを解決するため、ネガティブマージンを使いました。
ネガティブマージンについてはこちらをご覧ください。
.p-featureMedia {
margin-left: -92px;
}
いい感じになりました。
ですが、このままでは画面幅を縮めたときにテキストがはみ出してしまいます。
なので、これも先ほど使ったフォントの拡大縮小にも使えるテクニックではみ出ないよう調整します。
フォントの拡大縮小にも使えるテクニックは使わず、
はみ出したらメディアクエリ使って調整しても大丈夫だと思います。
margin-right: clamp(
-92px,
-92 * (100vw - 1150px) / (1200 - 1150),
0px
);
はみ出なくなりました。
価格(PRICE)
table の仕様について知っていることだけ書いて終わります。
tableの仕様について その1
table 専用の子要素(th や td など)には、min-height、max-height が効きません。
table には効きます!
代わりに、height を指定すると、
div などの普通の要素における、min-height の役割を果たします。
なので、th td 内のテキスト量がたくさんあって、
指定した height の高さよりもコンテンツ(テキストなど)の高さの方が大きくなってしまっても文字がはみ出ることはありません。
div などの普通の要素に height だけ指定すると、テキストは溢れてしまいますが
table の子要素はそうならないようにできているみたいです。
後は、テキストがいっぱいになった時用に
padding を上下左右すべてにつけておけばいいと思います。
tableの仕様について その2
スマホ用に縦積みにする場合は、
html 内に tbody を使っていなくても tbody に display: block; をつけてください。
html 内に tbody がなくても、勝手にブラウザがつけるので。
table,
tbody,
tr,
th,
td {
display: block;
}
ブログ(BLOG)
FAQ は特に書くことがなかったので、割愛させていただきます。
カードの日付に関して、2種類くらいやり方が思いつくのですが、
今回は flex で使えるテクニックを使って日付の位置を揃えました。
詳しくはこちらの記事をどうぞ。
もう一つのやり方は、日付を position: absolute; で浮かせて
日付を置く部分に、padding を指定して文字と被らないようにする方法です。
このやり方だと、日付のフォントサイズを変えた場合に、
タイトル部分と日付がかぶってしまう可能性があるので、注意が必要です。
先に紹介した、flex を使ったテクニックなら、日付がかぶることはありません。
お問い合わせ(CONTACT)
制作実績(WORKS)に関しても割愛させていただきます。
swiper は調べたらたくさん使い方の記事が出てくると思うので。
参考になった書籍
フォームに関しては
「HTML+CSSコーディングの強化書」
が参考になりました。
しょーごさんが紹介していらっしゃる記事もあるので、そちらも見てみてください。
ただ、HTML+CSSコーディングの強化書は、javascript については触れていないので、
javascript に関して書いておこうと思います。
form 用に作った html と javascript (jquery付き)
javascript(vanilla) で作っていたんですが、jquery を使っている方もいらっしゃると思ったので、
jquery 用のものも作ってみました。
詳しい解説は javascript の方に書いてあるので、
そちらを見てから jquery の方をご確認ください。
html ↓
<form class="p-contactForm js-contactForm" id="form" name="contact" action="#" method="POST" data-aos="fade-up">
<div class="p-contactForm-list">
<div class="p-contactForm-item"><label for="name">お名前<span>必須</span></label>
<div class="p-contactForm-inputField" data-field-name="name"><input type="text" name="name" id="name" required>
<p class="p-contactForm-errorTxt">お名前を入力してください</p>
</div>
</div>
<div class="p-contactForm-item"><label for="email">メールアドレス<span>必須</span></label>
<div class="p-contactForm-inputField" data-field-name="email"><input type="email" name="email" id="email" required>
<p class="p-contactForm-errorTxt">メールアドレスを入力してください</p>
</div>
</div>
<div class="p-contactForm-item"><label for="message">お問い合わせ内容<span>必須</span></label>
<div class="p-contactForm-inputField" data-field-name="message"><textarea name="message" id="message" cols="30" rows="10" required></textarea>
<p class="p-contactForm-errorTxt">お問い合わせ内容を入力してください</p>
</div>
</div>
</div>
<div class="p-contactForm-privacy"><label><input type="checkbox" name="privacy" value="同意する" required><span>プライバシーポリシーに同意する</span></label></div>
<div class="p-contactForm-btns"><button class="c-btn _submit" type="submit" disabled>送信する</button></div>
</form><!-- .p-contactForm -->
js ↓
(() => {
const $doc = document;
const $form = $doc.querySelector('.js-contactForm');
// form 内にある必須項目を取得
const $requiredElements = $form.querySelectorAll('[required]');
// 送信ボタンの取得
const $submitBtn = $form.querySelector('[type="submit"]');
// 各入力項目に適用する event を指定
// blur はフォーカスが外れたときにイベント発生
// change は入力項目になにかしら入力した後にフォーカスを外すとイベント発生
// ただし、radio checkbox に関しては、チェックを入れても外してもイベント発生
const elementsEventList = ["blur", "change"];
// 個別チェック
elementsEventList.forEach((elementsEvent) => {
$requiredElements.forEach(($requiredElement) => {
$requiredElement.addEventListener(elementsEvent, (e) => {
// プライバシーポリシーに同意するボタンには、
// エラーテキストを付けていないので除外
if (e.target.name != "privacy") {
// event を起こした入力項目の [name] を取得し
const targetFieldName = e.target.name;
// 入力項目の親要素にあたる
// .p-contactForm-inputField に指定しておいた、
// [data-field-name] と一致する .p-contactForm-inputField を取得
const $targetField = $form.querySelector(
`[data-field-name="${targetFieldName}"]`
);
// checkValidity() は form の入力項目や form 自身などにつかえるメソッドで
// ブラウザにもともと備わっている検証機能をもとに
// 書かれた内容が、正しいものであれば true
// 不適切であれば false を返します
if (e.target.checkValidity() === true) {
// 正しい場合は、エラーテキストを非表示に
$targetField.classList.remove("is-error");
} else {
// 不適切であれば、エラーテキストを表示します
$targetField.classList.add("is-error");
}
}
});
});
});
// input と change は似ていますが、
// こちらは change と違い一文字入力するたびにイベント発生
// form そのものに、input イベント(もしくは change イベント)を指定すると、
// すべての入力項目において、入力が行われた場合にイベントが発生することになります
// (change イベントの場合は、入力してフォーカスが外れたとき)
// 例えば <form> の中に <input type="text"> <input type="email"> があったとき
// text に入力してもイベント発生
// email に入力してもイベント発生
$form.addEventListener("input", (e) => {
// form に checkValidity() をつかうことで、
// 全ての必須項目が正しく入力されている場合にのみ true を返します
// これで、submit ボタンの disabled を無効化します。
$form.checkValidity() === true
? ($submitBtn.disabled = false)
: ($submitBtn.disabled = true);
});
})();
jquery↓
$(function () {
const $form = $(".js-contactForm");
const $submitBtn = $form.find('[type="submit"]');
$form.find("[required]").on("blur change", (e) => {
if (e.target.name != "privacy") {
const targetFieldName = e.target.name;
const $targetField = $(`[data-field-name="${targetFieldName}"]`);
if (e.target.checkValidity() === true) {
$targetField.removeClass("is-error");
} else {
$targetField.addClass("is-error");
}
}
});
$form.on("input", (e) => {
$form[0].checkValidity() === true
? ($submitBtn.prop("disabled", false))
: ($submitBtn.prop("disabled", true));
})
});
実際にどう動くのか、こちらでご覧ください。
- ユーザー名: mothi001
- パスワード: mothi001
変数名の付け方や、
間違っている箇所など、
おかしな点がございましたら教えていただけると幸いです。
個別チェックにて、input ではなく、change を使ったのは、
今回のフォームでは、かな文字入力がありませんでしたが、
名前などのかな文字入力が必要な際に、input だといちいちガタガタなるからです。
(input に ひらがなだけ受け付けさせるには 「pattern=”^[ぁ-んー]*$”」 をつける必要があります)
<input type="text" name="kana" id="kana" pattern="^[ぁ-んー]*$">
↑のようになるのは、
pattern によってひらがなのみを受け付けるようになった関係で、
例えば「さ」と入力するときの 「s」 の時点ではまだローマ字なので、その時点でエラーテキストが表示され、
「sa」と入力すると「さ」になるので、エラーテキストがきえる。
この繰り返しによってガタガタなってしまうようです。
textarea と radio checkbox の css についてちょっとした紹介
あと、css についても少しだけ。
textarea には resize というプロパティが使え、このプロパティの値を none にすることで、
textarea を伸び縮みさせることができなくなります。
resize にはほかにも値があります↓
textarea {
resize: vertical; // 垂直方向にのみ伸ばすことができる
resize: horizontal; // 水平方向にのみ伸ばすことができる
resize: both; // どちらにも伸ばせる(デフォルトの値)
resize: none;
}
また、input の radio checkbox は、::before ::after が使えるので
見た目を変えるときは、span などを別に用意する必要なく、
::before ::after だけで見た目を整えることができます。
ただ、どこの記事を見ても span を使っているので
使ったりするのはダメなのでしょうか?
ご存じの方おられましたら、ぜひ教えてください!
↓に実際に作ったものを置いておきます。
See the Pen Untitled by mothihuku (@mothi002) on CodePen.
aos.js について
aos.js で起こったバグについて その1
自分は CDN ではなく npm でインストールしたのですが、
そのせいなのか、若干挙動がおかしくなることがありました。
特にメインビジュアルがふわっと出てきてくれず、それの対応にもかなり時間がかかってしまいました。
他のユアコーディング制作者さんのページをみてみると
何の問題もなくメインビジュアルがふわっとでてきていたので、
npm でインストールするとおかしな挙動になるのかなとおもいます。
その方は、CDN を使っていました。
一応メインビジュアルに opacity: 0; を指定することで
ちゃんとふわっと出てきてくれるようになりました。
aos.js で起こったバグについて その2
他にも aos.js には注意しなければならないことがあり、
それは、 fade-left (slide-left もかも) を指定したときに横スクロールが発生してしまう可能性があることです。
(これは、npm CDN など関係なく起こる可能性があります。)
詳しくはこちらのサイトの
「AOS.jsで起こる可能性があるトラブル」
をご覧ください!
fade-left を指定した場合に、PC では問題なくても、
スマホでは必ず横スクロールが発生していたので、これの解決にも少し時間がかかりました。。。
解決策は上記のサイトでも書いてある通り、
fade-left を指定した要素の親要素に、overflow: hidden; を指定することで
問題なく動くようになります。
最後に
今回初めてこのような記事を書かせていただきました。
なので、読みずらい部分、わかりずらい部分、間違っている部分など
たくさんあるかもしれませんが、
そのときはお問い合わせにて教えていただけると幸いです。
修正する必要ありと感じたら、
できる限り早く修正させていただきます。
また、この記事についての感想などいただけるとうれしいです!
この記事書くのに1日まるまる使ってしまいました。。。
記事も早くかけるようになりたいです。
今回はここまでになります。
最後まで見てくださりありがとうございました!
この記事がお役に立てばうれしいです!
上記の本には、
などなど、参考になることがたくさん書いてあり大変勉強になりました。