令和のHTML / CSS / JavaScriptの書き方まとめ

animal_chara_radio_penguin ※Web開発

Web制作の技術は日々進化しており、会社やプロジェクトによっては昨今の環境に適さない書き方をしているケースも時折見受けられます。

そこで今回は「2024年のWeb制作ではこのようにコードを書いてほしい!」という内容をまとめました。

質より量で、まずは「こんな書き方があるんだ」をこの記事で伝えたかったので、コードの詳細はあまり解説していません。なので、具体的な仕様などを確認したい方は参考記事を読んだりご自身で調べていただけると幸いです。!

当記事では「iOS Safari バージョン15系以上」でサポートされている技術を基本的に紹介しています。しかし、15系や16系でサポートされていない技術も少しだけ含まれているので、その場合は補足をしておりますのでご留意ください。

1. HTML

Lazy loading

<img>loading="lazy"属性を付けると画像が遅延読み込みになり、サイトの読み込み時間が早くなります。

<img src="..." alt="" width="600" height="400" loading="lazy">

loading="lazy"属性の補足です。

  • widthheightが必須(レイアウトシフト対策にもなるので必ず付けましょう)
  • iframe要素にも使える
  • 仲間的なdecoding="async"はあまり意味がない

Picture要素

画面幅に応じて画像を出し分ける時は<picture>を使います。

CSS側(display: noneなど)で画像を出し分けると、小さい画面幅の時には不要な「大きい画面幅用の画像」も読み込まれるのでサイトパフォーマンスが悪くなります。

<picture>
  <source media="(min-width:768px)" srcset="lerge.png" width="400" height="200">
  <img src="small.png" alt="" width="80" height="40">
</picture>

Details要素

アコーディオンの実装には<details>を使います。ページ内検索で閉じているアコーディオンの中身もヒットしたり、開閉処理が備え付けられているなどのメリットがあります。

<details>
  <summary>タイトル</summary>
  アコーディオンの中身
</details>

開閉処理のアニメーションには、GSAPやgrid-template-rowsを使った方法があります。

Dialog要素

モーダルウィンドウの実装には<dialog>を使います。アクセシビリティに優れていたり、z-indexを使わなくても最上位に表示されるなどのメリットがあります。

<dialog open>
  <div>モーダルのコンテンツ</div>
  <form method="dialog">
    <button>閉じる</button>
  </form>
</dialog>

iOS Safariのバージョン15.3以下がサポート外なので、プロジェクトの要件に満たしているかを必ず確認しましょう。

Hgroup要素

見出しに複数の要素(主題+副題)がある場合は<hgroup>でグルーピングします。

<hgroup>
  <h2>DX支援事業</h2>
  <p>経営課題をDXで解決</p>
</hgroup>

Dl要素

<dl>の直下には<div>を置き、その直下に<dt><dd>を置くことでスタイリングがしやすくなります。最新のWHATWGの仕様では<dl>の直下に<div>を置けるようになっています(<div>を置かなくても仕様的には問題ありません)。

🥳 Good!!

<dl>
  <div>
    <dt>クラウドコンピューティング</dt>
    <dd>インターネット経由でコンピューターの資源を提供する...</dd>
  </div>
  <div>
    <dt>API</dt>
    <dd>Application Programming Interfaceの略で、ソフトウェア間で...</dd>
  </div>
</dl>

🤮 Bad…

<dl>
  <dt>クラウドコンピューティング</dt>
  <dd>インターネット経由でコンピューターの資源を提供する...</dd>
  <dt>API</dt>
  <dd>Application Programming Interfaceの略で、ソフトウェア間で...</dd>
</dl>

2. CSS

モダンなCSSを取り入れるなら、まずはレイアウト手法のGrid Layoutに慣れることからスタートするといいでしょう。また、CSSは特にブラウザのサポート状況が複雑なので、Can I use…などでしっかりと確認してから実務に取り入れてください。

Grid Layout

記事一覧などの格子状のレイアウトはGrid Layoutで実装します。Flexboxに比べ、レスポンシブ時に要素の順番や大きさが変わるケースにも対応ができたり、calc()を使った横幅や余白の複雑な計算も不要になります。

例1

.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr); /* 3列に並べる */
  gap: 40px; /* 子要素の上下左右の間隔 */
}

例2

.grid {
  display: grid;
  grid-template-areas: "thumb title" "thumb description"; /* 付けた名前を並べる */
  grid-template-columns: 300px 1fr; /* 1列目は300px、2列目は余った幅全て */
}

/* gridの子要素 */
.grid_title {
  grid-area: title; /* 名前を付ける */
}
.grid_description {
  grid-area: description;
}
.grid_thumb {
  grid-area: thumb;
}

Subgrid

Grid Layoutで並べた各アイテム内の要素の縦位置を揃えたい時にSubgridを使います。こちらの例では、説明文の高さがバラバラでも日付の縦位置が同じ位置になるように実装しています。

.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 40px;
}

.card {
  display: grid;
  grid-template-rows: subgrid;
  grid-row: span 3;
  gap: 20px;
}

iOS Safariのバージョン15.8以下がサポート外なので、プロジェクトの要件に満たしているかを必ず確認しましょう。

gap

Flexboxで横並びにした要素の余白を調整するならgapプロパティを使います。marginを使うと、calc◯◯-of-typeなどの記述が発生するので複雑になってしまいます。

🥳 Good!!

.flex {
  display: flex;
  gap: 20px;
}

🤮 Bad…

.flex {
  display: flex;
}
.child {
  margin-left: 20px;
}
.child:first-of-type {
  margin-left: 0;
}

:has / :is / :where

便利な擬似クラスがここ数年で追加されました。

擬似クラス解説備考
:has()引数に指定した子孫要素を持つ場合、自分自身にマッチiOS Safari バージョン15.3以下ではサポート外
:is()引数に指定した要素にマッチ詳細度は通常計算
:where()引数に指定した要素にマッチ詳細度が常に0になる

:has()を使うことで、CSSだけで子要素の有無に応じてスタイルを変えられます(これまではJSを使っていました)。

:has()

/* .card の中に a が含まれているなら背景を赤に、含まれていないなら青にする */
.card {
  background-color: blue;
}
.card:has(a) {
  background-color: red;
}

:is() :where()を使うことで、親要素や前方隣接要素の状態に応じた記述が楽になります。

🥳 Good!!

.post:is(h2, h3, h4, h5, h6) {
  font-weight: bold;
}

🤮 Bad…

.post h2, .post h3, .post h4, .post h5, .post h6 {
  font-weight: bold;
}

Sassでも便利な使い方があります。以下はラジオボタンの選択状態に応じて背景色を変える例で、:is()を使うことでspanのブロック内にスタイルをまとめています。

🥳 Good!!

.radio {
  span {
    background-color: blue; // 未選択の時
    &:is(input:checked + span) {
      background-color: red; // 選択済みの時
    }
  }
}

🤮 Bad…

.radio {
  span {
    background-color: blue; // 未選択の時
  }
  input:checked + span {
    background-color: red; // 選択済みの時
  }
}

object-fit

background-sizeプロパティの挙動を使うために画像をbackground-imageプロパティで読み込むのは古い手法です。昨今では<img>で読み込んだ画像に対してobject-fitを使うことで、background-sizeと全く同じ挙動を再現できます。

<img>を使えば遅延読み込みなどの恩恵を受けられるので、画像はできるだけ<img>で読み込むようにしましょう。

🥳 Good!!

.img {
  width: 100px;
  height: 100px;
  object-fit: cover;
}

🤮 Bad…

.img {
  width: 100px;
  height: 100px;
  background-image: url(...);
  background-size: cover;
}

aspect-ratio

画像の比率を制御するにはaspect-ratioプロパティを使います。padding-top%で指定する昔ながらの手法もありますが、aspect-ratioのほうが記述が簡潔で分かりやすいです。

🥳 Good!!

.img {
  width: 100px;
  height: 100px;
  aspect-ratio: 16/9; /* 縦横比を16:9に */
  object-fit: cover; /* coverを指定しないと画像の縦横比が崩れる */
  object-fit-position: center top; /* 必要に応じて画像の位置を調整 */
}

🤮 Bad…

.parent {
  position: relative;
  padding-top: 56.25%; /* 56.25% = 16:9 (9/16*100%) */
}
.child {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

inset

親要素全体に自身のサイズを広げる場合、insetプロパティを使うと記述が簡潔になります。insettop left right bottomを一括指定するショートハンドプロパティです。

🥳 Good!!

.element {
  position: absolute;
  inset: 0;
  margin: auto;
}

🤮 Bad…

.element {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  margin: auto;
}

margin-inline: auto

横幅を指定している要素の左右中央寄せはmargin-inline: autoを使います。

🥳 Good!!

/* margin-inlineを使った書き方 */
.element {
  margin-inline: auto;
}

🤮 Bad…

.element {
  margin-left: auto;
  margin-right: auto;
}

.element {
  margin: 0 auto;
}

place-content: center

横幅を指定しない要素の左右中央寄せはplace-content: centerを使います。

🥳 Good!!

.parent {
  display: grid;
  place-content: center;
}

🤮 Bad…

.parent {
  display: flex;
  justify-content: center;
  align-items: center;
}

width: fit-content

width: fit-contentを指定すると自身の横幅が子要素の横幅と同じ値になります。つまり、widthに固定値を指定しなくてもmarign-inline: autoなどで中央配置できるようになります。

🥳 Good!!

/* ひとつの要素で中央配置が可能に! */
.target {
  width: fit-content;
  marign-inline: auto;
}

🤮 Bad…

.parent {
  text-align: center;
}
.target {
  display: inline-block;
}

word-break

文字列がはみ出ないように折り返しをword-break: break-wordで制御している方は多いと思いますが、現在は非推奨です。

文字列の折り返しについてはICSさんの記事で丁寧に解説されているので是非ご覧ください。

🥳 Good!!

body {
  overflow-wrap: anywhere;
  word-break: normal;
  line-break: strict;
}

🤮 Bad…

body {
  word-break: break-word;
}

transform

transformプロパティのtranslaterotateは独立プロパティになったので、以下のように指定できます。

.element {
  translate: 10px;
  scale: 1.5;
  rotate: 45deg;
}

複数の変形を行っている場合の記述も簡潔になります。

🥳 Good!!

.icon {
  translate: 10px;
  rotate: 45deg;
}
a:hover .icon {
  rotate: 90deg;
}

🤮 Bad…

.icon {
  transform: translate(10px) rotate(45deg);
}
a:hover .icon {
  transform: translate(10px) rotate(90deg);
}

transition

transitionプロパティを使う時はアニメーションを適用させたいプロパティを必ず指定します。

プロパティを指定しないでtransition: all 0.3sのようにすると全てのプロパティにアニメーションが適用されるので、ページ読み込み時やレスポンシブ時に変な挙動になることがあります。

🥳 Good!!

.fadein {
  transition: opacity 0.3s;
}

🤮 Bad…

.fadein {
  transition: 0.3s;
}

filter

filterプロパティを使うことで、画像をぼかしたり暗くしたりすることができます。hover時に画像をぼかすような処理も、ぼかし用の画像に切り替えるのではなくCSSだけで完結するので、画像が運用時に変わってもぼかし用の画像作成が不要になります。

.photo {
  filter: blur(10px);
}

clip-path

三角形などの図形を描画するにはclip-pathプロパティを使います。三角形を作るにはborderを使った昔ながらの手法がありますがclip-pathのほうが直感的に扱えます。

🥳 Good!!

.triangle {
  clip-path: polygon(100% 50%, 0 0, 0 100%);
  width: 100px;
  height: 100px;
  background-color: red;
}

🤮 Bad…

.triangle {
  width: 0;
  height: 0;
  border-style: solid;
  border-width: 100px 0 100px 173.2px;
  border-color: transparent transparent transparent red;
}

便利なジェネレーターもあります。

currentColor

currentColorを値として指定すると、現在のcolorプロパティの値が参照されます。

https://embed.zenn.studio/card#zenn-embedded__0d5086c83db96

以下のようなボタンの実装例を用意しました。currentColorを使うことで、hover時のsvgの色指定を省略できます。

HTML

<a class="button" href="">
  <span>BUTTON</span>
  <svg ... /> // 矢印アイコン
</a>

🥳 Good!!

.button {
  color: white;
  /* ...略 */
}
.button:hover {
  color: blue;
  /* ...略 */
}
.button svg {
  fill: currentColor; /* .buttonのcolorを参照しているので、通常時はwhite、hover時はblueになる */
}

🤮 Bad…

.button {
  color: white;
  /* ...略 */
}
.button:hover {
  color: blue;
  /* ...略 */
}
.button svg {
  fill: white;
}
.button:hover svg {
  fill: blue;
}

clamp()

clamp関数はvwなどの動的な値に対して最大(最小)値を設定できます。

例えば、フォントサイズにvwを指定すると大きく(小さく)なりすぎることがありますが、clamp関数を使うことで最大(最小)の文字サイズを指定できるようになります。ブレイクポイントでvwの値を変えるより直感的に扱えます。

🥳 Good!!

.text {
  font-size: clamp(16px, 5vw, 20px); /* ベースサイズは5vw、最小16px、最大20px */
}

🤮 Bad…

.text {
  font-size: 5vw;
}
@media (max-width: 767px) {
  .text {
    font-size: 8vw;
  }
}

便利なジェネレーターもあります。

svh

要素の高さを画面いっぱいにするには100vhではなく100svhを指定します。vhはiOSのアドレスバーの高さを含んでしまうので「画面の高さ+アドレスバーの高さ」になってしまいますが、svhはアドレスバーの高さを含まない純粋な「画面の高さ」のみを取得できます。

.main-visual {
  height: 100svh;
}

border-radius: 100vmax

完全な角丸のボタンを実装する時のborder-radiusには9999pxなどの大きい数値を指定するのではなく100vmaxを指定することで、ボタンがどんな大きさになっても完全な角丸を保てるようになります。

🥳 Good!!

.button {
  border-radius: 100vmax;
}

🤮 Bad…

.button {
  border-radius: 9999px;
}

@media (min-width: 768px) & range記法

昨今のブラウザではメディア種別のscreenを省略しても「画面」と認識してくれるので、メディアクエリのscreen andは省略しても問題ありません。

🥳 Good!!

@media (min-width: 768px) {
  .element { ... }
}

🤮 Bad…

@media screen and (min-width: 768px) {
  .element { ... }
}

また、range記法という記述方法も2023年にリリースされました。

@media (width <= 768px) {
  .element { ... }
}

iOS Safariのバージョン16.3以下がサポート外なので、使う場合はコンパイラを挟むことを推奨します。

any-hover: hover

スマホやタブレットなどタップで操作をする端末ではhover処理は無効にします。

タップデバイスを判定するにはメディア特性のany-hover: hoverを使います。昨今は小さいノートパソコンや大きいスマホなどがあるので、画面幅で判定するのはよろしくありません。

🥳 Good!!

@media (any-hover: hover) {
  .button:hover {
    background-color: red;
  }
}

🤮 Bad…

@media (min-width: 768px) {
  .button:hover {
    background-color: red;
  }
}

prefers-reduced-motion: reduce

メディア特性のprefers-reduced-motionを使うことで、デバイス設定で「視差効果を減らす」が有効かどうかを判定できます。

ユーザーは過度なアニメーションを求めていない場合もあるので、ユーザー側でアニメーションのON/OFFを選択できるように実装してあげることが大切です。

https://embed.zenn.studio/card#zenn-embedded__3fcdfecdf30d7https://embed.zenn.studio/card#zenn-embedded__d9cb79f245811

以下は「視覚効果を減らす」が有効化されている時に、アニメーション時間を極限まで短くする例です。

@media (prefers-reduced-motion: reduce) {
  *,
  ::before,
  ::after {
    transition-duration: 1ms !important;
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
  }
}

Visually Hidden

Visually Hiddenとは、視覚的には要素を非表示にしたいけど、スクリーンリーダーには読み上げてもらいたい時に使うCSSスニペットです。

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

ラジオボタンやチェックボックスのinput要素を非表示にしてスタイリングする際は、display: noneではなくVisually Hiddenを使います。display: noneinput要素自体を消してしまうとフォーカスが当たらないなどの弊害が生じます。

🥳 Good!!

[type="radio"] {
  /* visually-hiddenのスタイル */
}

🤮 Bad…

[type="radio"] {
  display: none;
}

親要素の左右にpaddingが指定されている状態で子要素の横幅を画面幅と同じにするレイアウト手法

親要素の左右にpaddingが指定されている状態で、子要素の幅を画面幅と同じにする場合はcalcvwを使って実装します。

🥳 Good!!

.wrapper {
  padding-left: 40px;
  padding-right: 40px;
}
.photo {
  width: 100vw;
  margin-inline: calc(50% - 50vw);
}

従来の書き方だと、以下のようにpaddingの値に応じて子要素のwidthmarginの値も変わってしまいます。これだと、レスポンシブ時にpaddingの値が変わったらwidthmarginも変える必要がありますが、calcvwを使うことで再定義が不要になります。

🤮 Bad…

.wrapper {
  padding-left: 40px;
  padding-right: 40px;
}
.photo {
  width: calc(100vw + 80px); /* 80px = 左右のpaddingの合計値 */
  margin-left: -40px; /* ネガティブマージンで要素を左に移動させる */
}

コンテンツ幅から片方だけ画面の端まではみ出しているレイアウト手法

このようなレイアウトもcalcvwを使うことで効率よく実装できます。

.片方だけはみ出させる要素(左配置の場合) {
  width: 50vw;
  margin-left: calc((50vw - 50%) * -1);
}
.片方だけはみ出させる要素(右配置の場合) {
  width: 50vw;
  margin-right: -50vw;
}

/* 反対側の要素には`width: 50%`を、これらの親要素には`display: flex`を指定します */

詳しくはCodepenをご覧ください。

メインコンテンツが少ない状態でもフッターを画面最下部に固定させるレイアウト手法

コンテンツ量が少なくてもフッターを画面最下部に固定するレイアウト手法です。

body {
  min-height: 100dvh;
}
footer {
  position: sticky;
  top: 100%;
}

3. JavaScript

JavaScriptも画像と同様にパフォーマンスに影響を与えやすい項目なので、ファイルの読み込み方やスクロール時の処理の実装方法などをまずは覚えることをおすすめします。

Defer

<script>defer属性を付けると非同期でJSファイルがダウンロードされます。また、ダウンロード開始をできるだけ早くしたいので</body>の手前ではなく<head>のできるだけ上のほうで読み込ませます。

🥳 Good!!

🤮 Bad…

  ...
  <script src="script.js">
</body>

DOMContentLoaded

deferでJSを読み込む場合、HTMLが全て読み込まれる前にJSが実行されることがあります。そのため、要素の取得ができずにエラーになることがあるのでDOMContentLoadedイベントの中で処理を実行します。

🥳 Good!!

window.addEventListener('DOMContentLoaded', () => {
  // ここにページ読み込み時の処理を書く
});

🤮 Bad…

(() => {
  // ここにページ読み込み時の処理を書く
})();

Debounce

スクロールイベントやリサイズイベントは実行される頻度が極端に高いので、ブラウザに負荷がかかり画面がカクカクする原因になります。なので、Debounceという手法で実行頻度を減らしてあげます。

debounce関数

function debounce(func, timeout) {
  let timer;
  timeout = timeout !== undefined ? timeout : 300; // funcが呼び出されるまでの遅延時間
  return () => {
    const context = this;
    const args = arguments;
    clearTimeout(timer);
    timer = setTimeout(() => {
      func.apply(context, args);
    }, timeout);
  };
}

以下はリサイズ時にヘッダーを取得する例です。

🥳 Good!!

const getHeader = () => document.querySelector('header');
const debouncedFunction = debounce(getHeader)
window.addEventListener('resize', debouncedFunction, false);

🤮 Bad…

const getHeader = () => document.querySelector('header');
window.addEventListener('resize', getHeader, false);

Intersection Observer

前項でも書いた通り、スクロールイベントは負荷が高いのであまり使いたくありません。Intersection Observerを使うことで、ブラウザに負荷をかけずにスクロールに応じた処理を実装できます。

以下はスクロールアニメーションのサンプルで、data-scroll-anima属性を持つ要素が画面の上下20%の位置までスクロールされたら属性値がtrueになります。

JavaScript

// 監視対象要素
const animaElements = document.querySelectorAll("[data-scroll-anima]");

// 交差時に実行される関数
const doWhenIntersect = entries  => {
  const entriesArray = Array.prototype.slice.call(entries, 0);
  entriesArray.forEach((entry) => {
    if (entry.isIntersecting) {
      entry.target.dataset.scrollAnima = 'true';
    }
  });
}

// IntersectionObserverのオプション
const options = {
  root: null,
  rootMargin: '-20% 0px -20% 0px', // 要素が画面の上下20%を超えたら監視する
  threshold: 0
};

// 対象要素の数だけobserverで監視
const observer = new IntersectionObserver(doWhenIntersect, options);
animaElements.forEach((box) => {
  observer.observe(box);
});

CSS

[data-scroll-anima] {
  opacity: 0;
  transition: opacity .3s;
}
[data-scroll-anima="true"] {
  opacity: 1;
}

matchMedia

ブレイクポイントに応じて処理を実行する場合、画面幅をリサイズイベントで監視すると前項で書いた通りブラウザに負荷がかかるので、代わりにmatchMediaを使って今のブレイクポイントを判定します。

また、CSS変数にブレイクポイントを指定しておくことで、CSS側でブレイクポイントの値が変わってもJS側での修正は不要になります。

CSS

:root {
  --breackpoint-md: 768px;
}

JavaScript

// ブレイクポイントの値をCSS変数から取得して、matchMediaにセット
const rootStyles = getComputedStyle(document.documentElement);
const breackpointMd = rootStyles.getPropertyValue('--breackpoint-md');
const mediaQueryList = window.matchMedia(`(max-width: ${breackpointMd})`);

// ブレイクポイントに応じて実行する処理
const mediaQueryFunction = (event) => {
  if (event.matches) {
    console.log('768px以下です');
  } else {
    console.log('769px以上です');
  }
};

// ブレイクポイントが変わった時のイベントを登録
mediaQueryList.addEventListener('change', mediaQueryFunction);

// ページ読み込み時のイベントを登録
window.addEventListener('DOMContentLoaded', () => mediaQueryFunction(mediaQueryList));

Sassでブレイクポイントの変数を定義している場合、以下のようにCSS変数を登録をすれば上記と同じことができます。

Sassの例

$breackpoint-md: 768px;
:root {
  --breackpoint-md: #{$breackpoint-md};
}

375px未満のレスポンシブ対応

幅320pxのような小さい端末のレスポンシブ対応はCSSで頑張るのではなく、Viewportで表示倍率を縮小します。

昨今のデザインは375pxで作られることが多く、そもそも320px程度まで考慮されていない場合が多いのでCSSで調整するには限界があります。なので、表示倍率を縮小することで実装工数が大幅に削減でき、大量のメディアクエリの記述も発生しなくなります。

const adjustViewport = () => {
  const triggerWidth = 375;
  const viewport = document.querySelector('meta[name="viewport"]');
  const value = window.outerWidth < triggerWidth
    ? `width=${triggerWidth}, target-densitydpi=device-dpi`
    : 'width=device-width, initial-scale=1';
  viewport.setAttribute('content', value);
}
const debouncedFunction = debounce(adjustViewport) // debounce関数は、Debounceの項で解説した関数です
window.addEventListener('resize', debouncedFunction, false);

target-densitydpi=device-dpiは、数年前にAndroidの4系か6系の一部の端末でViewportが正しく変らない事象が起きて、その対処法として付けていました。
昨今の環境でも必要かどうかはちゃんと調べられていないので、これの有無はご自身で判断してください。正直あってもなくても挙動は変らないと思いますが、あることによって特定の環境でも崩れないのであれば付けたままでもいいと私は思っています。

ES6以降の記法

JavaScriptはES6(ES2015)以降、便利な機能や構文が数多く追加されました。ここからはES6以降に追加されたWeb制作寄りの内容を少し紹介していきます。

文字列の結合

テンプレートリテラルを使うことで、変数と文字列の結合が楽になります。

🥳 Good!!

const message = `私は${name}です。`;

🤮 Bad…

const message = '私は' + name + 'です。';

配列操作

配列に関するメソッドはかなり追加されました。新しい配列を生成するmap、特定の配列を探すfind、配列の有無を確認するsomeなど、これまではfor文で行っていた処理をこれらのメソッドを使うことで記述量が圧倒的に短くなります。

// この中からidが2のデータを検索する
const users = [
  { id: 1, name: '山田' },
  { id: 2, name: '田中' },
  { id: 3, name: '中村' }
];

🥳 Good!!

const targetUser = users.find(user => user.id === 2);

🤮 Bad…

let targetUser;
for (let i = 0; i < users.length; i++) {
  if (users[i].id === 2) {
    targetUser = users[i];
    break;
  }
}

スプレッド構文

スプレッド構文を使うことで、配列やオブジェクトの結合や展開が楽になります。

let arr1 = [1, 2, 3];
let arr2 = [4, 5];

🥳 Good!!

// 配列の結合
let combined = [...arr1, ...arr2]; // [1, 2, 3, 4, 5]

// 配列のコピー
let arrCopy = [...arr1]; // [1, 2, 3]

🤮 Bad…

// 配列の結合
let combined = arr1.concat(arr2); // [1, 2, 3, 4, 5]

// 配列のコピー
let arrCopy = arr1.slice(); // [1, 2, 3]

Async / await

特定の処理の後に他の処理を実行する場合は Async / await を使います。setTimeoutで遅延させると、必ずしも遅延させた秒数で手前の処理が終わるとは限らないので絶対に辞めましょう。

🥳 Good!!

// データを取得(取得に時間がかかる)
async function fetchData() {
  const response = await fetch('https://api.example.com/data');
  const data = await response.json();
  return data;
}

async function run() {
  try {
    const data = await fetchData(); // awaitを使うことでfetchData()が完了するまで次の行の処理を待つ
    console.log('取得したデータ:', data);
  } catch (error) {
    console.error('エラーが発生しました:', error.message);
  }
}

run();

🤮 Bad…

function fetchData() {
  // 同様の処理
}

async function run() {
  const data = fetchData(); // fetchData()の完了を待たずに次の行を実行してしまう
  // setTimeoutで処理を遅延させているが、fetchData()の完了が2000ミリ秒以内に終わる保証はないため、dataが空の状態でconsole.logが実行される可能性がある
  setTimeout(() => {
    console.log('取得したデータ:', data);
  }, 2000);
}

run();

Fetch / Axios

APIなど外部からデータを取得する時はFetchAxiosを使います。昔はXMLHttpRequestやjQueryのAjaxを使っていましたが、FetchAxiosのほうが例外処理やデータの扱いに優れています。

🥳 Good!!

fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error(error));

🤮 Bad…

const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data');
xhr.onload = function() {
  if (xhr.status === 200) {
    console.log(xhr.responseText);
  } else {
    console.error('APIエラー');
  }
};
xhr.onerror = function() {
  console.error('ネットワークエラー');
};
xhr.send();

最後に

かなりの量を紹介したので一度に全部を使いこなすのは難しいと思います。個人的にこれだけは…をいくつかピックアップしたので、まずはそれだけでも取り入れてみてください。

  • 1. 画像
    • Lazy loadingで遅延読み込みをして、画像の出し分けはPicture要素を使う
    • 背景画像ではなくImg要素で読み込み、要素いっぱいに広げる時はobject-fit、縦横比を制御するにはaspect-ratioを使う
  • 2. レイアウト
    • 格子状のレイアウトはGrid Layoutを、Flexboxの間隔はgapを使う
    • 状況に応じてcalc()vwを組み合わせてレイアウトを組む
  • 3. JS最適化
    • JSファイルはDeferで読み込み、処理はDOMContentLoadedイベント内で行う
    • スクロールやリサイズのイベントは高負荷なので、Debounceで実行回数を間引く
    • スクロール時の処理はIntersection Observerを使う
タイトルとURLをコピーしました