30歳からのプログラミング

30歳無職から独学でプログラミングを開始した人間の記録。

CSS で斜線を描画する

HTML や CSS に、斜線を描画するための仕組みは用意されていない。
だがlinear-gradientを利用することで、このような斜線を描画できる。

f:id:numb_86:20200106013247p:plain

「Can I use...」で確認すると、主要なブラウザは全てlinear-gradientに対応している。

linear-gradientはその名の通り、線形グラデーションを表現するためのスタイル。
なぜそれを使って斜線を描画できるのか理解するために、まずはlinear-gradientの機能を説明する。

以降の例では全て<div></div>という空のdiv要素を用意し、そこにスタイルをあてていく。
説明しやすいからそうしただけであり、linear-gradientが機能する要素ならどこにでも斜線を描画できる。

まず、以下のようたスタイルを定義してみる。

div {
  width: 100px;
  height: 100px;
  background-image: linear-gradient(0deg, skyblue, greenyellow);
}

すると、以下のように表示される。

f:id:numb_86:20200106012636p:plain

100*100の要素が表示され、上方向に、青から緑へとグラデーションになっている。
この画像と CSS を見比べると、linear-gradientの仕組みを理解しやすい。

ひとつめのパラメータである0degはグラデーションの方向を角度で指定している。0degは上方向を意味する。90degは右方向、180degは下方向、270degは左方向を意味する。
残りのパラメータは開始色と終了色。
そのため今回の例では、「上方向に向かって、skyblueからgreenyellowへとグラデーションさせる」という意味になる。

ひとつめのパラメータについては、角度ではなく、方向を表すキーワードを使うこともできる。
例えばto rightとすれば右方向に、to right topとすれば右上方向に、グラデーションさせる。

f:id:numb_86:20200106012648p:plain

このグラデーションを使って、どうやって斜線を表現するのか。重要なのが、「途中色」という概念である。

linear-gradientで指定できるのは、2色だけではない。開始色と終了色の他に途中色を指定して、複数の色でグラデーションさせることができる。

div {
  width: 300px;
  height: 100px;
  background-image: linear-gradient(90deg, black, white, skyblue, black);
}

f:id:numb_86:20200106012713p:plain

この例では、右方向に、黒 -> 白 -> 青 -> 黒とグラデーションさせている。

画像を見れば分かるように、デフォルトだと均等な間隔でのグラデーションになる。
この間隔を指定することもできる。

div {
  width: 300px;
  height: 100px;
  background-image: linear-gradient(90deg, black 20%, white 30%, skyblue 90%, black);
}

f:id:numb_86:20200106012727p:plain

0%から20%までが黒で、そこから10%分の領域が白へのグラデーションとなる。
そこから60%分の領域が青へのグラデーションとなり、残りの10%が青から黒へのグラデーションとなる。

これを応用することで、線を描画することができる。

div {
  width: 300px;
  height: 100px;
  background-image: linear-gradient(90deg, transparent 44%, black 44%, black 56%, transparent 56%);
}

f:id:numb_86:20200106012738p:plain

transparentは透明を意味するので、0%から44%地点までが透明である。
そして同じ44%地点で黒に切り替えるが、56%地点でも黒を指定している。そのため、この12%分の領域は黒で塗り潰される。
そして56%地点で再び黒から透明に切り替え、あとは最後まで透明が続く。
これが、「線」が表示される理由である。

あとはこれを傾ければ、斜線になる。

div {
  width: 300px;
  height: 100px;
  background-image: linear-gradient(to right top, transparent 44%, black 44%, black 56%, transparent 56%);
}

f:id:numb_86:20200106012750p:plain

要素の大きさやlinear-gradientの指定によって、斜線の太さ、角度、色を、自由に調整できる。

f:id:numb_86:20200106012801p:plain

***

個人的な感想だが、この手法はだいぶ無理をしている感がある。
ブラウザは本来ドキュメントビューアであり、HTML は文書構造を表現するためのものであり、CSS はそれを装飾するためのものだった。
それがいつの間にか、アプリケーションのプラットフォームとして使われるようになっていった。本来とは異なる用途での利用が盛んになった。
だからどうしても、アプリケーションを開発する上での機能が貧弱になる。アプリケーションプラットフォームとして考えると「斜線を描画する機能もないのか」となるが、その出自を考えれば仕方がない。
だから今回のように、本来とは違う使い方をして、やりたいことを実現していくことになる。CSS は特にその傾向が強い印象がある。
Web やブラウザが本来とは違う使い方をされていて、苦し紛れというか、あまり健全ではないような気もするが、それくらいインターネットや Web の仕組みが便利だったのだろう。

参考資料

React.Suspense と React.lazy でコンポーネントを動的に読み込む

React.SuspenseReact.lazyを使うことで、import()で動的に読み込んだコンポーネントを通常のコンポーネントとしてレンダリングすることができる。
動的読み込みはパフォーマンス向上のためなどに使われるが、それを簡単に React アプリに取り入れることができる。

import()の概要はこちらを参照。
numb86-tech.hatenablog.com

この記事に出てくるコードは React のv16.10.2で動作確認している。

React.lazy

React.lazyは、コンポーネントを返す関数。引数には、import()の返り値をそのまま返す関数を渡す。そしてimport()で読み込まれるモジュールは、コンポーネントをdefaultでエクスポートしている必要がある。
そのため、以下のようになる。

// One.js

import React from 'react';

const One = () => <span>1</span>;

export default One;

// App.js

const One = React.lazy(() => import('./One'));

これで、Oneを動的に読み込み、それでいて普通のコンポーネントと同じように使えるようになった。

React.Suspense

動的に読み込んだコンポーネントは、必ずSuspense要素でラップされる必要がある。そしてSuspense要素にはfallback属性が必須である。
具体的には以下のようになる。

<Suspense fallback="loading...">
  <One />
</Suspense>

Oneが読み込まれるまではfallbackに渡された内容が表示され、読み込みが終わるとOneの表示に切り替わる。
上記の例ではfallbackに文字列を渡しているが、React 要素なら何でもよい。

コード全体は以下のようになる。

// One.js

import React from 'react';

const One = () => <span>1</span>;

export default One;

// App.js

import React, {Suspense} from 'react';

const One = React.lazy(() => import('./One'));

const App = () => {
  return (
    <div>
      <Suspense fallback="loading...">
        <One />
      </Suspense>
    </div>
  );
};

export default App;

一瞬だけloading...が表示されたあと、1が表示される。

Suspenseには、動的に読み込んだコンポーネントを複数ラップできる。また、動的に読み込んだコンポーネント以外のコンポーネントも、ラップできる。

// Two.js

import React from 'react';

const Two = () => <span>2</span>;

export default Two;

// App.js

import React, {Suspense} from 'react';

const One = React.lazy(() => import('./One'));
const Two = React.lazy(() => import('./Two'));

const App = () => {
  return (
    <div>
      <Suspense fallback="loading...">
        <One />
        <Two />
        <span>3</span>
      </Suspense>
    </div>
  );
};

export default App;

動的に読み込んだコンポーネントを複数ラップした場合、全てのコンポーネントが読み込まれた時点で、ラップした内容を表示する。それまではfallbackの値が表示される。

それを確認するため、OneTwoを時間差で読み込ませる。

import React, {Suspense} from 'react';

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

const One = React.lazy(() => sleep(1000).then(() => import('./One')));
const Two = React.lazy(() => sleep(2000).then(() => import('./Two')));

const App = () => {
  return (
    <div>
      <Suspense fallback="loading...">
        <One />
        <Two />
        <span>3</span>
      </Suspense>
    </div>
  );
};

export default App;

このようにすると、約2秒間loading...が表示され、その後123が表示される。読み込みが終わったものから順次表示する、とはならない。

条件分岐で読み込むコンポーネントを変える

以下のコードでは、isOnetruthyのときにOneが、falsyのときにTwoが、それぞれ読み込まれる。
該当しないほうのコンポーネントは、表示に使う使わないではなく、そもそも読み込まれない。

import React, {Suspense} from 'react';

const isOne = true;

const TargetComponent = React.lazy(() => {
  if (isOne) return import('./One');
  return import('./Two');
});

const App = () => {
  return (
    <div>
      <Suspense fallback="loading...">
        <TargetComponent />
      </Suspense>
    </div>
  );
};

export default App;

参考資料