# アプリバーを作成する
アプリバーを作成します。 このページでは、JavaScript による制御の説明が含まれます。
今回は、画面上部に粘着するバーを設定します。

ボタンをクリックすると、該当のカードに移動するようにします。

# 内容
記入する場所に注意して、追記してください。
アプリバーは、自己紹介と趣味のカードの間に配置します。
また、移動先の位置を指示するために、各カードの要素に id を指定します。
さらに、スライドショーの際に作成した <script /> タグの中に、ボタンがクリックされたときの処理を追記します。
以下の HTML は これまでにコーディングしたコードの全文のうち一部を省略して表記しています。
~~/public/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="robots" content="noindex" />
<title>Yumi's Portfolio</title>
<link rel="stylesheet" href="./assets/main.css" />
<link
rel="stylesheet"
href="https://unpkg.com/swiper@8/swiper-bundle.min.css"
/>
</head>
<body>
<img
src="./assets/decorations/lt.svg"
alt="背景デザイン画像1"
class="page-bg-icon page-bg-icon-lt"
/>
<img
src="./assets/decorations/rb.svg"
alt="背景デザイン画像2"
class="page-bg-icon page-bg-icon-rb"
/>
<div class="page-cover bg-3 text-dark">
<h1 class="page-title text-1">Portfolio</h1>
<div class="card-cover bg-white">
<h2 class="card-title-cover bg-2 text-light">
<div class="card-title">English Title</div>
<div class="card-subtitle">日本語タイトル</div>
<div class="card-title-line"></div>
</h2>
<div class="card-contents">内容</div>
</div>
<div style="height: 20px"></div>
<div class="card-cover bg-white">
<h2 class="card-title-cover bg-2 text-light">
<div class="card-title">Self-Introduction</div>
<div class="card-subtitle">自己紹介</div>
<div class="card-title-line"></div>
</h2>
<div class="card-contents">内容</div>
<div class="card-contents">内容</div>
</div>
<div style="height: 20px"></div>
<div class="app-bar">
<div class="app-bar-title">MENU</div>
<div class="app-bar-button-cover">
<button onclick="scroller('#hobby')">趣味</button>
<button onclick="scroller('#skill')">スキル</button>
<button onclick="scroller('#mywork')">制作物</button>
<button onclick="scroller('#licence')">帰属</button>
</div>
</div>
<div style="height: 20px"></div>
<div class="card-cover bg-white" id="hobby">
<h2 class="card-title-cover bg-2 text-light">
<div class="card-title">Hobby</div>
<div class="card-subtitle">趣味</div>
<div class="card-title-line"></div>
</h2>
<div class="card-contents">内容</div>
<div
class="swiper-area"
style="
--swiper-navigation-color: #fff;
--swiper-pagination-color: #fff;
"
>
スライドショー
</div>
</div>
<div style="height: 20px"></div>
<div class="card-cover bg-white" id="skill">
<h2 class="card-title-cover bg-2 text-light">
<div class="card-title">Skill</div>
<div class="card-subtitle">スキル</div>
<div class="card-title-line"></div>
</h2>
<div class="card-contents tile-grid-cover">内容</div>
</div>
<div style="height: 20px"></div>
<div class="card-cover bg-white" id="mywork">
<h2 class="card-title-cover bg-2 text-light">
<div class="card-title">My Work</div>
<div class="card-subtitle">制作物</div>
<div class="card-title-line"></div>
</h2>
<div class="card-contents">内容</div>
</div>
<div style="height: 20px"></div>
<div class="card-cover bg-white" id="licence">
<h2 class="card-title-cover bg-2 text-light">
<div class="card-title">Licence</div>
<div class="card-subtitle">帰属</div>
<div class="card-title-line"></div>
</h2>
<div class="card-contents">内容</div>
</div>
<div style="height: 40px"></div>
<footer class="footer-cover bg-2 text-light">
<div style="height: 4px; background-color: rgba(0, 0, 0, 0.1)"></div>
<div class="footer-contents">© Yumi's web page.</div>
</footer>
</div>
<script src="https://unpkg.com/swiper@8/swiper-bundle.min.js"></script>
<script>
var swiper = new Swiper(".swiper-area", {
direction: "horizontal",
spaceBetween: 30,
autoplay: { delay: 10 * 1000, disableOnInteraction: false },
loop: true,
pagination: { el: ".swiper-pagination" },
navigation: {
nextEl: ".swiper-button-next",
prevEl: ".swiper-button-prev",
},
});
var TOP_BAR_OFFSET = 48;
function scroller(query) {
var elements = document.querySelectorAll(query);
if (elements.length == 0) {
console.error(
"Selected query:",
query,
"are not found on this page."
);
return;
}
var rect = elements[0].getBoundingClientRect();
window.scrollTo({
top: rect.top + window.pageYOffset - TOP_BAR_OFFSET,
behavior: "smooth",
});
}
</script>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
~~/public/assets/main.css
/* ===== CARD | MY WORK END ===== */
/* ===== APP BAR BEGIN ===== */
.app-bar {
position: sticky;
top: 0;
height: 40px;
max-width: 1000px;
margin: 0 auto;
z-index: 15;
background-color: #fefefe;
box-shadow: 0px 0px 20px -7px rgba(0, 0, 0, 0.56);
box-sizing: border-box;
}
.app-bar .app-bar-button-cover {
display: grid;
padding-top: calc(6px / 2);
padding-left: 20px;
padding-right: 20px;
gap: 0 6px;
}
.app-bar .app-bar-button-cover {
grid-template-columns: repeat(4, 1fr);
}
@media screen and (min-width: 600px) {
.app-bar .app-bar-button-cover {
grid-template-columns: repeat(6, 1fr);
}
}
.app-bar .app-bar-title {
transform: translateX(-50%) translateY(-50%) rotate(-90deg) translateX(-50%)
translateY(calc(50% + 2px));
font-size: 12px;
font-weight: 500;
color: #aeac78;
width: 40px;
position: absolute;
text-align: center;
}
.app-bar button {
height: calc(40px - 6px);
padding: 0 12px;
background-color: #aeac78;
color: #ffffff;
font-weight: 500;
border-radius: calc((40px - 6px) / 2);
}
.app-bar button:hover {
opacity: 0.8;
}
/* ===== APP BAR END ===== */
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# アプリバーの粘着
.app-bar {
position: sticky;
top: 0;
}
2
3
4
上記は .app-bar に指定されている Style の一部です。
position: sticky; と指定すると、スクロール時に画面領域にふれると、画面内に残るように
粘着します。どの位置に粘着するかを top: 0 のプロパティで指定しています。
# アプリバーの前後
.app-bar {
z-index: 15;
}
2
3
上記は .app-bar に指定されている Style の一部です。
要素の前後を z-index: 15; を指定して変更しています。
これを指定しない場合アプリバーがカードの後ろに回り込んでしまいます。
また、 z-index が足りない場合、以下のように一部の要素だけが、前面に出てしまいます。
z-index: 10; とした場合 (Swiper.js のライブラリに z-index が変更されている要素がありました。)

# ボタンの割合
.app-bar .app-bar-button-cover {
display: grid;
padding-top: calc(6px / 2);
padding-left: 20px;
padding-right: 20px;
gap: 0 6px;
}
.app-bar .app-bar-button-cover {
grid-template-columns: repeat(4, 1fr);
}
@media screen and (min-width: 600px) {
.app-bar .app-bar-button-cover {
grid-template-columns: repeat(6, 1fr);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
上記は .app-bar-button-cover に指定されている Style です。
.app-bar-button-cover は Grid レイアウトになっています。
600px を境界に、Grid 1 行に割り当てる 列数を変更しています。 600px 未満では 均等 4 分割、600px 以上では 均等 6 分割です。
今回のポートフォリオでは .app-bar-button-cover の中にある要素の数(ボタンの数)が 4 つのため、
スマートフォンレイアウトでは 4 つのボタンが均等配置されているように見えます。
PC レイアウトでは、右側に少し余白が生まれます。
スマートフォンレイアウト(600px 未満)の場合

PC レイアウト(600px 以上)の場合

# app-bar-title の Style
アプリバーの中に、縦向き表記の 「MENU」 という文字があります。
これは、CSS の transform プロパティを使用して、回転することにより実現しています。
このことがわかるように、background-color: green; を追記して、視覚的に確認できるようにすると良いです。
.app-bar .app-bar-title {
background-color: green; /* ← 確認用 */
transform: translateX(-50%) translateY(-50%) rotate(-90deg) translateX(-50%) translateY(calc(50% + 2px));
font-size: 12px;
font-weight: 500;
color: #aeac78;
width: 40px;
position: absolute;
text-align: center;
}
2
3
4
5
6
7
8
9
10


transform: ...;はまず、 X 軸方向に半分移動、Y 軸方向に半分移動します。その後、90 度回転させます。このようにすることで、要素の中心に対して回転します。最後に X 軸、Y 軸を戻す方向に半分移動させます。※ 最後の translateY については、レイアウトの微調整のため、 2px 内側に来るように調整しています。width: 40px;は アプリバーの高さと同じにしています。position: absolute;で、もとの領域を無視するように指定しています。
# クリック時の挙動
<div class="app-bar">
<div class="app-bar-title">MENU</div>
<div class="app-bar-button-cover">
<button onclick="scroller('#hobby')">趣味</button>
<button onclick="scroller('#skill')">スキル</button>
<button onclick="scroller('#mywork')">制作物</button>
<button onclick="scroller('#licence')">帰属</button>
</div>
</div>
2
3
4
5
6
7
8
9
4 つ <button /> タグがあります。このタグは onclick 属性に対して scroller(...) という値が指定されています。
これは、JavaScript の scroller() という関数を呼び出す書き方です。(...) の中には '#hobby' 等の文字が書かれており、
scroller() を呼び出す際に、異なる値を渡しています。
scroller() はページ下部にある通り、自分で定義する関数です。
以下の場合 2 行目の function scroller(query) { から 17 行目の }の範囲が定義範囲です。
つまり、このボタンがクリックされると、定義した範囲のプログラムが実行されます。
var TOP_BAR_OFFSET = 48;
function scroller(query) {
var elements = document.querySelectorAll(query);
if (elements.length == 0) {
console.error(
"Selected query:",
query,
"are not found on this page."
);
return;
}
var rect = elements[0].getBoundingClientRect();
window.scrollTo({
top: rect.top + window.pageYOffset - TOP_BAR_OFFSET,
behavior: "smooth",
});
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
プログラムの概要は以下の通りです。
- 第一引数(2 行目
queryの部分)でセレクターの文字を受け取ります。 - JavaScript に標準で用意されている
document.querySelectorAll(...)という関数を使用して、セレクタに該当する要素を配列で受け取ります。それをelementsという名前の変数に保存します。 elements.lengthは、配列の長さです。4 行目のifでelements.lengthが 0 かどうかを確認します。elements.lengthが 0 というのは、ページの中に該当する要素が一つもなかったことになるため、処理を継続できません。そのため、console.error関数でエラーメッセージを出力し、returnで関数内の処理終了します。elements.lengthが 0 ではない場合、ifの条件には当てはまらないため 12 行目が実行されます。- 0 番目の要素に対して処理を行います。(複数の要素が見つかった場合は、今回の目的では一番最初に登場する要素に対して処理することにします。)要素に対して実行可能な
getBoundingClientRect()という関数を実行し、要素の寸法と、そのビューポートに対する相対位置に関する情報を受け取ります。その情報は、rectという変数に保存します。内容の詳細は MDN (opens new window) をご確認ください。 - JavaScript に標準で用意されている
window.scrollTo(...)という関数を使用します。これは、指定の位置にページをスクロールする関数です。 第一引数に{ オブジェクト }を渡しますが、 そこにはtopとbehaviorというプロパティが含まれています。topはページ上部からの位置を指定します。rect.topには今見えている範囲からの高さが含まれている(負の数の場合もあります)ため、現在スクロールしている量がわかるwindow.pageYOffsetという変数(中にはスクロールしたピクセル数が含まれています)足し、1 行目で定義したTOP_BAR_OFFSETの量(48)引きます。TOP_BAR_OFFSETを引かない場合、ページの上部ピッタリの位置にスクロールされますが、 アプリバーを粘着させており、 40px の領域が見えなくなってしまうため、 8px の余白とともに 48px 分調整しています。behaviorにはsmoothと指定することにより、対応しているブラウザであれば、スムーズにスクロールします。
# スクロールしない場合
何らかのエラーが発生している可能性があります。
検証 → Console タブ からメッセージが表示されていないか確認してください。
例: Selected query: #hobby are not found on this page
#hobby という要素が無いというエラーです。このエラーはご自身で elements.length == 0 のときに出力するようにプログラムを記述しています。
ページ内のどこにも id="hobby" と書いたタグがないという状況です。趣味のカードタグに id="hobby" を追記してください。

例: Uncaught SyntaxError: Unexpected end of input
JavaScript の構文エラーです。{ などの記号も、間違えないように入力してください。
全角、半角に注意してください。 { と {は違います。1 と 1、( と ( も異なります。
半角 ({, 1, ()で入力してください。
括弧の閉じ忘れにも注意してください。 { は必ず } で終わります。括弧の始まりと終わりの数は一致します。

例: Uncaught ReferenceError: scroller is not defined
関数 (scroller) が定義されていません。Typo(スペルミス)などに注意してください。 また、構文エラーになっていないかどうかも確認してください。

# スクロールがスムーズにならない場合
対応していないコンピュータまたはブラウザの可能性があります。
Chrome の場合 実験的な機能の設定ですが、 chrome://flags/#smooth-scrolling にアクセスして、 Enabled にすることにより、動作確認できる場合があります。
