「CSS完全に理解した」でCSS完全に理解する

# 1：導入

![スクリーンショット 2021-03-11 17.58.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135161/c7f23539-3512-0190-20c5-526fc6d08b63.png)


例の「CSS完全に理解した」Tシャツ。
色々な方が話している通り、あれを意図的にやろうとすると実はなかなか難しい。

**むしろ、「CSS完全に理解した」を再現するコードを書くたびに我々はCSSの完全理解に近づけるんじゃ無いだろうか？**
**CSS完全に理解したでCSS完全に理解すればもっとCSS完全に理解したできるんじゃ無いか**

とにかく何かよくわからない追求心にとらわれた我々（約1名）は「CSS完全に理解した」を理解するために色々な方法でこれを再現することにしました。
以下に「CSS完全に理解した」するための数々の方法について書いていこうと思います。

# 2：前提

前提条件が限定されていないと無限の「CSS完全に理解した」状態を作り出せることになってしまうので、
今回は以下の画像のように、まず、「CSS完全に理解したわけではない」状態を再現し、ここに手を加えることで「CSS完全に理解した」状態を作り出すことにしました。

この状態でのHTML、CSSは以下の通りです。
つまり、テキストが入ったP要素を、CSSで枠を指定したDIVに入れたシンプルなものです。
HTMLはいじらず、CSSだけいじることでCSS完全に理解する状態にしていきます。

![スクリーンショット 2021-03-11 15.55.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135161/67e5edcc-1ea2-27cf-d24f-25bb23f8212b.png)

```html:html
    <div class="box">
      <p class="box__text">
        CSS<br>
        完全に理解した
      </p>
    </div>
```

```css:css
.box {
  font-size: 16px;
  position: relative;
  border: 1px solid #000;
  border-radius: 5px;
  width: 140px;
  height: 62px;
}
.box__text {
  margin: 10px;
}
```
※以下のCSSコードでは、付け足す部分のみを記述し、上に書かれた根幹の部分は省略します。

# 3：実践

## 基本：P要素の左側に単純な隙間を空けるパターン

まず単純にテキスト部分の左側に隙間を開けることを考えてみます。
これは、`padding-left`や`margin-left`使えば可能です。
（※`padding-inline-start`や`margin-inline-start`も同様）

```css:css
.box__text {
    margin-left: 60px;
}
```

```css:css
.box__text {
    padding-left: 50px;
}
```
しかし、ただ隙間を空けただけだと下の画像のように勝手に文字が途中で改行されてしまいます。
![スクリーンショット 2021-03-11 16.07.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135161/ff699c99-0b13-86ec-0954-f885ec879400.png)

また、`position:absolute`と`left`を使用して右側にテキストを移動した場合も、途中で改行されてしまいます。

```css:css
.box__text {
    position: absolute;
    left: 50px;
}
```

これは親要素であるDIVの幅が固定であるためで、それに引っ張られて下層のPタグも幅が制限され、改行されてしまうということです。
そこで、このテキストの自動折り返しをキャンセルする方法を考えなければなりません。

### 折り返しキャンセル方法1：`white-space: nowrap;`

文字通り改行を禁止するために使うCSSなので一番ふさわしいと思われます。`line-height`などをいじれば`white-space: pre;`でも不可能ではないが、今回は再現性が低いので割愛します。
また、`white-space: nowrap;`の代わりに`word-break: keep-all;`でも同じ結果を得られます。

### 折り返しキャンセル方法2：ネガティブマージン

左側に隙間を空けた分だけの幅を`margin-right`のマイナス値で指定すれば、結果全体の幅は保たれるので、自動折り返しをキャンセルすることができます。

### 折り返しキャンセル方法3：幅を指定する

親の幅に引っ張られているので、自身が幅を持てばそちらを優先することができ、結果折り返しが起こらなくなります。
`width`の代わりに`inline-size`を利用しても同じ効果を得られます。
（もちろん`min-width`や`min-inline-size`も同様）

以上、「左側に単純な隙間を空ける方法」と「折り返しをキャンセルする方法」の組み合わせでここまでで９種類の「CSS完全に理解した」ができます。

## 応用：折り返しをキャンセルする必要がないパターン

### `position:relative`を使う

```css:css
.box__text {
    position: relative;
    left: 50px;
}
```
`position: relative;`は元の高さや幅を保ったまま見た目を移動させるため、`left`（あるいは`inset-inline-start`）を使用しても自動折り返しも起こりません。

### `position: absolute;`と`right`を使用する

`position: absolute`の時に`left`を使うと上記のように折り返しが発生しますが、`right`（あるいは`inset-inline-end`）をマイナス値で指定する方法ならばそれは起こりません。ただ基準がテキストの幅（フォントの種類）に依存するため、正確な位置合わせは難しくなります。
上記のサンプルで`margin-left`と`margin-right`を同時に指定したように、`left: 50px;`と`right: -50px`を同時に指定したり、あるいは`inset-inline: 50px -50px;`を指定したりすればテキストの幅に対する依存は無くなります（そこまでする必要があるかどうかは別として）。

```css:css
.box__text {
    position: absolute;
    right: -42px;
}
```

### `transform`を使う

`transform`は「移動」ではなく「変形」なので、transformでの変形結果は幅や位置などに（見た目以上の）影響を与ええません。このためもちろん自動折り返しも起こりません。

```css:css
.box__text {
    transform: translateX(50px);
}

/* あるいは */
.box__text {
    transform: translate(50px, 0);
}
/* またあるいは */
.box__text {
    transform: translate3d(50px, 0, 0);
}
```
ちなみに上記は下記と同義となります。

```css:css
.box__text {
    transform: matrix(1, 0, 0, 1, 50, 0);
}

/* あるいは */
.box__text {
    transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 50, 0, 0, 1);
}
```

また、translateXやmatrix以外のtransformでも、下記のように無理やり右に50px移動させることができます。

```css:css
/* Y軸回転→Z軸移動→Y軸回転 */
.box__text {
    transform: rotateY(90deg) translateZ(50px) rotateY(-90deg);
}

/* Z軸回転→Y軸移動→Z軸回転 */
.box__text {
    transform: rotateZ(90deg) translateY(-50px) rotateZ(-90deg);
}

```

## 応用：flexを無理やり使う。

DIVに`display: flex;`を指定しP要素をフレックスアイテムにした場合も自動折り返しが働きますが、この場合は上にあげた方法に加えて`flex-shrink: 0;`でも折り返しをキャンセルすることができます。

```css:css
.box {
    display: flex;
}
.box__text {
    padding-left: 50px;
    flex-shrink: 0;
}
```

## 変態：常識にとらわれないパターン

### ボックスを一旦消したあと書き直してCSS完全に理解する

とりあえず、枠の幅をautoに指定し、P要素の左に隙間を空けてみましょう。

```css:css
.box {
    width: auto;
}
.box__text {
    margin-left: 60px;
}
```

![スクリーンショット 2021-03-11 17.28.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135161/750fc36f-266c-9f08-40ca-be9d298a78e3.png)


当然P要素の指定に引っ張られて枠の長さが伸びてしまいます。
そこで、伸びた枠を背景と同じ色に指定した擬似要素で塗りつぶし、新しい枠を作ってしまいます。
その上でP要素が一番上に来るように`z-index`で調整します。
枠を上書きして塗りつぶす以外に`border-color: transparent;`で枠を透明にする方法もあります。

```css:css
.box {
    width: auto;
}
.box::after {
    content: '';
    position: absolute;
    top: -1px;
    left: -1px;
    background-color: #fff;
    width: 221px;
    height: 64px;
    z-index: 1;
}
.box::before {
    content: '';
    position: absolute;
    top: -1px;
    left: -1px;
    border: 1px solid #000;
    border-radius: 5px;
    width: 140px;
    height: 62px;
    z-index: 2;
}
.box__text {
    position: relative;
    margin-left: 60px;
    z-index: 3;
}
```

### インデントを二重に指定してCSS完全に理解する

親のDIV要素に`text-indent`を指定し、P要素をインラインブロック要素にすると、親要素のtext-indentの影響をブロック全体で受けるようになるため、P要素はブロックごと右にずれることになります。しかしtext-indentはP要素の中にまで継承されるので、このままでは一行目の「CSS」がさらに右へずれてしまいます。そこでP要素で`text-indent`をゼロに指定してこれを打ち消せば、思惑通りの見た目を得ることができます。

```css:css
.box {
    text-indent: 50px;
}
.box__text {
    text-indent: 0;
    display: inline-block;
}
```

### 影文字で見た目を騙してCSS完全に理解する

本来の文字を`color: transparent;`で透明にし、text-shadowを使ってぼかしの無い影文字を50px右に表示させることにより、スマートに枠からはみ出した文字を再現することができます。

```css:css
.box__text {
    color: transparent;
    text-shadow: 50px 0 0 #000;
}
```

### transformのscaleで幅を突破しCSS完全に理解する

前述したようにtransformの変形結果は幅や位置などに影響を与えません。
そこでまずは、`font-size`を半分のサイズにすることで、折り返しが起こらない状況を作ります。
![スクリーンショット 2021-03-24 17.01.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135161/ba327f19-2cd5-704f-edf7-0fade94a2029.png)
こうした上で、`transform: scale(2);`で大きさを戻すことで折り返しを回避することができます。

```css:css
.box__text {
    font-size: 8px;
    padding-left: 25px;
    transform-origin: 0 0;
    transform: scale(2);
}
```

この時`transform-origin`で始点を左上にしても`padding-left`はその範囲に入るので、移動させる距離である50pxの半分である25pxを指定します。

### transformのrotateYで2回反転しCSS完全に理解する

まず枠全体を180度Y軸回転させます。
![スクリーンショット 2021-03-17 12.50.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135161/f936c1d0-3d6b-c7b8-4ae3-acc00008cd17.png)
当然文字が裏返りますが、この状態で中のP要素のみを中心軸を変えてもう一回反転させれば、文字の裏返りが直り、なおかつ枠の外にはみ出させることができます。

```css:css
.box {
    transform: rotateY(180deg);
}
.box__text {
    transform-origin: 35px 0;
    transform: rotateY(180deg);
}
```

裏返っていることで`transform-origin: 0 0;`は右上をさすことになり、幅いっぱい（120px）分右にずれるので、ここから動かす50pxを引いた上での半分（(120-50)/2）つまり35pxを指定すればちょうどの位置にずらすことができます。

### グリッドシステム完全に理解したからCCSも完全に理解する

せっかくなのでグリッドシステムを利用した方法も考えてみました。
グリッドセルを異なるグリッドアイテムで共有できることを利用した方法です。
親の枠を消し、before疑似要素と`.box__text`要素をグリッドアイテムとします。

縦方向のグリッドは
```
grid-template-rows: 11px auto 11px;
```
となります。`11px`は枠線の太さと枠線と`.box__text`要素間の隙間を足した数値です。

横方向のグリッドは
```
grid-template-columns: 11px 50px 68px 11px auto;
```
となります。`50px`が右にずらす数値、`68px`は余り、`auto`ははみ出す部分です。

こうして作ったグリッドにbefore疑似要素と`.box__text`要素を指定の位置に当て込めば完成です。

```css:css
.box {
	width: auto;
	display: grid;
	grid-template: 11px auto 11px / 11px 50px 68px 11px auto;
	border: 0;
}
.box::before {
	content: '';
	grid-area: 1 / 1 / 4 / 5;
	border: 1px solid #000;
	border-radius: 5px;
}
.box__text {
	margin: 0;
	grid-area: 2 / 3 / 3 / 6;
}
```

### 鬼畜の所業！SVGをCSSに入れ込んでCSS？完全に理解する

まず、「CSS完全に理解した」と全く同じ見た目になるようにSVGを作ります。

```html:svg
<svg xmlns="http://www.w3.org/2000/svg" width="202" height="64" viewbox="0 0 202 64">
	<style>
		text {
			font-family: sans-serif;
		}
	</style>
	<rect x="0.5" y="0.5" width="141" height="63" rx="4.5" ry="4.5" stroke="#000" stroke-width="1" fill="transparent"/>
	<text x="0" y="50" font-size="16">
		<tspan x="61" y="24">CSS</tspan>
		<tspan x="61" y="43">完全に理解した</tspan>
	</text>
</svg>
```

cssのborderとsvgのstrokeでは線の中央位置が違うのでうまく調整しないと同じ見た目になりません。
SVGができたら、これをbase64に変換し、疑似要素のcontentとして指定します。
あとは元からあった枠を透明に、P要素は非表示にすれば完成！

```css:css
.box::before {
    content: url(data:image/svg;base64,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);
    position: absolute;
    top: -1px;
    left: -1px;
    border: none;
}
.box__text {
    display: none;
}
```

**これはCSSと言えるのか。。。。。？**


# 4: まとめ

以下に上記で解説した、数々の「CSS完全に理解した」解法をのせました。

<p class="codepen" data-height="265" data-theme-id="light" data-default-tab="result" data-user="ichimonzi" data-slug-hash="WNoLOpW" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="CSS完全に理解したでCSS完全理解する">
  <span>See the Pen <a href="https://codepen.io/ichimonzi/pen/WNoLOpW">
  CSS完全に理解したでCSS完全理解する</a> by ichimonzi (<a href="https://codepen.io/ichimonzi">@ichimonzi</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

おそらくこれ以上の変態の解法もあるものと思われますので、変態の皆さんはよろしければ教えてください。

皆さんのさらなるCSSの完全理解をお祈りしています。