20200713のCSSに関する記事は8件です。

TechAcademyが無料公開している「レスポンシブWebデザイン入門講座」を受講する~第3回~

アジェンダ

  • メディアクエリ
    • メディアクエリとは
    • 書き方
    • メディアタイプの例
    • メディア特性の例
  • スマートフォンの「フィット」機能への対応
  • 高解像度ディスプレイへの対応
  • 実践

メディアクエリ

メディアクエリとは

PCのモニター(横長)やスマートフォンの画面(縦長)など、表示させるデバイスに適した表示方法を選択する手法。

書き方

メディアクエリは、HTMLのlinkタグで指定する方法と、CSSで指定する方法がある。

html
<!-- 条件を満たすデバイスで閲覧する場合、CSSファイルの記述が適応される. -->
<link rel="stylesheet" href="./style.css" media="メディアクエリの条件文">
css
/* 条件を満たすデバイスで閲覧する場合、CSSの記述が適応される */
@media (メディアクエリの条件文) {
  /* CSSの記述 */
}

「メディアクエリの条件文」は、メディアタイプやメディア特性を指定することができる。

メディアタイプの例

メディアタイプには以下のようなものがある。

all
全てのデバイス
screen
一般的なディスプレイ(モニター、スマートフォン、など)
projection
プロジェクター
print
印刷

メディア特性の例

メディア特性には以下のようなものがある。

width
表示領域の横幅
height
表示領域の縦幅
max-width
CSSが適応される、最大の表示領域の横幅
min-width
CSSが適応される、最小の表示領域の横幅
device-width
デバイスの横幅
device-height
デバイスの縦幅
orientation
デバイスの向き(縦: portrait 横: landscape)

スマートフォンの「フィット」機能への対応

スマートフォンのブラウザには、画面の解像度をページに合わせる「フィット」という機能がある。
この機能が働くと、メディアクエリが正常に働かない場合がある。

フィット機能に対応するために、以下の通りに「デバイスの解像度を、デバイスの画面サイズに設定する」記述をする必要がある。

<meta mame="viewport" content="width=device-width">

高解像度ディスプレイへの対応

ディスプレイの中には、1pxの表示に2つのピクセルを使用する「高解像度ディスプレイ」が存在する。
この高解像度ディスプレイでは、1ピクセルの大きさが通常のディスプレイと異なるため、表示が崩れてしまう場合がある。

高解像度ディスプレイに対応するために、メディア特性に以下の記述を追記する必要がある。

/* 通常のディスプレイ. */
-webkit-device-pixel-ratio: 1

/* 高解像度ディスプレイ. */
-webkit-device-pixel-ratio: 2

実践

上記の内容を用いて以下のindex.html、style_pc.css、style_phone.cssを作成した。

index.html
<!DOCTYPE html>

<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <meta mame="viewport" content="width=device-width">

    <!-- 一般的なPCモニター用のCSS. -->
    <link rel="stylesheet" href="./style_pc.css" media="screen and (min-width: 1024px)">
    <!-- 一般的なスマートフォン用のCSS. -->
    <link rel="stylesheet" href="./style_phone.css" media="(min-width: 768px) and (max-width: 1023px)">

    <title>レスポンシブなサイト</title>
  </head>
  <body>
    <div class="title">レスポンシブなサイト</div>
    <img src="./makoto.jpg" alt="上目遣いの天使" class="profile_icon">
    <div class="content">
      <p>標準的なPCのモニターでは、画像の右側に文字が配置されているはずです。スマートフォンサイズでは、画像の下に文字が配置されているはずです。</p>
      <p>表示させるデバイスによって適した表示方法になるサイトを、<span class="strong">レスポンシブなサイト</span>といいます。</p>

      <!-- 使用しているCSSファイルによって、表示を変化させる. -->
      <p class="device_content">現在、<span class="device"></span>用のWebページが表示されています。</p>
    </div>
  </body>
</html>
style_pc.css
.title {
  font-size: 3em;
  font-weight: bold;
  color: brown;
  text-shadow: 7px 7px 2px burlywood;
  margin: 1em;
  padding: 0.2em;
  border-style: dotted;
  border-color: chocolate;
  border-radius: 1em;
}

.profile_icon {
  margin: 2em;
  width: 500px;
  height: 500px;
  float: left;
}

.content {
  margin: 5%;
}

.strong {
  font-weight: bold;
  text-decoration: underline;
}

.device_content {
  font-size: 2em;
  line-height: 2em;
}

/* PC用のCSSで表示していることをWebページに表示する. */
.device::before {
  content: "PC";
  font-weight: bold;
  color: orange;
}
style_phone.css
.title {
  font-size: 3em;
  font-weight: bold;
  color: brown;
  text-shadow: 7px 7px 2px burlywood;
  margin: 1em;
  padding: 0.2em;
  border-style: dotted;
  border-color: chocolate;
  border-radius: 1em;
}

.profile_icon {
  margin: 0em 25%;
  width: 300px;
  height: 300px;
}

.content {
  margin: 5%;
}

.strong {
  font-weight: bold;
  text-decoration: underline;
}

.device_content {
  font-size: 2em;
  line-height: 2em;
}

/* スマートフォン用のCSSで表示していることをWebページに表示する. */
.device::before {
  content: "スマートフォン";
  font-weight: bold;
  color: orange;
}

これをWebブラウザに表示させるとこんな感じ。
GoogleChromeのデベロッパーツールで、PCで閲覧した場合とスマートフォン(iPhone6)で閲覧した場合を確認。

●PC
index_pc.png

●iPhone6
index_phone.png

このように、今どのデバイスによって、文書の構造や表示させる文章を変化させることもできる。
(CSSで文書を表示させる手法についての是非は、また別の話)

おわりに

今回は、ついにレスポンシブな内容だった。
デバイスごとに複数のCSSファイルを用意するか、一つのCSSファイルを作成してその中でデバイスを指定するか・・・。
より良い設計を心がけるために、調べよう。

参考

3-1 メディアクエリとは(メディアクエリ)
3-2 メディアタイプ(メディアクエリ)
3-3 メディア特性(メディアクエリ)
3-4 環境に合わせた指定の追加(メディアクエリ)
3-5 ページの印刷(メディアクエリ)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WEBサイトで大切な3つのパスの違いについて解説(絶対パス、相対パス、ルートパス)

どうも7noteです。初心者が混乱しやすい3種類のパスについて違いを解説します。

そもそもパスとは何なのでしょうか。今回は「パスとは何か」「違いはどこにあるのか」「メリット・デメリット」について見ていきたいと思います。

パスとは?

「パス」とは日本語では「道」という意味になります。たとえば、「TOPページに表示されているこの画像はTOPページのindex.htmlから見て、このフォルダの中のこのフォルダの中にある○○.jpgという画像を読み込んでいる。」というように、とあるファイルから別のとあるファイルまでたどり着くための道のりを「パス」といいます。

よくあるディレクトリ構造(それぞれのファイルの配置とか住所みたいな意味)を例に考えてみましょう。

〈親子関係をもつディレクトリ構造の例〉

  • image(フォルダ)
    • cat.jpg
    • dog.jpg
  • animal(フォルダ)
    • aaa.html
    • bbb.html
    • ccc.html
  • toppage.html

①toppage.htmlから「cat.jpg」を読み込む場合(相対パス)

「image/cat.jpg」と書きます。

1.png

②aaa.htmlから「dog.jpg」を読み込む場合(相対パス)

「../image/dog.jpg」と書きます。

2.png

これらのように①であれば、「imageフォルダの中のcat.jpg」。②であれば「一つ上(親)のフォルダのimageフォルダの中のdog.jpg」という道を示すことで、画像を読み込むことができます。この「違う場所にあるとあるファイルを読み込むまでの道」を「パス」と言います。

そして、このパスには大きく3つの書き方があり、「絶対パス」「相対パス」「ルートパス」の3種類が存在します。先ほどの①と②の例では相対パスという書き方で書きました。
いまからこれらの書き方や考え方などをご紹介していきます。

3種類のパスについて

「絶対パス」

~絶対にURL全部書かないとダメ~


https://www.domain.com/」
https://qiita.com/image/image.jpg」

上記のようにプロトコルやドメインから始まる書き方は「絶対パス」と言います。
絶対パスは、違うドメインのサイトにリンクさせたり、他のサイトから画像を参照のためもってきたりする場合に使われます。
「どこどこのサイトのこのフォルダの中のこの画像」みたいな書き方になります。

「相対パス」

~書き方いろいろあってちょっとややこしい~


「image.jpg」
「./image/gazou.jpg」
「../../css/style.css」

相対パスはちょっとややこしく、このファイルからみてどこのファイルを読み込むのかをしっかりと把握して記述する必要があります。
少しややこしいかもしれませんが、相対パスの中でも大きく2種類の書き方を覚えるだけで大丈夫です!

A.「./」(もしくは記載なし「 」)
これは「現在表示しているこのファイルからみて」という書き方になります。
例の2行目「./image/gazou.jpg」でいうと、「このファイルと同じ階層にあるimageフォルダのgazou.jpg」という意味になります。

B.「../」
これは「現在のファイルからみた一つ上(親)」という書き方です。
例の3行目「../../css/style.css」でいうと、「このファイルの親の親にある、cssというフォルダの中のstyle.css」という意味になります。

注意点 同じサーバーにあるファイルを参照するので、他のサイトにリンクさせたり、他のサイトの画像を読み込むことはできません。

「ルートパス」

~いつも同じスタート地点~


「/image/image.jpg」
「/image/gazou.jpg」
「/css/style.css」

ルートパスは「常に一番親にあたる位置から見て考える」書き方になります。
どの場所にあるファイルから読み込もうとしても、常に同じスタート地点である一番親のディレクトリから考えてどこにあるファイルなのかを表します。

メリットとしては、ファイルを管理する場所さえ変わらなければどこにあるファイルからでも同じ書き方で同じファイルを読み込むことができます。

まとめ

WEBサイトなどでリンクや画像をいれる際は基本的には、「相対パス」で書くのが基本になると思います。
ただ、ヘッダーのロゴ画像などはどのページからでも同じ場所にある画像を読み込む必要があるので、「ルートパス」で書くといいでしょう。
外部のサイトなどへのリンクをいれるときには、絶対パスでなければ読み込むことができません。

パスは何度も使うことで場面場面で適切なパスの書き方をすることができるようになりますので、日々トレーニングを積み重ねてください!

おそまつ!

(コメント・質問・ソースの指摘等なんでもウェルカムです!初心者の方でも気軽に質問ください!)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WEBサイトのパフォーマンスUPのためのCSSの最適化

少しでも早いサイトを作ってみたくて、WEBサイトパフォーマンスのよくなる方法を調べました。
今回はCSSについてです。

CSSプロパティを短く書いて、CSSのファイルの容量を減らす

CSSファイルの圧縮をするだけではなく、書き方を工夫してCSSのファイルの容量を減らすことができる。

CSS短縮形で書く

fontやmarginなど、複数プロパティではなく、単一プロパティで書く

浅いCSSセレクタを使う

CSSセククタの要素指定を細かくせず、階層を浅くする。

繰り返しの除去

DRYに書きましょう。

CSSのセグメント化

ページのテンプレートごとにCSSを分割する方法。
1つにまとめると不要なCSSもユーザが読み込んでしまう。

 モバイル対応

レスポンシブにする。

CSSのチューニング

@importを使わない

タグと異なり、スタイルシートの全体がダウンロードされるまで@importの処理が行われない。

CSSは内に置く

スタイル未指定のコンテンツのちらつきを防ぐことができる。

flexboxの利用

CSSトランジション

CSSトランジション

  • サポートするブラウザが多い
  • 複雑なDOMのリフローの際にCPUが効率的に使われる
  • オーバヘッドがない

単純なUI操作であれば、jQueryよりもパフォーマンスが良い。

参考

Webサイトパフォーマンス実践入門

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

classのない要素にだけcssを適用する

class属性のない要素にだけcssを適用したいときは疑似否定クラスを使う。

<p>文字を赤くしたい</p>
<p class="ao">こっちは変えたくない</p>
p:not([class]) {
  color:red;
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者によるプログラミング学習ログ 369日目

100日チャレンジの369日目

twitterの100日チャレンジ#タグ、#100DaysOfCode実施中です。
すでに100日超えましたが、継続。
100日チャレンジは、ぱぺまぺの中ではプログラミングに限らず継続学習のために使っています。
369日目は、

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

約2か月間で、HTML5レベル1に合格出来ました

HTML5プロフェッショナル認定 レベル1合格しました

2020年の7月にHTML5プロフェッショナル認定 レベル1に合格することができました。
私は2019年の6月に中途入社で今の会社に入り、ITは全くの未経験でした。
また、2019年の10月に受けた基本情報技術者試験では、午前の点数が足りず、不合格になりました
(基本的には午後で不合格になる人が多いので、周りからは何やってんだよ、と
叱責を受けました、、、)
そのため、IT系で取得した初めての資格となります。

資格概要

資格名:HTML5プロフェッショナル認定 レベル1
時間:90分
問題数:60問
合格点:約70点/100点(都度、変わるそう)
受験料:15,000円(税別)
日時・会場:全国各地のテストセンターから自由に選択できる
試験方式:コンピュータベーストテスト(CBT)
キーボード入力問題がいくつか出題される。
認定の有効期限:5年間
HTML5プロフェッショナル認定試験公式サイト

HTML5,CSS3,JavaScriptなど最新のマークアップに関する技術力と知識を認定する認定資格です。
レベルは1と2があり、私はレベル1を受験しました。
レベル1ではhtmlとcss
レベル2ではjavascriptがメインらしいです

出題範囲として
要素(htmlの要素)
CSS3
レスポンシブルwebデザイン
API概要
web概要(httpプロトコルなど)
この5項目が出題されます。
比率とかは、公式サイトをご覧ください。

勉強期間

約2か月間

勉強方法

最初の1か月→テキストの内容理解(メモ取りつつ)、テキスト内の練習問題、公式サイトの練習問題

最初の1か月間は購入したテキストをひたすら読んで理解しました。
しかし、簡単には理解できませんでした、、、
→そのため、html要素とcssについては、テキストエディタ(サクラエディタなど)を使って、実際にどのような挙動になるのか確認していました。

レスポンシブルwebデザイン、API概要については、知らない単語が多すぎて、かなり苦労しました。
この対策としては一つ一つネットで調べる笑
丸暗記するのは簡単ですが、仕組みを知っているだけで、色々恩恵も受けれますしね。

web概要については、基本情報技術者の試験で、学習していた内容と被っているところもあり、比較的覚えやすかったですね。

2周くらいテキストを読んで、ある程度理解ができてきたら、テキストおよび公式サイトにある問題を解きます。
自分は、メモ帳にメモを取っていたので、出た範囲に印をつけていました。
どこが出題されやすいのか、など傾向が掴みやすくなりました!

残りの1か月→ping-tの問題を全部金にするまでひたすら解く

他の方も書かれているかと思いますが、ping-tでひたすら問題を解いていました。

いやーこのping-t、めっちゃいいです笑
1か月2400円かかりますが、問題の質も本番と似ていて、何より解説がしっかり書かれていました
正直、テキストは少し自分では理解できないところもあったのですが、ping-tではその点が初心者でもわかりやすいように、かみ砕いて書かれていたな、という印象です。

また、「分野別」、「模擬試験」と2パターンの出題方法を選ぶことができて
まずは、分野別に問題を解いて、都度復習
全て、銀のボックスに入れ終わったら模擬試験形式で問題を解く。
常に、95%以上の正答率、金のボックスにすべての問題を入れ終わることができるレベルまでひたすら解きました
(もともと銅のボックスに問題が入っていて、1回正解したら、銀のボックス、2回連続で正解したら金のボックス、という風に問題を押し出していくイメージです)

受験結果

78点獲得して、見事合格です(合格最低点数70点)

所感

正直、もうちょっと獲得できたかなーって感じでした。
そこそこ問題は解いてきたので、少しショック、、、

本番の問題は、ping-tよりは少し難しい問題が出ました。
個人的な手ごたえとして
自信のある問題→65%くらい
少し迷った問題→20%くらい
かなり迷った問題→10%くらい
分からない問題→5%くらい

結論、ほとんどが見たことのある内容でした
テキスト、ping-t、公式サイトの問題を完全に理解していれば、95%は取れるってことですね。
ただ、細かい理解が追い付いていなかったり、でこういう結果だったんでしょう、、、
これは、今後の教訓ですね。

問題の難易度ですが
公式サイト問題>本番>テキストの練習問題>ping-tといったところでしょうか。
公式サイトの問題は、かなり意地悪な問題多かった印象ですね笑
テキストの問題が一番本番に近かったですかね
ping-tの問題は、解くというより、解いた後の解説を見るということが大切かと思います。
前も触れたように、本当に細かく解説してくれていますので勉強になります。

また、公式サイト問題、テキスト問題での知識が本番でも出たりしたので、こちらは解いて損は無いかと思います!

総括

htmlやcssは今後、業務で使用していく頻度が高いので、勉強しておいて損はなかったです。
また、業務でhtmlのソースを見ても、容易に解読できるようになったなと思います!
今後はwebサイトの作成とかも趣味で行っていければいいな~
多分、合格したいだけであれば、要領良い人なら1か月あればできるのかなと思います。
ただ私は、本質を理解したいなと思っていたので、多少時間はかかりましたが、知識を身に着けて、かつ合格できて本当に良かったと思います。

初投稿となり、拙い文章でしたが、最後まで読んでいただきありがとうございました!

参考文献

Qiita初投稿だったため、文章作成に際して、こちらの記事を参考にさせていただきました。
ありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript + CSS: ヘッダを上部に固定してカラムの中身はスクロールさせる

お題は、つぎのサンプル001のようなレイアウトです。ヘッダを上部に固定し、左カラムのリストはスクロールします。

サンプル001■JavaScript + CSS: Fixed top header and scrolling column

See the Pen JavaScript + CSS: Fixed top header and scrolling column by Fumio Nonaka (@FumioNonaka) on CodePen.

たとえば、Bootstrapの「Documentation」もこのスタイルを採り入れています。

図001■BootstrapのDocumentationページ

2007001_001.png

基本となるCSSの設定はごく簡単です。けれど、ユーザーがウィンドウサイズを変えたときなど、設定は動的に更新しなければなりません。この部分は、JavaScriptコードで処理します。

ページの基本的な構成

サンプルの<body>要素の中身はつぎのように組み立てました。ページを構成する要素は大きく3つ、ヘッダと左カラム、そしてメインコンテンツです(図002)。CSSのclass属性の定めは省いています。確かめたい方は、前掲サンプル001をご覧ください(Bootstrap 4.5使用)。

<body>要素
<header id="header">
    <h1>Header</h1>
</header>
<div>
    <div id="left-column">
        <h3>Left column</h3>
        <ul id="list">
            <li>...[中略]...</li>
        </ul>
    </div>
    <main>
        <h2>Main contents</h2>
        <p>...[中略]...</p>
    </main>
</div>

図002■ページはヘッダと左カラムにメインコンテンツで組み立てられる

2007001_002.png

ヘッダをページ上部に固定する

まずはサイトでよく見かける、ヘッダあるいはトップナビゲーションをページ上部に固定するCSSの設定です。

位置はpositionプロパティにfixedを与えれば固定できます。具体的な置き場所は、別にプロパティで定めなければなりません。上部ならtop: 0です。ただし、<body>要素の領域に含まれなくなることにお気をつけください。そのままでは、ページの上部がかぶって隠れてしまうのです(図003)。

<style>要素
#header {
    position: fixed;
    top: 0;
}

図003■ページ上部にヘッダがかぶって見えない

2007001_003.png

<body>要素のpadding-topは、ヘッダの高さ分下げなければなりません。サンプルでは、ヘッダの高さを調べると72pxでした。

body {
    padding-top: 72px;
}

これで、ヘッダはページ上部に固定されます。

カラムの中身をスクロールさせる

要素の中のテキストをスクロールさせるには、その高さ(height)が具体的に決められていなければなりません。そのうえで、overflowプロパティはscrollにします。高さに収まらないテキストは、スクロールして表示する設定です(図004)。

#left-column {
    height: 500px;
    overflow: scroll;
}

図004■左カラムの高さに収まらないテキストはスクロールで表示される

2007001_004.png

もっとも、高さを決め打ちしたのでは、役に立ちません。ユーザーが開いたウィンドウサイズに合わせて、カラムの高さをJavaScriptで変えましょう。

<styel>要素
#left-column {
    /* height: 500px; */

}

カラムの高さを動的に決める

ブラウザウィンドウの内側(ビューポート)の高さを返すのはwindow.innerHeightプロパティです。また、要素の高さはelement.clientHeightで得られます。

したがって、ビューポートの高さからヘッダの高さを差し引いて、左カラムの高さとすればよさそうです。ただし、element.clientHeightは、読み取り専用プロパティであることにご注意ください。そのため、高さの設定には、HTMLElement.styleを用います。プロパティ(height)の値は、要素のインラインstyle属性と同じ文字列です。単位(px)も忘れないようにしてください。

<script>要素
document.addEventListener('DOMContentLoaded', () => {
    const header = document.getElementById('header');
    const leftColumn = document.getElementById('left-column');
    const setLayout = () =>
    leftColumn.style.height = (window.innerHeight - header.clientHeight) + 'px';
    setLayout();
    window.addEventListener('resize', setLayout);
}

スクリプトを改善する

ヘッダの高さはJavaScriptコードでわかったのですから、<body>要素のpadding-topもこの処理の中で定めましょう。ヘッダやナビゲーションバーは、その中身によってはウィンドウ幅に応じて変わることもあるからです。

<style>要素
body {
    /* padding-top: 72px; */

}

HTMLElement.styleに定めるのはJavaScriptのプロパティです。つまり、ケバブケース(padding-top)は使えないことにお気をつけください。キャメルケース(paddingTop)に改めるのが決まりです。

<script>要素
document.addEventListener('DOMContentLoaded', () => {
    const body = document.querySelector('body');
    const setLayout = () => {
        const headerHeight = header.clientHeight;
        body.style.paddingTop = headerHeight + 'px';
        leftColumn.style.height = (window.innerHeight - headerHeight) + 'px';
    }

}

スクロール操作を試していると、ちょっとした不具合が見つかります。メインの領域のテキストが多い場合、下までスクロールすると左カラムも上に動いてしまうのです(図005)。

図005■メイン領域とともに左カラムが上に動いてしまう

2007001_005.png

動かなくするには、<body>要素と同じく左カラムも固定しなければなりません。すると、カラムが<body>の領域から外れて、メイン(<main>要素)のテキストが左に潜ってしまいます。左余白の調整も必要です(marginを使ったのはCSSですでにpaddingを定めていたからです)。

<script>要素
document.addEventListener('DOMContentLoaded', () => {

    const main = document.querySelector('main');

    const setLayout = () => {

        leftColumn.style.position = 'fixed';
        leftColumn.style.top = headerHeight;
        main.style.marginLeft = leftColumn.clientWidth + 'px';
    }

}

CSSの定めと書き上げたJavaScriptの記述は、つぎのコード001のとおりです。細かい内容や動きは、冒頭のサンプル001でお確かめください。

コード001■<header>を上部に固定して<div>の中身はスクロールさせる

<style>要素
#header {
    position: fixed;
    top: 0;
}
#left-column {
    overflow: scroll;
}
<script>要素
document.addEventListener('DOMContentLoaded', () => {
    const body = document.querySelector('body');
    const main = document.querySelector('main');
    const header = document.getElementById('header');
    const leftColumn = document.getElementById('left-column');
    const setLayout = () => {
        const headerHeight = header.clientHeight;
        body.style.paddingTop = headerHeight + 'px';
        leftColumn.style.height = (window.innerHeight - headerHeight) + 'px';
        leftColumn.style.position = 'fixed';
        leftColumn.style.top = headerHeight;
        main.style.marginLeft = leftColumn.clientWidth + 'px';
    }
    setLayout();
    window.addEventListener('resize', setLayout);
});
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

スクロールに連動して現在地を示す目次を作りたい

ブログみたいにWEB上で長い文章を読ませるときは、いまどこを読んでいるのか教えてくれるような目次があるほうが便利です。
目次をクリックするとそのセクションに飛んでくれるとさらに便利です。

イメージとしてはangular.jpみたいなこれ↓↓とか、Qiitaの横にもあるアレとか。
なんかのライブラリが探せばありそうだけど普通にJavaScript書けば十分そう、ということで作ってみましょう。

20200712.gif

HTML構造

サンプル。
目次(Table of contents)になるul要素と、記事の表示領域になるwrapperoverflow: autoでスクロールさせる)を作ります。
中身はdivで区切ってありますが特に意味はないです。heightで疑似的に記事の長さを出したり背景色をつけて見やすくするためのものです。

<ul>
  <li class="toc" scrollTo="content1">AAAA</li>
  <li class="toc" scrollTo="content2">BBBB</li>
  <li class="toc" scrollTo="content3">CCCC</li>
  <li class="toc" scrollTo="content4">DDDD</li>
</ul>
<div class="wrapper">
  <div class="content content1">
    <h1>AAAA</h1>
  </div>
  <div class="content content2">
    <h1>BBBB</h1>
  </div>
  <div class="content content3">
    <h1>CCCC</h1>
  </div>
  <div class="content content4">
    <h1>DDDD</h1>
  </div>
</div>

CSS

目次上の現在地は.activeクラスで丸印を付けて判別しやすくします。
他はオマケ。

ul {
  list-style: none;
}
li {
  border-left: solid 1px silver;
  padding-left: 1rem;
  position: relative;
  box-sizing: border-box;
}
.toc {
  cursor: pointer;
  width: 100px;
}
.toc:hover {
  background: whitesmoke;
  color: royalblue;
}
.toc:hover:not(.active)::before {
  content: '';
  position: absolute;
  top: 0.5rem;
  left: -3px;
  border-radius: 50%;
  width: 5px;
  height: 5px;
  background: silver;
}
.toc.active:before {
  content: '';
  position: absolute;
  top: 0.5rem;
  left: -3px;
  border-radius: 50%;
  width: 5px;
  height: 5px;
  background: skyblue;
}
.wrapper {
  width: 400px;
  height: 300px;
  overflow: auto;
}
.content {
  overflow: hidden;
}
.content1 {
  width: 100%;
  height: 500px;
  background: skyblue;
}
.content2 {
  width: 100%;
  height: 500px;
  background: royalblue;
}
.content3 {
  width: 100%;
  height: 500px;
  background: lightblue;
}
.content4 {
  width: 100%;
  height: 500px;
  background: aliceblue;
}

JavaScript

本題。
アプローチとしては以下の通りです。

  1. 記事中の各セクションの開始位置、終了位置を覚えておく。
  2. スクロールイベントリスナで、現在のスクロール位置と各セクションの範囲を比較して.activeクラスを付ける。
  3. 最後までスクロールしたら最後のセクションに.activeクラスを付ける。
  4. 目次をクリックしたら、対応するセクションの開始位置を取得してスクロール位置を変更する。

3は、セクションの高さがスクロール領域よりも小さい場合にスクロール位置が最後のセクションの範囲に入らないことがあるので、その対策です。
たとえば、スクロール可能なwrapper領域が500pxあったとして、コンテンツの合計heightが2000px、最後のセクションのheightが300pxであった場合、wrapperを最後までスクロールさせても最大1500までしか行けない(1500~2000が表示された状態)ので、単純な比較では最後のセクション(1700~2000)には永遠に入らないことになります。

// これをbody.onLoadとかで動かす
function onLoad() {
  const wrapper = document.querySelector('.wrapper'); // ラッパー(スクロール領域)
  const contents = document.querySelectorAll('.content'); // 各セクションのコンテンツ
  const toc = document.querySelectorAll('.toc'); // 目次(クリックしたらそのセクションにスクロール)
  const contentsPosition = [];
  contents.forEach((content, i) => {
    const startPosition =
      content.getBoundingClientRect().top -
      wrapper.getBoundingClientRect().top +
      wrapper.scrollTop;
    const endPosition = contents.item(i + 1)
      ? contents.item(i + 1).getBoundingClientRect().top -
        wrapper.getBoundingClientRect().top +
        wrapper.scrollTop
      : wrapper.scrollHeight;
    contentsPosition.push({ startPosition, endPosition });
  });

  // スクロール位置に応じてTOCの現在位置を変更する
  const calcCurrentPosition = () => {
    toc.forEach((item, i) => {
      const { startPosition, endPosition } = contentsPosition[i];
      item.classList.remove('active');
      if (
        wrapper.scrollTop + wrapper.getBoundingClientRect().height ===
        wrapper.scrollHeight
      ) {
        toc.item(toc.length - 1).classList.add('active');
      } else if (
        wrapper.scrollTop >= startPosition &&
        wrapper.scrollTop < endPosition
      ) {
        item.classList.add('active');
      }
    });
  };

  // スクロールイベントリスナを登録
  wrapper.addEventListener('scroll', calcCurrentPosition);

  // 目次にクリックイベントリスナを登録
  toc.forEach((item) => {
    item.addEventListener('click', () => {
      const destination = event.target.getAttribute('scrollTo');
      wrapper.scrollTop =
        document.querySelector(`.${destination}`).getBoundingClientRect().top -
        wrapper.getBoundingClientRect().top +
        wrapper.scrollTop;
    });
  });

  calcCurrentPosition();
}

セクションの位置を求めるのにはElement.getBoundingClientRect()を利用してます。
Element.getBoundingClientRect()で指定した要素のビューポート上の位置を取得できるので、wrapperのtop位置やscrollTopを足し引きして「wrapper内での絶対位置」を計算しています。
各セクションの位置がわかれば、あとはwrapper.scrollTopとの比較で現在地を特定することができます。

Element.getBoundingClientRect() - Web API | MDN
Element.scrollTop - Web API | MDN

目次に対応するセクションを特定するのはscrollToみたいなワケワカラン属性で対象クラスを指定させていますが、ちゃんとやるならたぶんidとかを使う方がいい気がします。

結果

20200712-1.gif

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む