みかづきブログ・カスタム

基本的にはちょちょいのほいです。

styled-componentsでリキッドレイアウトを再現するために画面幅に合わせたサイズを計算する関数をつくる 📱

f:id:kimizuka:20211229223852g:plain

ことの発端

以前、vwを使って画面幅に合わせたサイズ調整を行うモックをつくりましたが、今回はそれを簡単に設定できるユーティリティ関数を作ろうと思いました。
ひとつひとつを手書きで書いていくのは非常に骨が折れるからです。

blog.kimizuka.org


方針としては、

  • styled-componentsを使う
  • 最小画面幅でのサイズ、デフォルトの画面幅でのサイズ、最大画面幅でのサイズが決まっている
  • 最小サイズから最大サイズまではデフォルトを基準に画面幅に併せて拡大縮小する

という条件で使える関数を作ります。

インターフェイスを考える

引数に、プロパティ名、デフォルトのサイズを受け取り、スタイルを返す関数を作ろうと思うので、

function getResponsiveSize(
  property: string,
  defaultSizes: number[]
): string {
  return ''; // styleをテキストで返す
}

という設計にしようと思います。
なぜdefaultSizeではなく、defaultSizesと配列にしているかというと、CSSプロパティはmarginやpaddingなど複数の値を取るものも少なくないからです。
本当は、

function getResponsiveSize(
  property: string,
  defaultSizes: number || number[]
): string {
  return ''; // styleをテキストで返す
}


という感じで、数値も配列も受け取れるようにするのがスマートということは重々承知なのですが、今回はシンプルに作るために数値をひとつ渡す時でも配列に入れるという方針でいきます。

ソースコード

const defaultScreenSize = 980;
const minScreenSize = 320;
const maxScreenSize = 1200;

export function getResponsiveSize(
  property: string,
  defaultSize: number[]
): string {
  let defaultStyle = '';
  let minSizesStyle = '';
  let maxSizesStyle = '';

  defaultSize.forEach((size) => {
    defaultStyle += ` calc(100vw / ${ defaultScreenSize } * ${ size })`;
    minSizesStyle += ` calc(${ minScreenSize }px / ${ defaultScreenSize } * ${ size })`;
    maxSizesStyle += ` calc(${ maxScreenSize }px / ${ defaultScreenSize } * ${ size })`;
  });

  return `
    ${ property }:${ defaultStyle };

    @media(max-width: ${ minScreenSize }px) {
      ${ property }:${ minSizesStyle };
    }

    @media(min-width: ${ maxScreenSize }px) {
      ${ property }:${ maxSizesStyle };
    }
  `;
}

一旦、基準幅980、最小幅320、最大幅1200で決め打ちにしてしまっていますが、実際は別ファイルにして管理する想定です。

つかいかたの例

const Box = styled.div`
  ${ getResponsiveSize('margin', [8, 16]) }
  ${ getResponsiveSize('width', [120]) }
  ${ getResponsiveSize('height', [120]) }
`;

このように書くと、

margin: calc(100vw / 980 * 8) calc(100vw / 980 * 16);

@media(max-width: 320px) {
  margin: calc(320px / 980 * 8) calc(320px / 980 * 16);
}

@media(min-width: 1200px) {
  margin: calc(1200px / 980 * 8) calc(1200px / 980 * 16);
}

width: calc(100vw / 980 * 120);

@media(max-width: 320px) {
  width: calc(320px / 980 * 120);
}

@media(min-width: 1200px) {
  width: calc(1200px / 980 * 120);
}

height: calc(100vw / 980 * 120);

@media(max-width: 320px) {
  height: calc(320px / 980 * 120);
}

@media(min-width: 1200px) {
  height: calc(1200px / 980 * 120);
}

と展開され、リキッドレイアウトを実現できるはずです。

DEMO

f:id:kimizuka:20211229223852g:plain

https://develop.kimizuka.org/next-liquid-lyout/

import styled from 'styled-components';

const defaultScreenSize = 980;
const minScreenSize = 320;
const maxScreenSize = 1200;

export function getResponsiveMultipleSize(
  property: string,
  defaultSizes: number[]
): string {
  let defaultStyle = '';
  let minSizesStyle = '';
  let maxSizesStyle = '';

  defaultSizes.forEach((size) => {
    defaultStyle += ` calc(100vw / ${ defaultScreenSize } * ${ size })`;
    minSizesStyle += ` calc(${ minScreenSize }px / ${ defaultScreenSize } * ${ size })`;
    maxSizesStyle += ` calc(${ maxScreenSize }px / ${ defaultScreenSize } * ${ size })`;
  });

  return `
    ${ property }:${ defaultStyle };

    @media(max-width: ${ minScreenSize }px) {
      ${ property }: ${ minSizesStyle };
    }

    @media(min-width: ${ maxScreenSize }px) {
      ${ property }: ${ maxSizesStyle };
    }
  `;
}

const Wrapper = styled.div`
  h1 {
    ${ getResponsiveMultipleSize('font-size', [120]) }
  }

  .box-red {
    ${ getResponsiveMultipleSize('margin', [8, 16]) }
    ${ getResponsiveMultipleSize('width', [120]) }
    ${ getResponsiveMultipleSize('height', [120]) }
    background: red;
  }

  .box-blue {
    ${ getResponsiveMultipleSize('margin', [40, 160]) }
    ${ getResponsiveMultipleSize('width', [60]) }
    ${ getResponsiveMultipleSize('height', [80]) }
    background: blue;
  }
`;

export default function Index() {
  return (
    <Wrapper>
      <h1>HELLO</h1>
      <div className="box-red" />
      <div className="box-blue" />
    </Wrapper>
  );
}