こんにちは、もえぎです!
現在、独学でWeb制作を勉強中の私が取り組んでいる、しょーごログさんのコーディング練習課題について、中級Ex編1つ目のWebサイトが完成したので、備忘録として今回投稿します。
中級Ex編は2つありどちらも取り組むことができます!ただし、無料で添削を受けられるのはどちらか1つです。
私は2つ目を添削に出す予定なので、1つ目はセルフチェックまで実施しました。
・現役エンジニアによる「2回のレビュー特典」付き
・全課題「オリジナルポートフォリオ化」して、ポートフォリオとして利用できる
・「コーディング→セルフチェック→アップロード→再セルフチェック→レビュー依頼→修正→完成」という実案件の流れを経験できる
私が取り組んでいる「全部盛りセット」は単品購入よりも25%お得になります!
\ 詳細はこちら! /
「しょーごログのコーディング練習課題とは何か?」「私がしょーごログのコーディング練習課題に取り組み始めた理由と感想」は、以下の記事で紹介しているので、気になる方はご覧ください!
中級Ex編「石井花壇」の概要
私が今回の練習課題で作成したWebサイトはこちらです!
ユーザー名:demo
パスワード:demo
所要時間
セルフチェック完了までの合計所要時間は、23時間30分でした!
前提条件
コーディンング練習課題の中級Ex編に取り組むための前提条件は、「コーディング練習課題、中級編を問題なくクリアできるほどの実力があること」です。
コーディング練習課題、中級編については、以下の3つの記事で解説しています。
中級編までのコーディング練習課題はトップページのみでしたが、中級Ex編からは下層ページが追加され、複数ページのコーディングを練習します!
ページを跨いで共通して使える処理をまとめたり、部分的に違う処理だけを分けたり、複数ページのコーディングは1ページのみの場合と比べて、考えることがとても増える印象でした。
逆に言えば、設計をしっかり作ることができれば、効率的かつミスの少ないコーディングができます!
学べること
- 複数ページのコーディング
- スクロールでヘッダーを変化
- ファーストビューのアニメーション
- モーダルの実装
- flatpickerの実装
- タブの実装
- 縦書きの実装
- AOSで要素をふわっと表示させる
- パンくずリスト
中級Ex編「石井花壇」の学習記録
コーディングをしていて難しいと感じたところについて、記録していきます!
コードは各項目に該当する部分のみ抜粋しています。
スクロールでヘッダーを変える
トップページのみ、スクロールの前後でヘッダーナビが変わる仕様になっています。
↓スクロール前
・PC
・スマホ
↓スクロール後
・PC
・スマホ
<header class="header header--top">
<div class="header__inner inner">
<!-- PC幅用 -->
<nav class="header-nav">
<h1><a href="#"><img src="img/top-header-logo.svg" alt="" class="site-ttl__header site-ttl__header--top"></a></h1>
<ul class="header-nav__list">
<li class="header-nav__item header-nav__item--top"><a href="room.html">お部屋</a></li><!-- /.header-nav__item -->
<li class="header-nav__item header-nav__item--top"><a href="menu.html">お料理</a></li><!-- /.header-nav__item -->
<li class="header-nav__item header-nav__item--top"><a href="spa.html">温泉</a></li><!-- /.header-nav__item -->
</ul><!-- /.header-nav__list -->
<div class="reservation modal__open">
<p href="" class="reservation__txt"><img src="img/calender.svg" alt="" class="calender-logo">宿泊予約</p>
</div><!-- /#open -->
</nav><!-- /.header-nav -->
<!-- スマホ幅用 -->
<button type="button" id="js-hamburger-menu" class="hamburger"
aria-controls="js-glabal-menu" aria-expanded="false" aria-label="メニューを開閉する">
<span class="hamburger__line hamburger__line--top"></span>
<span class="hamburger__line hamburger__line--top"></span>
<span class="hamburger__line hamburger__line--top"></span>
</button>
<div class="sp-global-menu" id="js-global-menu" aria-hidden="true">
<nav class="sp-global-menu__wrapper">
<h1><a href="#"><img src="img/top-header-logo.svg" alt="" class="sp-global-menu__site-ttl"></a></h1><!-- /.sp-global-menu__site-ttl -->
<ul class="sp-global-menu__list">
<li class="sp-global-menu__item header-nav__item"><a href="room.html">お部屋</a></li><!-- /.header-nav__item -->
<li class="sp-global-menu__item header-nav__item"><a href="menu.html">お料理</a></li><!-- /.header-nav__item -->
<li class="sp-global-menu__item header-nav__item"><a href="spa.html">温泉</a></li><!-- /.header-nav__item -->
</ul><!-- /.sp-global-menu__list -->
<div class="sp-global-menu__reservation-wrapper">
<div class="sp-global-menu__reservation modal__open">
<p href="" class="reservation__txt"><img src="img/calender.svg" alt="" class="calender-logo">宿泊予約</p>
</div><!-- /#open -->
</div><!-- /.sp-global-menu__reservation-wrapper -->
</nav><!-- /.sp-global-menu -->
</div><!-- /.sp-global-menu__wrapper -->
</div><!-- /.header__inner -->
</header><!-- /.header -->
.header{
&--top{
&--sticky {
position: fixed;
top: 0;
left: 0;
right: 0;
opacity: 1;
visibility: visible;
animation: slide-down 0.3s ease-in-out;
background-color: $color-white;
color: $color-txt-main;
}
@keyframes slide-down {
0% {
transform: translateY(-100%);
}
100% {
transform: translateY(0%);
}
}
}
}
$(function(){
window.addEventListener('scroll', function() {
const header = document.querySelector('.header--top');
const headerHeight = header.offsetHeight; // ヘッダーの高さを取得
const scrollY = window.pageYOffset;
if (scrollY >= 100) {
header.classList.add('header--top--sticky');
document.body.style.marginTop = headerHeight + 'px'; // コンテンツにヘッダーの高さ分の余白を設定
const siteTtlHeader = document.querySelector('.site-ttl__header--top'); // スクロール位置が一定値以上の場合、画像を差し替える
siteTtlHeader.src = 'img/sub-header-logo.svg'; // 新しい画像のパスを指定
const headerNavItem = document.querySelectorAll('.header-nav__item--top a');
headerNavItem.forEach(item => {
item.style.color = '#000000' // 変更後の文字色を指定
});
const hamburgerLine = document.querySelectorAll('.hamburger__line--top');
hamburgerLine.forEach(item => {
item.style.backgroundColor = '#000000' // ハンバーガーメニューの変更後の色を指定
});
} else {
header.classList.remove('header--top--sticky');
document.body.style.marginTop = '0'; // コンテンツの余白をリセット
const siteTtlHeader = document.querySelector('.site-ttl__header--top'); // スクロール位置が一定値以下の場合、元の画像に戻す
siteTtlHeader.src = 'img/top-header-logo.svg'; // 元の画像のパスを指定
const navItems = document.querySelectorAll('.header-nav__item--top a');
navItems.forEach(item => {
item.style.color = ''; // 空文字列を指定することで元の文字色に戻す
});
const hamburgerLine = document.querySelectorAll('.hamburger__line--top');
hamburgerLine.forEach(item => {
item.style.backgroundColor = ''; // 空文字列を指定することでハンバーガーメニューを元の色に戻す
});
}
});
});
トップページと下層ページで共通して使える仕様とそうでない仕様を、「–top」の有無で区別しています。
参考サイト
しょーごログさんの参考記事を元に実装しました!
レンタルサーバーにアップロード後の表示確認をしていたとき、なぜか、ヘッダースクロール時のサイトロゴが切り替わらない現象が発生しました。
時間をかけて調べたのですが、結果かなり初歩的な間違いであったことがわかりました。
ここに載せるか迷いましたが、また同じ間違いをしないように記録します!
結論から言うと、「画像のパスがHTMLファイルから見たものではなく、JavaScriptファイルから見たものになっていたから」です。
HTMLファイルのsrcを書き換える処理なので当たり前なのですが、書き間違いをしないように、VSCodeの変換予測を使ってパスを記述する癖がついていて、引っかかりました。
・修正前
siteTtlHeader.src = ../’img/sub-header-logo.svg’; // 新しい画像のパスを指定
siteTtlHeader.src = ‘../img/top-header-logo.svg’; // 元の画像のパスを指定
・修正後
siteTtlHeader.src = ‘img/sub-header-logo.svg’; // 新しい画像のパスを指定
siteTtlHeader.src = ‘img/top-header-logo.svg’; // 元の画像のパスを指定
ローカルでは、これでも想定通りに動いていたので、アップロード後の表示確認は大切だと改めて感じました!
ファーストビューのアニメーション
3枚の画像を順番に、かつ、ふわっと表示させます。
<section class="top-fv">
<div class="top-fv__slide">
<div class="top-fv__slide-image"></div>
<div class="top-fv__slide-image"></div>
<div class="top-fv__slide-image"></div>
</div>
.top-fv{
&__slide {
position: relative;
width: 100%;
height: 100vh;
overflow: hidden;
}
&__slide-image {
background-size: cover;
background-repeat: no-repeat;
background-position: center;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
animation: top-fv__slider 24s linear infinite;
&:nth-child(1) {
background-image: url(../img/mainbg01.jpg);
animation-delay: -2s;
}
&:nth-child(2) {
background-image: url(../img/mainbg02.jpg);
animation-delay: 6s;
}
&:nth-child(3) {
background-image: url(../img/mainbg03.jpg);
animation-delay: 14s;
}
}
@keyframes top-fv__slider {
0% {
opacity: 0;
transform: scale(1);
}
4.16% {
opacity: 1;
}
33.33% {
opacity: 1;
}
41.66% {
opacity: 0;
transform: scale(1.2);
}
100% {
opacity: 0;
}
}
参考サイト
こちらも、しょーごログさんの参考記事を元に実装しました!
モーダルの実装
予約画面をモーダルで実装します。
<section class="modal__area">
<div class="modal__bg"></div><!-- /.modal__bg -->
<div class="modal__wrapper">
<div class="modal__contents">
<h2 class="modal__ttl">宿泊予約</h2><!-- /.modal__ttl -->
<form action="" id="form">
・・・中略・・・
</form><!-- /#form -->
</div><!-- /.modal__contents -->
<div class="modal__close">
<a href=""><img src="img/close.svg" alt=""></a>
</div><!-- /.modal__close -->
</div><!-- /.modal__wrapper -->
</section><!-- /.modal__area -->
.modal{
&__area {
visibility: hidden; /* displayではなくvisibility */
opacity : 0;
position: fixed;
z-index: 2000;
top: 0;
left: 0;
width: 100%;
height: 100%;
transition: .4s;
}
&__bg {
width: 100%;
height: 100%;
background-color: $color-white;
opacity: .5;
}
&__wrapper {
position: absolute;
top: 50%;
left: 50%;
transform:translate(-50%,-50%);
width: 550px;
height: 95vh;
max-width: 550px;
background-color: $color-white;
border: 1px solid $color-border;
padding: 27px 60px 90px;
}
&__close {
position: absolute;
top: 20px;
right: 20px;
}
&__ttl{
font-weight: $weight-500;
font-size: $size-20;
padding-bottom: 32px;
margin-bottom: 33px;
border-bottom: 1px solid $color-border-02;
text-align: center;
}
}
.is-show { /* モーダル表示用クラス */
visibility: visible;
opacity : 1;
}
$(function(){
$(".modal__open").click(function(){
$(".modal__area").toggleClass("is-show");
});
$(".modal__close").click(function(){
$(".modal__area").toggleClass("is-show");
});
$(".modal__bg").click(function(){
$(".modal__area").toggleClass("is-show");
});
});
参考サイト
こちらも、しょーごログさんの参考記事を元に実装しています!
プラン選択のデフォルトの文字を灰色にする
selectタグにはplaceholderのオプションがないため、プラン選択前にデフォルトで表示される文言を設定したい場合、optionタグを先頭に追加することになります。
その場合、文字の色が、bodyタグに設定されているものになるため、inputタグのplaceholderの色に合わせようという試みです。
<div class="form__item">
<p class="form__ttl"><label for="plan">ご希望プラン(空いているプランのみ表示されます)</label></p><!-- /.form__ttl -->
<select id="plan" class="desired-plan" name="entry.1176010670" required>
<option class="desired-plan__item" value=""><span class="placeholder">プランを選択してください</span><!-- /.placeholder --></option><!-- /.plan__item -->
<option class="desired-plan__item" value="plan-01">【期間限定】海辺の四季旬彩、贅沢美味懐石プラン</option><!-- /.plan__item -->
<option class="desired-plan__item" value="plan-02">平日に優雅に楽しむ、特別宿泊プラン</option><!-- /.plan__item -->
<option class="desired-plan__item" value="plan-03">絶景貸切露天と個室会席を満喫できるファミリープラン</option><!-- /.plan__item -->
</select>
</div><!-- /.form__item -->
.desired-plan{
border: 1px solid $color-border-02;
padding: 10px;
width: 100%;
&:invalid {
color: $color-placeholder;
}
}
文字の色を灰色にする条件は次のとおりです。
- 灰色にしたい項目のvalueが空であること
- selectタグにrequiredを付けること(valueが空の項目が選択されている場合、invalid(= 無効)になる)
- select:invalidで灰色を設定すること
参考サイト
flatpickerを使って日付選択を実装する
まず、HTMLに以下を記述し、flatpickerのCDNを読み込みます。
・headタグ内
<link rel=”stylesheet” href=”https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css”>
・bodyタグ終了直前
<script src=”https://cdn.jsdelivr.net/npm/flatpickr”></script>
<script src=”https://cdn.jsdelivr.net/npm/flatpickr/dist/l10n/ja.js”></script> ←日本語表示にするためのCDN
最新版を確認したい方は、公式サイトを参照ください。
今回のコーディング練習課題では、「現在より前の日付を選択できないようにする」「連泊の設定ができるようにする」の2つを仕様として提示されました。
<div class="form__item">
<p class="form__ttl"><label for="flatpicker">日付選択</label></p><!-- /.form__ttl -->
<input type="text" id="flatpicker" class="flatpickr" name="entry.1374459539 placeholder="日付を選択してください" required>
</div><!-- /.form__item -->
$(function(){
flatpickr(".flatpickr", {
locale: 'ja',
minDate: "today",
dateFormat: "Y-m-d",
mode: "multiple",
mode: "range",
disableMobile: true
});
});
「minDate: “today”」で、今日より前の日付を選択できないように、「mode: “multiple”」で連泊の選択ができるようにしています。
flatpickerには他にもたくさんのオプションがあります!
参考サイト
要素をスクロールでふわっと表示させる
AOS.jsを用いて実装しました。
まず、HTMLに以下を記述し、AOSのCDNを読み込みます。
・headタグ内
<link href=”https://unpkg.com/aos@2.3.1/dist/aos.css” rel=”stylesheet”>
・bodyタグ終了直前
<script src=”https://unpkg.com/aos@2.3.1/dist/aos.js”></script>
最新版を確認したい方は、公式サイトを参照ください。
<div class="top-fv__item-wrapper">
<div class="top-fv__item" data-aos="fade-up" data-aos-duration="3000">
<h2 class="top-fv__ttl">頑張る人の</h2><!-- /.top-fv__ttl -->
</div><!-- /.top-fv__item -->
<div class="top-fv__item" data-aos="fade-up" data-aos-duration="3000" data-aos-delay="1000">
<h2 class="top-fv__ttl">頑張らない時間</h2><!-- /.top-fv__ttl -->
</div><!-- /.top-fv__item -->
</div><!-- /.top-fv__item-wrapper -->
$(function(){
AOS.init();
});
参考サイト
縦書きにした時に、数字が横向きにならないようにする
縦書きにしたところ、数字が横向きになってしまったので、数字は縦向きのままになるようにします。
<div class="message__desc-box">
<p class="message__desc">
東北の奥座敷である温海温泉郷<br>
開湯は約1300年前とされ、役小角が<br>
発見したと伝えられます
</p><!-- /.message__desc -->
</div><!-- /.message__desc-box -->
.message{
&__desc-box{
margin-left: 20%;
display: flex;
flex-direction: row-reverse;
&__desc{
writing-mode: vertical-rl;
text-orientation: upright;
letter-spacing: .3rem;
line-height: 2.5;
}
}
「text-orientation: upright;」をつけることで解決しました!
ちなみに、和風系のサイトでよく見かける、縦書き時のポイントをまとめておきます。
・横書きにするには
writing-mode: vertical-rl;
・数字や英語(日本語以外)は縦書きにするには
text-orientation: upright;
・複数行で文字が右から左に並ぶようにするには
親要素に以下を記述
display: flex;
flex-direction: row-reverse;
・文字の間隔をあけるには
letter-spacing: ◯rem;
※line-heightではない!
・複数行で行間をあけるには
line-height: ◯;
参考サイト
文字は左揃えのまま、要素を右側に配置する
テキストボックスをabout__item内の右端に配置したいのですが、画像はabout__itemをはみ出して、画面左端にpositon:absoluteで絶対配置していたため、苦戦しました。
<div class="about__item">
<figure class="about__img-wrapper about__img-wrapper--left" data-aos="fade-up" data-aos-duration="3000">
<img src="img/oheya-top.jpg" alt="" class="about__img">
</figure><!-- /.about__img-wrapper -->
<div class="about__txt about__txt--right" data-aos="fade-up" data-aos-duration="3000">
<div class="about__ttl-wrapper">
<h2 class="about__ttl">喧騒から離れた空間<br>心落ち着く至極のひととき</h2><!-- /.about__ttl -->
</div><!-- /.about__ttl-wrapper -->
<div class="about__desc-wrapper">
<p class="about__desc about__desc">
まるで時が止まったかのような、圧倒的な静寂のなかで、<br class="pc-br">ひたすらにゆったりと…。<br>最高級の「何もしない時間」をお過ごしください。
</p><!-- /.about__desc -->
</div><!-- /.about__desc-wrapper -->
<a href="room.html" class="btn">お部屋について</a><!-- /.btn -->
</div><!-- /.about__txt -->
</div><!-- /.about_item -->
.about{
}
&__txt{
&--right{
width: 470px;
margin-left: auto;
}
}
}
widthの設定と、margin-left:autoで解決しました。
参考サイト
画像の最大幅・高さと縮小率を設定する
画像の最大幅・高さ、レスポンス時の縮小率を設定します。
.about{
&__img-wrapper{
max-width: 1050px;
width: 55%;
object-fit: cover;
&__img{
width: 100%;
max-height: 545px;
}
}
親要素の.about __img-wrapperクラスで、最大幅と縮小率を設定します。
最大高さは子要素の.abou__imgで設定しました。
お知らせタブの実装
タブのクリックで表示が切り替わるようにします。
<ul class="tab-menu">
<li class="tab-menu__item" data-aos="fade-up" data-aos-duration="3000"><span class="tab-trigger js-tab-trigger is-active" data-id="tab01">営業情報</span></li>
<li class="tab-menu__item" data-aos="fade-up" data-aos-duration="3000"><span class="tab-trigger js-tab-trigger" data-id="tab02">その他</span></li>
</ul><!-- .tab-menu -->
<div class="tab-content">
<div class="tab-content__item js-tab-target is-active tab01" data-aos="fade-up" data-aos-duration="3000">
</div><!-- .tab-content__item -->
<div class="tab-content__item js-tab-target tab02" data-aos="fade-up" data-aos-duration="3000">
</div><!-- .tab-content__item -->
・・・中略・・・
</div><!-- .tab-content -->
.tab-content{
display: flex;
justify-content: space-between;
flex-wrap: wrap;
&__item{
display: none;
&.is-active{
display: flex;
align-items: center;
animation: fade 0.5s ease;
width: 45%;
margin-right: 46px;
margin-bottom: 20px;
padding: 15px 10px;
background-color: $color-bg;
&:nth-of-type(2n){
margin-right: 0;
}
}
}
}
@keyframes fade {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.tab-trigger {
cursor: pointer;
&.is-active{
border-bottom: 1px solid $color-border;
}
}
$(function(){
$(".js-tab-trigger").click(function(){
//まずは全triggerからclass削除
$('.js-tab-trigger').removeClass('is-active');
//次に全targetからclass削除
$('.js-tab-target').removeClass('is-active');
//次にクリックした要素にis-active
$(this).addClass('is-active');
//data属性を取得する
let id = $(this).data("id");
//data属性値=idが等しいものにclass付与
$('.' + id).addClass('is-active')
});
});
コーディング練習課題全部盛りに含まれている、「JavaScript,jQuery特訓編」で実装したタブの仕様をほとんどそのまま使えました!
パンくずリストの実装
下層ページでよく見る、自分が今どのページにいるのかをわかりやすくするためのパンくずリストを実装します。
<nav class="pankuzu__wrapper">
<ol class="pankuzu__list" itemscope itemtype="https://schema.org/BreadcrumbList">
<li class="pankuzu__item" itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
<a itemprop="item" href="http://127.0.0.1:5500/index.html"><span itemprop="name">トップ</span></a>
<meta itemprop="position" content="1" />
</li><!-- /.pankuzu__item -->
<li class="pankuzu__item" itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
<a class="current" itemprop="item" href="http://127.0.0.1:5500/room.html"><span itemprop="name">お部屋</span></a>
<meta itemprop="position" content="2" />
</li><!-- /.pankuzu__item -->
</ol><!-- /.pankuzu__list -->
</nav><!-- /.pankuzu__wrapper -->
.pankuzu{
&__list{
list-style-type: none;
padding-top: 30px;
display: flex;
align-items: center;
}
&__item{
&:last-of-type::before{
content: '>';
padding: 0 5px;
}
&>a{
color: $color-txt-main; /* 他のリンクの色 */
&.current{
color: $color-txt-main /* 現在のページのリンク色 */
}
&:hover{
text-decoration: underline;
}
}
}
}
参考サイト
こちらも、しょーごログさんの参考記事を元に実装しています!
まとめ
中級Ex編は、JavaScript(jQuery)を用いて動きを出したり、各種CDNを活用したり、初見の仕様が多くありました。
しょーごログさんは、コーディング練習課題で実装する仕様の参考記事もたくさん出してくれているので、ありがたかったです!
初見の仕様があるとドキドキするので、参考記事があるだけでだいぶホッとします笑
全体的に、中級編と比べてかなり難しくなりましたが、とてもためになる内容ばかりだったので、実際の案件前に取り組むことができて良かったと感じました。
私は、しょーごログさんの「コーディング練習課題全部盛りセット」を購入して学習を進めています。
こちらのセットは、単品購入よりも25%お得になります。
現役エンジニアの添削を受けることができ、オリジナルポートフォリオ化したものは自分のポートフォリオとしても利用できますので、気になる方は見てみてください!
・現役エンジニアによる「2回のレビュー特典」付き
・全課題「オリジナルポートフォリオ化」して、ポートフォリオとして利用できる
・「コーディング→セルフチェック→アップロード→再セルフチェック→レビュー依頼→修正→完成」という実案件の流れを経験できる
私が取り組んでいる「全部盛りセット」は単品購入よりも25%お得になります!
\ 詳細はこちら! /