About Blog Works Contact

Chakra UIの横並び・縦並びしたいとき使うStack(HStack・VStack)・Flexの違いと使い分け

UI・コンポーネントライブラリはWebアプリを作るときに、開発はしたいけどデザインに苦手意識を持っていてなかなか進まない、そんなときに便利な存在ですが、Reactを使う人の中には「Chakra UI」を使っている人も多いんじゃないでしょうか?

ただ、Chakra UIを使い始めた人が気になるであろう点があります。

それが「Stack系の種類の多さ」と「Flexコンポーネントとの違い」です。

今回はそんな似たようなコンポーネントの違いと使い分けについてみていきたいと思います!

そもそもChakra UIって?

Chakra UIはアクセシビリティを重視したコンポーネントライブラリで、2024年4月に改正された障害者差別解消法に対するアクセシビリティ対応に有効とされるライブラリの一つです。

豊富なコンポーネントが用意されていて、レスポンシブ対応も行われている使いやすくなっています。

[

Chakra UI – A simple, modular and accessible component library that gives you the building blocks you need to build your React applications.

Simple, Modular and Accessible UI Components for your React Applications. Built with Styled System

v2.chakra-ui.com

](https://v2.chakra-ui.com/ "Chakra UI – A simple, modular and accessible component library that gives you the building blocks you need to build your React applications.")

Chakra UIにはレイアウトを組むためのコンポーネントがいくつも用意されていて、その中の一つにStackFlexがあります。

Stackとは?

Web制作でCSSを使っている人にはFlexコンポーネントの使い方は説明を見なくてもなんとなくイメージできると思いますが、Stackコンポーネントって聞きなじみがないかもしれません。

Chakra UIの公式ドキュメントにはこんな説明が。

Stack is a layout component used to group elements together and apply a space between them.

スタックは、要素をグループ化し、それらの間にスペースを適用するために使用されるレイアウトコンポーネントです。(DeepL訳)

Chakra UI公式ドキュメント「Stack」コンポーネント

つまり、StackFlexのように横並びや縦並びをさせたいときに使うコンポーネントです。

StackとFlexってどう違うの?

StackFlexも横並びや縦並びをしたいときに使うコンポーネントと説明しましたが、じゃあどう違うん?どう使い分ければええの?って思いますよね?

まずはそれぞれの特徴を見ていきましょう。

Stack(HStack、VStack)

Stackコンポーネントには、StackHStackVStackの3種類が用意されています。

HStackVStackはそれぞれ横並びや縦並びのために特化した、言わばショートカットのようなコンポーネントです。

HStackは子要素を横並びに、StackVStackは子要素を縦並びに配置します。

StackVStackも同じじゃん!と思うかもしれませんが、そこはショートカットたる所以と言いますか、デフォルトでStackは左寄せ、VStackは中央寄せになります。(HStackも横並びのまま、垂直方向に中央寄せとなります。)

Flex

Flexコンポーネントは言わずもがな、デフォルトで横並びになるコンポーネントです。

ただStackと異なるのは、使えるプロパティが違うという点とデフォルトでの子要素間の余白です。

例えば、Stack系では子要素間の余白を指定するspacing属性(CSSでいうgapプロパティ)が使えますが、Flexでは使えません。

逆にFlexではflexレイアウト専用のプロパティのショートハンドが用意されていて、flexDirectionならdirectionflexWrapならwrapといった、属性を省略して指定することができます。

違いがよくわからんのだが?

ここまで説明を見るとそれぞれのコンポーネントの違いがあまりないように感じますね。

それではどう使い分けるといいのでしょうか?

Chakra UIの公式ドキュメントにはこんなことが書いてあります。

The Stack component and the Flex component have their children spaced out evenly but the key difference is that the Stack won’t span the entire width of the container whereas the Flex will. Another thing to note is that the items in both Stack and Flex are aligned in the center by default.


StackコンポーネントとFlexコンポーネントは、子コンポーネントの間隔が均等ですが、重要な違いは、Stackがコンテナの幅いっぱいに配置されるのに対し、Flexはコンテナの幅いっぱいに配置されないことです。もうひとつ注意すべき点は、StackとFlexのアイテムはどちらもデフォルトで中央に配置されていることだ。(DeepL訳)

Chakra UI公式ドキュメント「Stack」コンポーネントより

Flex and Spacer vs Grid vs Stack#

The Flex and Spacer components, HStack treat children of different widths differently.

  • In HStack, the children will have equal spacing between them but they won’t span the entire width of the container.
  • With Flex and Spacer, the children will span the entire width of the container and also have equal spacing between them.

フレックスとスペーサーとグリッドとスタックの比較

FlexとSpacerコンポーネント、GridとHStackでは、異なる幅の子コンポーネントの扱いが異なります。

HStackでは、子コンポーネントの間隔は等しくなりますが、コンテナの幅全体には及びません。
Gridでは、子要素の始点の間隔は等しくなりますが、子要素間のギャップは等しくなりません。
FlexとSpacerを使用すると、子要素はコンテナの幅全体に広がり、子要素間の間隔も等しくなります。(DeepL訳)

Chakra UI公式ドキュメント「Flex」コンポーネントより

引用が長くなってしまいましたが、つまりFlexではSpacerという子要素間の余白を自動調整するコンポーネントと一緒に使うと、子要素が親要素の幅いっぱいまで広がって配置されて、Stackでは幅いっぱいまで広がらない、ということでした。

それ(Spacer)、Stackにやらせてください

が、一つ疑問に浮かぶ方もいるかもしれません。

そう、FlexSpacerをセットで、というのなら、StackSpacerをセットでは使えないのか?というところ。

実際のところ・・・使えます。

Chakra UIの公式ドキュメントにはお試し機能があるので、ぜひ下のコードをコピペして試してみてください。

import { Stack, HStack, Flex, Spacer, Box } from '@chakra-ui/react';

export const App = () => {
  return (
    <Stack spacing={4} px={4} py={16}>
      <HStack spacing='0' h={100} border="1px">
        <Box w='40px' h='40px' bg='yellow.200'>
          1
        </Box>
        <Box w='40px' h='40px' bg='tomato'>
          2
        </Box>
        <Spacer />
        <Box w='40px' h='40px' bg='pink.100'>
          3
        </Box>
      </HStack>
      <Flex border="1px" h={100} align="center">
        <Box w='40px' h='40px' bg='yellow.200'>
          1
        </Box>
        <Box w='40px' h='40px' bg='tomato'>
          2
        </Box>
        <Spacer />
        <Box w='40px' h='40px' bg='pink.100'>
          3
        </Box>
      </Flex>
    </Stack>
  )
}

ttps://play.chakra-ui.com

サンプルコードではHStackFlexを使って横並びにしていますが、全く同じ見た目を再現できており、StackでもしっかりSpacerが使えています。

ただ、HStackにはデフォルトでgapプロパティが付与されるようになっているので、余白なしで配置したい場合には打消しのための属性指定が必要なので少し冗長になってしまいますので、その場合はFlexを使うといいでしょう。

結論

FlexStackの違いについてみてきましたがどうでしょうか?

どの場面でどれを使っても同じように再現はできるので、ここではコレ!という決まりはありません。

ですが、コンポーネントによってスタイルの打消しが必要だったり、重複したりする場合もあるので、コードが煩雑にならないように気を付けて使っていきましょう。

YouTube埋め込みの最適解は?Webデザイナーが知っておくべき「埋め込み方法のベストプラクティス」と「縦型動画(ショート動画)」の導入の技術的ポイント

こんにちは、みみほくろです。

Youtubeって面白い動画が多くて時間を忘れちゃいますよね。

自分の動画もみんなに見てほしい!ってことで自分のサイトにYoutube動画を設置することもあると思います。

ですが、YouTube動画を埋め込む際、単にコードを貼り付けるだけでなく、「ページの表示速度(Lighthouseスコア)」や、近年主流となっている「YouTubeショート(縦長動画)」への対応は、UX(ユーザー体験)に直結する重要な要素なため、適材適所を見極める必要があります。

今回は、主要な3つの実装方法について、それぞれのメリット・デメリットを詳しく解説します。

紹介する実装方法はこの3つです。

  • Youtube発行のIframeをそのまま貼り付け
  • DOM操作による埋め込み
  • Youtube Player API

それでは一つ一つ見ていきましょう!

標準的な埋め込み:「公式Iframe」

YouTubeの共有機能から取得した埋め込みコードをそのままHTMLに配置する、手っ取り早くてスタンダードなやり方です。

赤枠の「共有」ボタンから発行されます。(画像はYoutube公式サイトで使われているYoutube動画のキャプチャ)

メリット:圧倒的な導入スピードと安定性

  • 実装が容易: HTMLを1行貼り付けるだけで完了し、プログラミングの知識がなくても対応可能です。
  • メンテナンス性: YouTube側の仕様変更があっても、公式が提供するコードであるため壊れにくく、保守が容易です。

デメリット:ページパフォーマンスへの悪影響

  • レンダリングブロック: ブラウザはIframe内のリソースを読み込むためにメインスレッドを占有し、ページの初期表示を遅延させます。
  • Lighthouseスコアの低下: 動画が重い場合、LCP(最大視覚コンテンツの表示時間)などのスコアに悪影響が出てしまい、SEO面でのマイナス要因になり得ます。

パフォーマンス重視:「DOM操作」による遅延読み込み

ユーザーが「再生ボタン」を押すまで動画を読み込まない、あるいはCSSや画像などページ全体の読み込みが完了した後にIframeを生成してHTMLに追加する手法です。

メリット:初期表示速度の劇的な改善

  • リクエストの削減: ページロード時にYouTube関連の重いスクリプトや画像(サムネイル以外)を読み込まないため、Lighthouseスコアを高く維持できます。
  • ユーザーファースト: 「動画を見たい」という明確な意思表示(クリック)があって初めて通信が発生するため、モバイルユーザーのデータ通信量節約にも貢献します。

デメリット:ユーザーの手間の増加

  • 2ステップの操作: 「動画読み込みボタンを押す」→「動画の再生ボタンを押す」という2段階の操作が必要になる場合があり、直感的なUXを損なうリスクがあります。
  • JavaScript依存: スクリプトがオフの環境では動画が全く表示されないため、フォールバックの検討が必要です。

実装例:

<button id="createYoutubeButton">動画を読み込む</button>
<div class="youtube-container"></div>

<script>
const youtubeContainer = document.querySelector(".youtube-container");
const createYoutubeButton = document.querySelector("#createYoutubeButton");

createYoutubeButton.addEventListener("click", () => {
    youtubeContainer.innerHTML = `<iframe src="https://www.youtube.com/embed/M7lc1UVf-VE" title="YouTube video player" frameborder="0" allowfullscreen></iframe>`;
});
</script>

読み込んでいるYoutube動画は公式で紹介しているものを使っています。

3. 高度な制御:「YouTube Player API」

JavaScriptでプレーヤーの挙動を詳細にコントロールする、開発者向けの手法です。

メリット:リッチなUXと細かい挙動制御

  • 細かなパラメータ管理: 再生終了時に特定の処理を走らせる、特定の時間から再生を開始する、といった柔軟な制御が可能です。
  • 非同期読み込み: API自体が非同期で読み込まれるため、標準のIframe貼り付けよりもパフォーマンスへの影響を抑えつつ、高度な機能を提供できます。

デメリット:実装コストと複雑性

  • 学習コスト: APIの仕様を理解する必要があり、単純な埋め込みに比べてコード量が増え、デバッグの手間も発生します。
  • 外部スクリプトへの依存: YouTube側のAPIサーバーが万が一ダウンしたり、スクリプトの読み込みに失敗したりした場合、プレーヤーが正しく初期化されないリスクがあります。

実装手順の詳細

YouTube Player APIを導入するための具体的なステップは以下の通りです。

① APIスクリプトの非同期読み込み

まず、APIの本体となるJavaScriptファイルを動的に読み込んでDOM上に組み込みます。

// APIのスクリプトタグを生成
const tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";

// 最初のscriptタグの前に挿入
const firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

② プレーヤーの初期化

APIの読み込みが完了すると、グローバル関数 onYouTubeIframeAPIReady が自動的に実行されます。この中でプレーヤーのインスタンスを生成します。

let player;
function onYouTubeIframeAPIReady() {
    player = new YT.Player('player', { // HTML側のIDを指定
        height: '360',
        width: '640',
        videoId: 'M7lc1UVf-VE',
        playerVars: {
            'playsinline': 1, // iOSでインライン再生を許可
            'rel': 0          // 関連動画を自身のチャンネルに限定
        },
        events: {
            'onReady': onPlayerReady,
            'onStateChange': onPlayerStateChange
        }
    });
}

playerVarsでは動画の状態を設定する「パラメータ」を指定することができます。よくあるパラメータは後述していますので、そちらを参考にするか、Youtube Player APIの公式ページをご覧ください。

[

YouTube Player API Reference for iframe Embeds  |  YouTube IFrame Player API  |  Google for Developers

アプリに YouTube プレーヤーを埋め込みます。

developers.google.com

](https://developers.google.com/youtube/iframe_api_reference?hl=ja "YouTube Player API Reference for iframe Embeds  |  YouTube IFrame Player API  |  Google for Developers")

③ イベントによる制御

動画の準備が整ったときや、再生状態が変わったとき(再生・停止・終了)に独自の処理を挟むことができます。

// プレーヤーの準備が整ったとき
function onPlayerReady(event) {
    // 例:準備ができたら自動でミュートにして再生
    event.target.mute();
    event.target.playVideo();
}

// 状態が変化したとき
function onPlayerStateChange(event) {
    if (event.data == YT.PlayerState.ENDED) {
        console.log("動画の再生が終了しました。");
        // 次の動画へ遷移させる、などの処理が可能
    }
}

挙動を制御する「プレーヤーパラメータ」の活用

Youtube動画にはURLの末尾にパラメータを付与することで、動画の挙動をカスタマイズすることができます。

よく使うパラメータ一覧

パラメータ

内容

autoplay

1

自動再生する。

mute

1

消音状態で再生する(自動再生には必須)。

controls

0 / 1

プレーヤーのコントロールを表示するか。

loop

1

ループ再生する(playlistパラメータとの併用が必要)。

rel

0

再生終了時、同じチャンネル内の関連動画のみを表示する。

start

数字

再生を開始する秒数を指定する。

end

数字

再生を終了する秒数を指定する。

具体的な使用例

① 背景動画として「ループ再生」させる

環境映像などを背景として使う場合、音声を消し、コントロールを隠してループさせます。

loop=1を使う際は、playlistパラメータに同じ動画IDを入れる必要があります。

<iframe src="https://www.youtube.com/embed/VIDEO_ID?autoplay=1&mute=1&loop=1&playlist=VIDEO_ID&controls=0"></iframe>

② 動画の「見どころ」だけを再生する

長い動画の中で、特定の30秒間だけを見せたい場合などに有効です。

<!-- 60秒目から再生開始し、90秒目で終了する -->
<iframe src="https://www.youtube.com/embed/VIDEO_ID?start=60&end=90"></iframe>

実装時の注意点:自動再生のルール

現在のブラウザポリシーにより、autoplay=1 を指定する場合は必ず mute=1 をセットにする必要があります。音声が出る状態での自動再生は基本的にブロックされます。

【重要】通常動画とショート動画のレスポンシブ戦略

YouTubeショート(9:16)を埋め込む際は、通常の動画(16:9)とは異なる設定が必要です。

1. 埋め込み用URLへの変換

Youtubeショートには埋め込み用のコードがデフォルトでは用意されていないので、自分でカスタマイズする必要があります

  • 視聴用URL: https://www.youtube.com/shorts/VIDEO_ID
  • 埋め込み用: https://www.youtube.com/embed/VIDEO_ID

といってもshortsembedに変えるだけです笑

2. デバイスごとの最適なアスペクト比

モダンなCSSプロパティ aspect-ratio を活用します。

/* 動画共通のベース設定 */
.video-wrapper {
    position: relative;
    width: 100%;
    margin: 20px auto;
}

.video-wrapper iframe {
    width: 100%;
    height: 100%;
    border: none;
}

/* 通常動画 (16:9) */
.video-normal {
    aspect-ratio: 16 / 9;
    max-width: 800px;
}

/* ショート動画 (9:16) */
.video-shorts {
    aspect-ratio: 9 / 16;
    width: 100%;
    max-width: 400px;
}

/* PC向けの調整 */
@media screen and (min-width: 768px) {
    .video-shorts {
        max-width: 315px; 
    }
}

通常の動画では16:9、ショートの場合には9:16と切り替えることができます。

まとめ:要件に合わせた手法の選択

さて、ここまでYoutube動画の埋め込み方法をいくつかご紹介しましたが、下記のような使い分けがオススメです。

  • スピード最優先のスポット案件公式Iframe
  • Lighthouseスコアを改善したいサイトDOM操作による後読み
  • 複雑なインタラクションが必要なキャンペーンYouTube Player API
  • ショート動画URL変換とデバイスごとのサイズ管理を徹底する

ちなみにYoutube Player APIには40以上の関数と6つのイベントに20ほどのプロパティーが用意されているので、より細かい調整を行いたい場合にうってつけです。

なので、オススメとしてはYoutube Player APIですが、現場の要件に合わせて、ケースバイケースで使い分けてみてください。

要素をスクロールに合わせて表示・非表示させたり切り替える方法

最近スクロールで追従するヘッダーがスタンダードかってほど見かけるようになりました。

グローバルナビゲーションを常に表示させることでページのどこにいても見たいページにアクセスできて、ユーザーとしては欲しい情報を探しやすくサイト運営者としては回遊性を高めやすい手法だと思います。

でも追従するヘッダーって時にはユーザーの閲覧の邪魔になってしまうんですよね。

常にメインコンテンツの一部をヘッダーで隠していることになって、画面サイズの小さいノートPCやスマートフォンだと余計に見づらくなりやすく、メインコンテンツの魅力が十分に伝えられなかったり、見にくくてUXの悪化につながります。

そこで今回は、下スクロール時は非表示、上スクロール時に表示させるようなスクロールの向きに合わせて要素を変化させる方法をご紹介します。

この記事ではスクロールでヘッダーが上方向に隠れる仕様で説明しています。下左右へ隠れる仕様については応用の章のCodePenをご覧ください。

基本のコード

説明用なのでHTMLはかなりシンプルになっています。

<div class="header">
  スクロールで非表示
</div>

以下はJavaScriptです。

// 現在の位置を保持
let currentPosition = 0;

// ヘッダーの高さを取得
const header = document.querySelector(".header");
const headerHeight = header.clientHeight * -1;

window.addEventListener("scroll", () => {

  // スクロール位置を保持
  let scrollPosition = document.documentElement.scrollTop;

  // スクロールに合わせて要素をヘッダーの高さ分だけ移動(表示域から隠したり表示したり)
  if (currentPosition <= scrollPosition) {
    header.style.transform = "translate(0," + headerHeight + "px)";
  } else if (currentPosition > scrollPosition) {
    header.style.transform = "translate(0, 0)";
  }
  currentPosition = document.documentElement.scrollTop;
})

解説

まず現在の位置を0として定義し、スクロールごとに発生するイベントによってスクロール位置を取得、現在の位置とスクロール位置を比較しています。

現在の位置の数値に対してスクロール位置の数値が大きく(下スクロール)なればヘッダーを非表示に、逆に小さく(上スクロール)なればヘッダーを再表示する仕組みです。

今回はヘッダーの高さを明確に設定していないのでJavaScriptによってヘッダーの高さを動的に取得してCSSスタイルを操作する仕様にしていますが、ヘッダーのサイズが明確ならばクラスの付け外しで表示・非表示を切り替えてもいいかと思います。

応用編

応用では上下左右にスクロールで表示・非表示が切り替わる要素を配置しています。

See the Pen scroll to hide by mimihokuro (@mimi_hokuro) on CodePen.

さていかがでしたでしょうか?

スクロールに合わせて要素の変化させることは使いやすさだけでなく、使いようによってはユーザーの目を引く演出も実装できそうですね。

この記事が参考になれば幸いです~。

JavaScriptを使わず、たった2つのCSSスタイルを追加するだけで、簡単にスライダーを作る方法

記事コンテンツでもECでもブランドページでも、Webサイトには多くの情報を載せてユーザーに少しでも伝えたいことってありますよね。

そこで、限られた範囲に多くの情報を載せられるのがスライダー(カルーセル)です。

スライダーを実装するとき、よく使われるのがSlickやSwiperといったJavaScriptライブラリですが、使わないスタイルやメソッドも多いためサイトの表示速度遅延に影響を及ぼすことも。

今回はJavaScriptを使わずCSSだけでスライダーを作る方法をご紹介します。

この記事ではマウススクロールやフリックによるスクロールで動作するスライダーについて紹介しています。SwiperやSlickのようなボタンによるスワイプについては説明していませんのでご注意ください。

デモ

デモとして一度に表示する画像は1枚、横方向のシンプルなスライダーを用意しました。

See the Pen
Untitled
by mimihokuro (@mimi_hokuro)
on CodePen.

基本のコード

<div class="slider">
  <img src="https://placehold.jp/0aa864/ffffff/600x300.png">
  <img src="https://placehold.jp/0aa864/ffffff/600x300.png">
  <img src="https://placehold.jp/0aa864/ffffff/600x300.png">
</div>

HTMLでは画像にダミー画像生成のplacehold.jpで生成したものを使用したシンプルな構成になっています。

.slider {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  max-width: 400px;
  width: 100%;
  margin: auto;
  scroll-snap-type: x mandatory;
}

img {
  width: 100%;
  scroll-snap-align: center;
}

解説

今回は親要素にflexを指定して横並びに、表示域を幅400pxで制限して、はみ出す分はスクロールするようにしています。

追加スタイルその1:scroll-snap-type

ここで指定しているscroll-snap-typeが今回のポイントの1つです。

ところでスクロールスナップとは?という点ですが、MDNではこのように解説しています。

ユーザーが文書をスクロールする際に、特定の位置にスクロールをスナップさせる

MDN「CSS スクロールスナップの基本概念」より抜粋

ちょっとこれだけだと分かりづらいですが、特定の位置にピタッと移動(スクロール)させる動作、というと分かりやすいでしょうか?

このscroll-snapのおかげでCSSだけを使ってSwiperやSlickに似た動作をさせることができます。

前述しましたが、ここではあくまで、マウススクロールやフリックをしたときのスクロール動作についての話になります。SwiperやSlickのようなボタンクリックでスクロールさせるためにはJavaScriptが別途必要ですのでご注意ください。

サンプルコードのscroll-snap-typeには二つの値が指定されていますが、1つ目の値にはスクロールスナップを適用させる方向、2つ目の値にはスナップの度合いを指定します。

まずxがスクロールの方向になり、x軸に沿ったスクロール方向ということになります。

y軸に適用させたいならyを指定する、ということですね。

2つ目の値についてですが、mandatoryproximityのどちらかを指定することができます。

mandatoryはスナップをSwiperやSlickのような軽い操作感で演出してくれますが、それ故に簡単にスナップしてしまうので、ユーザーが見たい部分が見れずにスナップしてしまうなど、逆にユーザーをイラつかせてしまう可能性もあるので使用には慎重になる必要があります。

proximityは、スナップ地点が近くなるまでスクロールするとスナップされるため、mandatoryに比べて軽い操作感というわけにはいきませんがコンテンツを見逃してしまう可能性は低くなります。

と、つらつらと言葉を並べてきましたが言ってることが分かりづらいかと思いますので、百聞は一見に如かず、こちらのデモで使用感を見てください。

See the Pen
Difference between 〇 and △
by mimihokuro (@mimi_hokuro)
on CodePen.

追加スタイルその2:scroll-snap-align

scroll-snap-alignはどの位置をスナップの基準にするか指定できます。

startはスナップする範囲の先頭に、centerは中央、endは末尾にピタッと移動します。

こちらもデモを用意しました。

スナップ位置が分かりやすいよう、親要素よりも子要素を大きくはみ出した状態に設定しています。

See the Pen
scroll-snap-align
by mimihokuro (@mimi_hokuro)
on CodePen.

子要素の表示域を親要素に合わせるならどの値を指定しても問題ありません。

さていかがでしたでしょうか?

CSSにたった2つのスタイルを追加するだけでスライダーがJavaScriptなしで実装できてしまうなんて神かよ、と思いましたね。

この記事が参考になれば幸いです~。

空要素に:emptyでスタイルを指定したのになぜか効かない・・・。:emptyの意外な落とし穴

テキストも画像も入っていない空のHTML要素ってありますよね。

特にPHPのような動的に生成したHTMLでは、場合によって中身が空の要素が出てくることが多いと思います。

しかし、ムダな余白を作ったり親要素でFlexGridなどを指定していると意図しないレイアウト崩れを引き起こすこともしばしば。

そんな子要素を持たない空要素を非表示にしたり特定のスタイルを指定したいときに便利なのが:emptyという疑似クラスです。

:emptyとは?どんなときに使う?

前述した通り、親要素でFlexやGridなどを指定してレイアウトを組んでいる場合、子要素の一つが空だとムダな余白ができてしまいます。

例えばこんなスタイルを指定しているとします。

<div class="flex">
  <div class="child">子要素1</div>
  <div class="child">子要素2</div>
  <div class="child"></div> <!-- 子要素3は空 -->
  <div class="child">子要素4</div>
  <div class="child">子要素5</div>
</div>
.flex {
  display: flex;
  gap: 40px;
}

.flex div {
  padding: 16px;
  background-color: rgb(100,150,200);
  color: #ffffff;
}

flexを付与した親要素に横並びになった5つの子要素が含まれていますが、その内1つがテキストもなにもない空要素です。

この場合、空要素はどうなるでしょうか?

無視されて子要素が4つ横並びになるでしょうか?

残念ながら5つの子要素が横並びになり、空要素は背景色と余白を持って表示されてしまいます。

各子要素にpaddingを指定しているため、空要素にも余白を含んでしまう

ここで:emptyの出番です。

上記のコードに以下のスタイルを追加することで空要素は非表示になりムダな余白が生まれません。

.flex div:empty { // 空のdiv要素を指定
  display: none;
}

:emptyの落とし穴

ここからが今回の本題です。

見た目で空要素だから:empty使えばいいや!というのはちょっと待ってください。

以下の例を見てください。

<div class="flex">
  <div>子要素1</div>
  <div>子要素2</div>
  <div> </div> <!-- 子要素3は空? -->
  <div>
</div> <!-- 子要素4も空? -->
  <div>子要素5</div>
</div>

子要素に:emptyを指定するとどうなるでしょうか?

この場合子要素はすべて表示されてしまいます

実は:emptyは中身が完全に空の状態でしかスタイルを反映させることができません。

上記のコードの場合、子要素3は「空白」という要素を持っているため空要素とみなされません。

子要素4の改行も同様です。空白はなくとも「改行」という要素を持つことになり、空要素判定されず:emptyが効かなくなるのです。

CMSやASPなどで起こる問題

直接HTMLを編集して空白を削除すれば簡単なのですが、WordPressなどのCMSやfutureshopなどといったSaasやASPでは、動的にHTMLを生成されるため、場合によっては制御できずどうしても空白や改行が含まれてしまうことがあります。

そうなるとそのままでは:emptyが使えないので別の方法をとる必要がありますが、残念ながら今のところ:emptyに代わるものはなく、JavaScriptでクラスを付与するか直接DOMを操作するしかありません。

:emptyの代替として、空白を含む空要素に適応できる:-moz-only-whitespaceという疑似クラスが存在しますがFirefoxでしか適用されません。
W3CのSelectors Level 4では:empty:-moz-only-whitespaceと同等の動作をするように変更されたということですが、まだ対応するブラウザはありません。今後に期待です。

JavaScriptで空要素にクラスを付与、または要素を直接操作する

空白や改行がある空要素へのJavaScriptによる対処方法は、クラスを対象の要素に付与するか、直接要素を操作する方法になります。

クラスを付与する方法

クラスを付与して空要素を非表示にしたい場合、display:none;を設定したクラスをJavaScriptによって付与します。

上記のサンプルコードの場合は以下のような記述になります。

// 要素を非表示にするemptyというクラス
.flex div.empty {
  display: none;
}
const els = document.querySelectorAll(".flex > div");

els.forEach((el) => {
  if (el.innerHTML === null || !el.innerHTML.match(/\S/g)) {
    el.classList.add("empty");
  }
})

flexクラスが付与されているdivの子要素を一つずつ条件分岐にかけています。

上記の場合、子要素の中身が完全に空(=null)、もしくは空白以外の文字列を含まないときにemptyクラスを付与して非表示にしています。

直接要素を操作する場合

クラスを付与する方法と流れは同じですが、空白や改行のみ含む子要素だけを操作しています。

const els = document.querySelectorAll(".flex > div");

els.forEach((el) => {
  if (el.innerHTML === null || !el.innerHTML.match(/\S/g)) {
    el.remove(); // 条件に当てはまる子要素だけを削除
  }
})

最後に

いかがでしょうか?

:emptyは完全な空要素には有効ですが空白や改行が一つでも含まれると効かなくなってしまうのがネックです。

直接HTMLを操作できるなら:emptyを、そうでなければJavaScriptを使って非表示用クラスを付与したり直接操作したりするなど、管理しやすいやり方で使い分けていきましょう。

モーダルウインドウの実装方法4選!それぞれのメリットとデメリットもご紹介

今回実装方法としてご紹介するのは以下の4つです。

  • 素のJavaScript(Vanilla-JS)
  • jQuery
  • Input + Label(HTML)
  • Dialog(HTML+JavaScript)

一つ一つ見ていきましょう!

素のJavaScript(Vanilla-JS)

JavaScriptメインで一番スタンダードな、初心者の方にも取っつきやすいやり方かなと思います。

See the Pen
Modal (Vanilla-JS)
by mimihokuro (@mimi_hokuro)
on CodePen.

デモでは、OPENボタンを押すことで非表示になっているモーダル部分にopacity: 1visibility: visibileを設定したクラスを付与することで表示するようスタイルを上書きしています。

素のJavaScriptによるメリット

メリットとしては実装が簡単な点が挙げられます。JavaScriptを勉強し始めの方も目にするメソッドを使用しているので、初心者の方にはオススメです。

素のJavaScriptによるデメリット

デメリットとしては、後述のDialogタグを使用するよりも冗長で分かりにくいという点があります。

また、ECカートシステムなどで注文手続き画面といった個人情報を入力するページではWebスキミング対策としてJavaScriptの使用を禁止していることがあります。モーダルを使ってユーザーにもっとアピールしたい!となっても使えないというデメリットがあります。

スタイルの設定においても、opacityやvisibilityといった冗長的になりやすく、記述量が増えてしまうといったところもデメリットでしょうか。

jQuery

See the Pen
Modal (JQuery)
by mimihokuro (@mimi_hokuro)
on CodePen.

JavaScriptのライブラリであるjQueryによる実装も初心者の方には簡単な方法となります。

jQueryのメリット

メリット・デメリットは素のJavaScriptに加えて、fadeInfadeOutといったメソッドがjQueryによってすでに用意されているので、CSS・JavaScriptの記述量を大幅に減らすことができてコードがスッキリ、かつ分かりやすい点が大きなメリットとなります。

jQueryのデメリット

大きなデメリットとして、jQueryはライブラリを構成するプログラムを一式読み込む必要があるため、それがないと動かないということと、それ自体の容量が膨大で使用したいメソッド以外のものまで読み込んでしまうためページ速度に影響が出てしまうという点が挙げられます。

また、ReactやVueといった軽量でモダンなフレームワークの普及によって、近年のフロントエンド開発に採用されなくなっているという観点から脱jQueryが進んでいます。とはいっても、未だにjQueryを利用しているところも多いようなので、こんな感じでできるよ程度で覚えてもらうのがいいと思います。

Inputタグ+Lableタグ

inputタグとlabelタグを利用したモーダル実装です。モーダルだけでなくハンバーガーメニューのようなボタンクリックで開くUIには汎用的に使える方法になります。

See the Pen
Modal(Input+label)
by mimihokuro (@mimi_hokuro)
on CodePen.

Input + Lableのメリット

最大のメリットはJavaScriptを使用しないというところです。HTMLタグとCSSのみ使用することでページ速度の向上が見込まれます。また、前述したようなJavaScriptが使えない特殊な環境でも対応できるため、環境依存に強い点もメリットの一つです。

Input + Lableのデメリット

デメリットとしては少しテクニカルな方法なので、初心者の方には取っつきにくい方法なのかなと思います。

可読性も他の3つの方法より下がるため、JavaScriptを使えない、もしくは理由があってどうしても使いたくないという場合を除いて使用する優先度は低くなります。

Dialogタグ

他の3つの方法に比べて最近使えるようになったHTMLタグです。JavaScriptと組み合わせて使いますが、より直感的で可読性も高い方法、かつ記述量が少ないためオススメです。

See the Pen
Modal (Dialog)
by mimihokuro (@mimi_hokuro)
on CodePen.

Dialogタグのメリット

dialogタグを使うことでpositionプロパティによる配置をすることなく自動で中央に配置され、記述量が減らせて可読性も上がります。

Dialogタグのデメリット

こちらもJavaScriptを使用しているのでJavaScriptが使えない環境では採用できません。記述も少しクセがありますが、モダンなWebデザインにおいては使い勝手の良い方法です。

最後に

いかがでしょうか?今モーダルを実装したい環境にどんな方法を取るのがいいのか、悩まれていたのなら参考になれば幸いです。