About Blog Works Contact

CSSによる「部分一致」でスタイルを一括適用させる方法&効かないときのポイント

CSSで複数のクラスやIDに同じスタイルを適用させるとき、どうしてますか?

.class-name1, .class-name2, #id1 { // 適用したいスタイル }

こんな感じが多いと思います。
でも適用したいクラスやIDを羅列するのはコードが見にくくなるし、何よりめんどくさい・・・ですよね?
そんなときに便利なのが部分一致!
今回は一括でスタイルを適用させられて、かつコードがスッキリする便利な「CSSによる部分一致」のお話です。

部分一致の基本

前方一致

前方一致は、要素のクラス名やIDの先頭が指定の文字列で始まっている場合に使えて、^=という書き方をします。

[id^="section-"] { // 適用したいスタイル }

例えば、[id^="section-"]とすることで、ID名がsection-で始まる全ての要素にスタイルを適用させることができます。

後方一致

後方一致は、要素のクラス名やIDの末尾が指定の文字列で終わっている場合に使えて、$=という書き方をします。

[class$="-item"] { // 適用したいスタイル }

例えば、[class$="-item"]とすることで、クラス名が-itemで終わる全ての要素にスタイルを適用させることができます。

部分一致

部分一致は、要素のクラス名やIDに指定の文字列含まれている場合に使えて、*=という書き方をします。

a[href*="/file/"] { // 適用したいスタイル }

a[href*="/file/"]とすることで、aタグのhref属性で/file/を含む要素にスタイルを適用させることができます。

この属性セレクタが一番柔軟にスタイルを適用させやすい反面、柔軟すぎるがゆえにスタイルを適用させたくない要素にも反映されてしまうリスクが高くなるデメリットがあります。
例えば、[class*="item"]としたとき、

  • class="item"
  • class="item-list"
  • class="main-item"

といった、意味合いが違う要素にも同じスタイルが反映されてしまいますので注意が必要です。

部分一致(単語一致)

こちらも部分一致ですが、先ほどの部分一致よりも厳格なチェックが行われ、要素のクラス名やIDに指定の“単語”が含まれている場合に使えて、~=という書き方をします。

[class~="logo"] { // 適用したいスタイル }

「*=」と「~=」の違いは?

*=と違いがよく分からないと思いますので、下の例をご覧ください。

<!-- 「~=」が適用される例 -->
<a class="logo">Logo</a>
<a class="header logo footer">Logo</a>

<!-- 「~=」が適用されない例 -->
<a class="logotype">Logotype</a>
<a class="logomark">Logo</a>

適用されるものとされないもので何が違うかをこの例でいうと、logoという単独の単語であるかどうか、ということです。
つまり、logotypeだとlogotypeという単語として認識されてしまい、logoという単語とは一致しないということになってしまいます。

言ってしまえば、*=は指定の文字列を含む部分一致、~=はほぼ完全一致になります。

部分一致セレクタ早見表

つらつらと書いてしまいましたので、パッと見で知りたい方は早見表をどうぞ!

セレクタ記述例対象のa文字列適用されるタグ
^=section[id^="section-"]section-section
$=li[class$="-item"]-itemli
*=a[href*="/file/"]/file/a
~=img[class~="logo"]logoimg

便利だけどこんな落とし穴も

部分一致は柔軟で使い勝手のいい方法ですが、使う前にいくつか考慮する点があります。

前方一致と後方一致は属性値全体でチェックされる

「どういうこと?」と思うところですが、次の例をご覧ください。

<style>
  [class^="content"] {
    color: red;
  }
</style>
<h2 class="title content-main">タイトル</h2>
<div class="meta content-sub">カテゴリー</div>
<div class="content">この記事のコンテンツがここに入ります。</div>

3つすべてのdivタグにcontentから始まるクラス名が含まれているので、すべて文字色が赤になると思われますが、実は1つしかスタイルは反映されません。
content~ってクラス名と前方一致してるじゃないか!」と思いますが、タグに設定された属性値全体と前方一致しているかどうかでチェックされます。
つまり、[class^="content"]だったらclass="content~"で始まらないと一致したとみなさないよ、ということです。
「前方一致/後方一致でスタイルを指定してるのに、なぜか効かない💦」ということも・・・。
どのようになるか、実際の表示をご覧ください。

See the Pen Example of Prefix Match by CSS by mimihokuro (@mimi_hokuro) on CodePen.

上記の例ですべてにスタイルを当てたい場合は部分一致(*=)で指定するか、属性値の並び順を変える必要があるので、使う場面には見極めが必要です。

パフォーマンスの問題

部分一致セレクタは非常に便利なのですが、コード量の多い大規模なDOM構造に対して使用する場合は、パフォーマンスにも注意が必要です。
特に*=セレクタは、すべての要素をチェックするため、処理が重くなることがあります。
例えば、DOMが100個でCSSルールが50個あった場合、100×50=5,000回のマッチング処理が行われることになります😱

優先度の問題

部分一致セレクタは、他のセレクタと組み合わせて使用することができますが、優先度に注意が必要です。
優先度が高いセレクタがある場合、部分一致セレクタが期待通りに機能しないことがあります。
優先度は、IDセレクタが最も高く、次にクラスセレクタ、そして最後にタグセレクタの順で優先されます。

さいごに

いかがでしたでしょうか??
部分一致はちょっとしたときに便利なんですが、ビシッと指定しないと「こんなとこに何でこのスタイルが!?」ということも十分にあり得るので、部分一致は最終手段程度に考えてもらえるといいかな思います👍

Posted in CSS

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の違いについてみてきましたがどうでしょうか?

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

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

HTML+CSSだけでマスク(切り抜き)したいときに参考にしたいテクニック3選

Webデザインをする上でやりたくなるテクニックの一つに切り抜き(マスク)があります。

文字や好きなシェイプの形で背景を切り抜いたりすると、華やかさというかなんかオシャレに見えるんですよね。

今回は色んな切り抜きのテクニックをご紹介します。

テキストの色にグラデーションをかける

テキストは通常では単色でしか指定できず、グラデーションがかけられません。

でもCSSでプロパティーをちょちょいと加えるだけで簡単にグラデーションがかけられるようになります。

<div class="gradation bg-clip">
  Lorem ipsum dolor sit, amet consectetur adipisicing elit.
</div>
.gradation {
  // 好きな色でグラデーション
  background: linear-gradient(30deg, #090979, #090979, #00d4ff, #090979, #090979);
}

.bg-clip {
  background-clip: text;
  color: transparent;
}

これ一つでもサイトの印象がガラリと変わりますね。

解説

テキストにグラデーションをかけるテクニックは、テキスト自体の色をグラデーションに変えているわけではなく、背景にグラデーションをかけてそれをテキストの形で切り抜き(マスク)をしています。

仕組みとしてはbackground-clip: text;で背景を前面にあるテキストの形で切り抜きをして、color: transparent;でテキストを透明にして背景の色を見えるようにしている、というカタチです。

ちょっと応用

グラデーションが動いてキラキラしてたらもっと目を引く演出になりそうじゃないですか?

ということで背景のグラデーションを動かしてみます。

.move {
  background-size: 300% 100%;
  background-position: 50% 50%;
  animation: move-background 1.5s infinite ease-in-out;

  @keyframes move-background {
    0% {
      background-position: 0% 50%;
    }
    100% {
      background-position: 100% 50%;
    }
  }
}

背景のサイズを大きくしてアニメーションで左から右へ移動させるようにしています。

より光沢のような高級感が出せていると思います。

実際の動きはこちらで見てみてください。

See the Pen background-clip: text; by mimihokuro (@mimi_hokuro) on CodePen.

SVGで切り抜き

次はSVG画像の形に切り抜く方法をご紹介します。

まずは実際のデモをご覧ください。

See the Pen clip-path by mimihokuro (@mimi_hokuro) on CodePen.

これの何がいいかって、「SVG」で切り抜いているところですよね。

SVGはベクター形式なので拡大縮小に強いのでキレイに切り抜けるところがいいところです。

スクロール量に合わせて拡大して次のコンテンツを見せる、みたいな使い方もできるので使いこなせれば表現の幅もグッと広がりますね。

SVGでの切り抜き方法には主に2つのパターンがあります。

  • clip-pathプロパティー+clipPathタグを使った方法
  • mask系プロパティー+maskタグを使った方法

clip-pathプロパティー+clipPathタグを使った方法

<div class="clip-path">
  <img src="https://picsum.photos/seed/picsum/300/300" alt="">
  <svg viewBox="0 0 512 512" width="0" height="0">
    <clipPath id="svg" clipPathUnits="objectBoundingBox">
      <path d="M378.409,0H208.294h-13.176l-9.314,9.315L57.017,138.101l-9.314,9.315v13.176v265.513   c0,47.36,38.528,85.896,85.895,85.896h244.811c47.361,0,85.888-38.535,85.888-85.896V85.896C464.297,38.528,425.77,0,378.409,0z M432.493,426.104c0,29.877-24.214,54.091-54.084,54.091H133.598c-29.877,0-54.091-24.214-54.091-54.091V160.592h83.717 c24.884,0,45.07-20.179,45.07-45.071V31.804h170.114c29.87,0,54.084,24.214,54.084,54.091V426.104z" />
      <path d="M180.296,296.668l-4.846-0.67c-10.63-1.487-14.265-4.978-14.265-10.104c0-5.78,4.309-9.817,12.383-9.817   c5.653,0,11.305,1.62,15.745,3.764c1.886,0.942,3.903,1.487,5.789,1.487c4.845,0,8.612-3.63,8.612-8.616   c0-3.226-1.481-5.921-4.71-7.939c-5.384-3.372-15.476-6.06-25.572-6.06c-19.781,0-32.436,11.171-32.436,27.998   c0,16.15,10.232,24.898,28.938,27.454l4.846,0.67c10.903,1.48,14.129,4.846,14.129,10.229c0,6.326-5.247,10.766-14.939,10.766 c-6.727,0-12.111-1.745-19.645-5.921c-1.616-0.942-3.634-1.62-5.788-1.62c-5.115,0-8.885,3.91-8.885,8.756 c0,3.226,1.616,6.326,4.713,8.344c6.054,3.764,15.878,7.8,28.798,7.8c23.823,0,35.934-12.24,35.934-28.795   C209.097,307.84,199.273,299.356,180.296,296.668z" />
      <path d="M281.108,259.382c-4.577,0-7.939,2.43-9.556,7.674l-16.69,54.51h-0.402l-17.634-54.51   c-1.745-5.244-4.978-7.674-9.551-7.674c-5.653,0-9.692,4.176-9.692,9.287c0,1.347,0.269,2.834,0.67,4.175l23.286,68.104   c2.96,8.477,6.727,11.57,12.652,11.57c5.785,0,9.555-3.093,12.516-11.57l23.282-68.104c0.406-1.341,0.674-2.828,0.674-4.175   C290.664,263.558,286.76,259.382,281.108,259.382z" />
      <path d="M364.556,300.836h-18.841c-5.114,0-8.344,3.1-8.344,7.806c0,4.713,3.23,7.814,8.344,7.814h6.193   c0.538,0,0.803,0.258,0.803,0.803c0,3.505-0.265,6.598-1.075,9.014c-1.882,5.796-7.67,9.426-14.669,9.426   c-7.943,0-12.921-3.903-14.939-10.096c-1.075-3.365-1.48-7.8-1.48-19.648c0-11.842,0.405-16.15,1.48-19.516   c2.018-6.325,6.867-10.228,14.67-10.228c5.924,0,10.362,1.885,13.859,6.724c2.695,3.777,5.387,4.852,8.749,4.852   c4.981,0,9.021-3.638,9.021-8.888c0-2.151-0.674-4.035-1.752-5.921c-4.842-8.204-15.071-14.264-29.877-14.264   c-16.287,0-28.935,7.408-33.644,22.204c-2.022,6.466-2.559,11.576-2.559,25.038c0,13.454,0.538,18.573,2.559,25.031   c4.709,14.802,17.357,22.204,33.644,22.204c16.286,0,28.668-8.204,33.374-22.881c1.617-5.111,2.29-12.645,2.29-20.716v-0.95   C372.362,303.664,369.538,300.836,364.556,300.836z" />
    </clipPath>
  </svg>
</div>
.clip-path {
  width: 300px;
  height: 300px;
  clip-path: url(#svg);

  img {
    width: 100%;
    height: 100%;
  }

  clipPath {
    transform: scale(0.00195);
  }
}

解説

まずHTMLの方では、imgタグで指定した画像が切り抜かれたときの背景となり、svgタグ内で指定したパスで実際に切り抜きを行うんですが、ポイントは2つあります。

  • svgパスをclipPathタグで囲う
  • clipPathタグにclipPathUnits属性を指定する

clipPathタグ自体は、囲ったパスを切り抜きに使うことを定義するためのもので、clipPathUnits属性は描画する範囲を指定しています。今回はobjectBoundingBoxを指定していますが、この場合はclipPathで囲うパスのサイズに合わせて描画します。

ここで注意なのがパスのサイズ

そのままだと元々のSVG画像のサイズで切り抜きしようとしてしまうので、元のサイズがどデカいとデカすぎて切り抜きされてるのにされてないと錯覚してしまうこともしばしば。

そのため、clipPathタグに対してtransform: scale; で縮小している、というわけです。

ここで指定している値は決まったものではなく、SVG画像のviewboxのサイズによって変動します。

値の出し方としては、1 ÷ viewboxの値という式になり、デモで使っているSVG画像のサイズの場合、1 ÷ 512 ≒ 0.00195で指定することでちょうどいいサイズになります。

≒としたのは、実際計算すると0.001953125となります。viewboxサイズによってはもっと細かい数値になることもあるかもしれず、正確に指定してもいいですが、省略した場合との差は微々たるものなのでここでは省略しています。正確に指定するかある程度で省略するかは、チームや会社の方針などに従って決めてください。

mask系プロパティー+maskタグを使った方法

<svg class="mask" viewBox="0 0 512 512" x="0" y="0">
  <image href="https://picsum.photos/seed/picsum/300/300"></image>
  <mask id="customMask">
    <path d="M378.409,0H208.294h-13.176l-9.314,9.315L57.017,138.101l-9.314,9.315v13.176v265.513   c0,47.36,38.528,85.896,85.895,85.896h244.811c47.361,0,85.888-38.535,85.888-85.896V85.896C464.297,38.528,425.77,0,378.409,0z M432.493,426.104c0,29.877-24.214,54.091-54.084,54.091H133.598c-29.877,0-54.091-24.214-54.091-54.091V160.592h83.717 c24.884,0,45.07-20.179,45.07-45.071V31.804h170.114c29.87,0,54.084,24.214,54.084,54.091V426.104z" />
    <path d="M180.296,296.668l-4.846-0.67c-10.63-1.487-14.265-4.978-14.265-10.104c0-5.78,4.309-9.817,12.383-9.817   c5.653,0,11.305,1.62,15.745,3.764c1.886,0.942,3.903,1.487,5.789,1.487c4.845,0,8.612-3.63,8.612-8.616   c0-3.226-1.481-5.921-4.71-7.939c-5.384-3.372-15.476-6.06-25.572-6.06c-19.781,0-32.436,11.171-32.436,27.998   c0,16.15,10.232,24.898,28.938,27.454l4.846,0.67c10.903,1.48,14.129,4.846,14.129,10.229c0,6.326-5.247,10.766-14.939,10.766 c-6.727,0-12.111-1.745-19.645-5.921c-1.616-0.942-3.634-1.62-5.788-1.62c-5.115,0-8.885,3.91-8.885,8.756 c0,3.226,1.616,6.326,4.713,8.344c6.054,3.764,15.878,7.8,28.798,7.8c23.823,0,35.934-12.24,35.934-28.795   C209.097,307.84,199.273,299.356,180.296,296.668z" />
    <path d="M281.108,259.382c-4.577,0-7.939,2.43-9.556,7.674l-16.69,54.51h-0.402l-17.634-54.51   c-1.745-5.244-4.978-7.674-9.551-7.674c-5.653,0-9.692,4.176-9.692,9.287c0,1.347,0.269,2.834,0.67,4.175l23.286,68.104   c2.96,8.477,6.727,11.57,12.652,11.57c5.785,0,9.555-3.093,12.516-11.57l23.282-68.104c0.406-1.341,0.674-2.828,0.674-4.175   C290.664,263.558,286.76,259.382,281.108,259.382z" />
    <path d="M364.556,300.836h-18.841c-5.114,0-8.344,3.1-8.344,7.806c0,4.713,3.23,7.814,8.344,7.814h6.193   c0.538,0,0.803,0.258,0.803,0.803c0,3.505-0.265,6.598-1.075,9.014c-1.882,5.796-7.67,9.426-14.669,9.426   c-7.943,0-12.921-3.903-14.939-10.096c-1.075-3.365-1.48-7.8-1.48-19.648c0-11.842,0.405-16.15,1.48-19.516   c2.018-6.325,6.867-10.228,14.67-10.228c5.924,0,10.362,1.885,13.859,6.724c2.695,3.777,5.387,4.852,8.749,4.852   c4.981,0,9.021-3.638,9.021-8.888c0-2.151-0.674-4.035-1.752-5.921c-4.842-8.204-15.071-14.264-29.877-14.264   c-16.287,0-28.935,7.408-33.644,22.204c-2.022,6.466-2.559,11.576-2.559,25.038c0,13.454,0.538,18.573,2.559,25.031   c4.709,14.802,17.357,22.204,33.644,22.204c16.286,0,28.668-8.204,33.374-22.881c1.617-5.111,2.29-12.645,2.29-20.716v-0.95   C372.362,303.664,369.538,300.836,364.556,300.836z" />
  </mask>
</svg>
.mask {
  width: 300px;
  height: 300px;

  image {
    width: 100%;
    mask-image: url(#customMask);
    mask-size: cover;
    mask-position: center;
  }

  path {
    fill: #ffffff;
  }
}

解説

簡単に言うと、imageタグで背景としたい画像を設定、maskタグで切り抜きしたいSVGパスを指定します。

CSSではmask-imageプロパティで切り抜きしたい対象のSVG画像を指定、mask-sizemask-positionでサイズと位置を指定しています。

ここではSVGのパスにfillstrokeを指定しないと切り抜きしてくれないので忘れずに指定しましょう。

ポイント

ここでのポイントとしては、画像指定に使うタグはimgタグではなく、imageタグであるというところ。

imageタグはSVGにだけ使える特殊なタグで、今回のmaskプロパティーとSVGを使った切り抜きの場合には、svgタグの配下に入れないと機能しません。

imageタグについての詳細はMDNをご参照ください。

[

– SVG | MDN

The は SVG の要素で、 SVG 文書内に画像を含めます。これはラスター画像ファイルや他の SVG ファイルを表示することができます。

developer.mozilla.org

](https://developer.mozilla.org/ja/docs/Web/SVG/Element/image " – SVG | MDN")

画像を縦横サイズ関係なく任意の比率で切り抜き

お次は画像を引き延ばしせずに、縦長・横長・正方形など好きな比率で切り抜くテクニックです。

これまたどういうこっちゃという方に、デモご用意しました。

See the Pen object-fit by mimihokuro (@mimi_hokuro) on CodePen.

すべて同じ画像を使っていて、真ん中と右の画像ではCSSで一部だけ切り抜いています。

コードの抜粋はこちら

<div class="object-fit">
  <img src="https://picsum.photos/id/20/500/300">
</div>

<div class="object-view-box">
  <img src="https://picsum.photos/id/20/500/300">
</div>
.object-fit img {
  width: 200px;
  height: 300px;
  object-fit: cover;
  object-position: 30% 10%;
}

.object-view-box img {
  object-view-box: inset(100px 50px 30px 50px);
}

解説

ポイントとしては、切り抜きたい画像にobject-fitプロパティーとサイズを指定するだけです。

コードにはobject-positionプロパティーも指定されていますが、こちらは表示したい位置指定のものなので、必要に応じて使いましょう。

object-view-boxプロパティーでは元のサイズの上下左右からどれだけの範囲を表示するかを指定しています。

こんなシーンで

レスポンシブ対応で、画像の表示方法についてよくある問題として挙がります。PCとスマホでは画面比率が違うので、PCで表示していた画像をそのままスマホで表示すると小さくなりすぎて見づらかったり、はたまたスマホでは同じ画像の一部だけ表示したいから複数画像を用意するほどでもない、など考えなければならないことがあります。

object-fitでは指定のサイズで切り抜けるだけでなく、画面幅に合わせることもできるので煩わしい設定をする必要がなくなります。

こちらのデモを、スマホかデベロッパーツールでスマホモード、もしくはブラウザサイズの横幅を縮めてみてください。

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

縦幅は画面いっぱいのまま、画像のサイド部分が切り抜かれています。

サイトのファーストビューやインパクトを出したい箇所にはうってつけの方法だと思います。

最後に

いかがでしょうか?

マスク(切り抜き)は奥が深く、なかなかに複雑ですが、使いこなせれば表現の幅はグッと広がります。

今回の記事がその助けになれば幸いです。

シンプル、だけどレスポンシブな日本語テキストの改行テクニック

改行の入る箇所によって、キャッチコピーとか言葉の伝わりやすさや印象というのは変わってきます。

でもWebサイトでは、日本語というのは英語よりも改行が入れにくいものです。

というのも単語ごとにスペースで区切る英語と違って、日本語は単語ごとにスペースで区切ることがないので、基本的にブラウザは区切る箇所がわからないので単語の途中だろうが問答無用で改行することがよくあります。

今回はレスポンシブで任意の箇所で改行させるためのテクニックをご紹介します。

まずはデモで

See the Pen
line-breaks-in-japanese-text
by mimihokuro (@mimi_hokuro)
on CodePen.

それぞれのコンテンツ幅は拡大縮小できるので、テキストがはみ出る具合や改行のタイミングなど試してみてください。

それでは解説です。

シンプルな【display: inline-block】を使った改行

私の感覚ではよく使われている印象を受ける方法ですが、display: inline-block;spanを組み合わせて改行させる方法です。

<p>
  <span>誰だって、</span><span>ほんとうにいいことをしたら、</span><span>いちばん幸せなんだねぇ。</span>
</p>
span {
  display: inline-block;
}

文章は「銀河鉄道の夜」より一部抜粋

この方法は今回ご紹介する中でもシンプルで直感的に使えるので使いやすいかと思います。

でも見ての通り改行するブロックごとにspanでいくつも囲ってブロック分けするのでコードが煩雑になりがちなので見づらくなるのがネックです。

句読点で改行する【word-break: keep-all】

次に句読点を改行ポイントとして自動で認識して改行してくれる方法です。

<p class="word-break">誰だって、ほんとうにいいことをしたら、いちばん幸せなんだねぇ。</p>
.word-break {
    word-break: keep-all;
}

句読点の後に改行が自動で入る

ムダにタグが増えないし句読点で改行してくれるならこれでいいんじゃない?と思うかもしれませんが、一つ問題があって、テキストが要素よりも長い場合は改行されずに要素を突き抜けて表示されちゃうんです。

改行されずにテキストが要素からはみ出てしまう

幅に合わせて改行する【wbr】

word-break: keep-all;にHTMLで改行タグであるwbrを組み合わせることで、要素幅に対してテキストがはみ出そうになったときにwbrを入れた箇所で改行させることができます。

<p class="word-break">誰だって、ほんとうに<wbr>いいことをしたら、いちばん<wbr>幸せなんだねぇ。</p>

テキストが要素からはみ出さずを配置した箇所で改行される

しかしまだ問題は残っています。

wbrで改行はされるようになりましたが、wbrがない箇所でははみ出してしまう問題が解消されていません。

wbrを細かく設定すればそこまで気にしなくてもいいんですが、コードの煩雑になりやすくなったり、スマホのような画面幅が狭いデバイスだと意図しない表示がされてしまう可能性があります。

overflow-wrap: anywhere;でさらに細かい改行

今までは句読点やwbrで明示的に改行させましたが、それ以外の箇所では改行してくれないのでさらに1行追加して細かい調整をします。

CSSにoverflow-wrap: anywhere;を追加することで、句読点でもwbrでも一文字単位でも改行してくれます。

<p class="word-break overflow-wrap">誰だって、ほんとうに<wbr>いいことをしたら、いちばん<wbr>幸せなんだねぇ。</p>
.word-break {
    word-break: keep-all;
}

.overflow-wrap {
    overflow-wrap: anywhere;
}

じゃあ全部これでやればいいじゃん?と思いますが、これも細かい問題がまだあって、一文字単位で改行する特定のせいで句読点の前でも改行されちゃんです。

作文を思い浮かべると分かりやすいと思うんですが、日本語というのは一行の最初に句読点が来るのはNGで、前行の最後のマスに一緒に書く法則があります。

Webでも通常は、その法則のように句読点が行の初めに来ないよう句読点の直前の一文字と一緒に改行されますが、今までの方法を使うとすべて無視してしまいます。

それぞれにメリット・デメリット。結論、どうすればいいの?

結論から言うと、適材適所、です。

使い分けるなら、説明文のような長い文章の場合はデフォルトもしくはword-break: break-all;を使い、見出しやキャッチコピーなど途中で改行をさけたいメッセージ性の強い箇所では今回紹介した方法を使うのがいいんじゃないかなと思います。

word-break: break-all;は要素からテキストがはみ出そうな場合に、単語の途中でも自動で改行させるためのプロパティーです。デフォルトでも改行してくれるんですが日本語だけの話で、英単語は途中で改行してくれません。word-break: break-all;を使うことで英単語の途中でも改行してくれるので、長い単語でもはみ出さずに改行させることができます。

場合によってはあえて改行させない方がいい場合もあるので組み合わせて使ってみてください。

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を使って非表示用クラスを付与したり直接操作したりするなど、管理しやすいやり方で使い分けていきましょう。