クモのようにコツコツと

フロントエンドエンジニア イイダリョウの技術ブログ。略称「クモコツ」

【React】アスペクト比ジェネレーターを作った(画像の縦横比率を計算するツール)

Reactアプリの続きです。前回までは「ジャンプ率ジェネレーター」をNext.jsで作り直していました。今回は「アスペクト比ジェネレーター」に取り組みます。以前こちらでまとめた主要なアスペクト比による画像サイズのCSS数値を調べられるツールにります。それではいきましょう!

【目次】

※参考:前回記事
【React】ジャンプ率ジェネレーターをNext.jsで作り直した(Next.js + TypeScript + CSS in JS) - クモのようにコツコツと

※参考:ReactでWebアプリを作るシリーズまとめ
qiita.com

前回のおさらい

「ジャンプ率ジェネレーター」をNext.jsで作り直した

https://cdn-ak.f.st-hatena.com/images/fotolife/i/idr_zz/20210406/20210406071313.jpg

文字サイズを変えると下のサンプルの見た目が変わって、適切なサイズを検討することができるツール

※参考:前回記事
【React】ジャンプ率ジェネレーターをNext.jsで作り直した(Next.js + TypeScript + CSS in JS) - クモのようにコツコツと

主なアスペクト比

次は「アスペクト比ジェネレーター」を作りたい。画像のアスペクト比(縦横比率)を検討することができる。

こちらの記事にまとめた主要なアスペクト比で切り替える設定にする。

  • スクエア(1:1)
  • デジカメ4:3(1.333:1)
  • 白銀比(1.414:1)
  • デジカメ3:2(1.5:1)
  • 黄金比(1.618:1)
  • デジカメ16:9(1.777:1)

※参考:【黄金比、白銀比】代表的なアスペクト比(縦横比率)を一覧にしてみた - クモのようにコツコツと


また、画像を外接リサイズしたいので私の愛してやまないobject-fit: coverも使う!これで画像のサイズが不揃いでもページ上の画像の見え方が統一されるのだ。

※参考:私が愛してやまない待望の外接リサイズobject-fitを使うのにIEのせいであと1年半も待ってらんないっ!!(→祝・IEサポート終了!) - クモのようにコツコツと

作ったもの

作ったものこちら「アスペクト比ジェネレーター」

aspect-ratio-generator.vercel.app

※参考:アスペクト比ジェネレーター

ソースコードはこちら

※参考:GitHub - ryo-i/aspect-ratio-generator


上の設定項目が下の画像に反映される。初期設定はスクエア(正方形)
f:id:idr_zz:20210516150159j:plain

「アスペクト比」を変更すると画像の縦横比率が変わる f:id:idr_zz:20210516150717j:plain

「画像の向き」を変更すると画像の向きが変わる f:id:idr_zz:20210516150713j:plain

「画像サイズ」を変更すると画像のサイズが変わる f:id:idr_zz:20210516150720j:plain

「サイズ(幅)の刻み」を変えると画像サイズ変更の刻みが変わる f:id:idr_zz:20210516150724j:plain

ページ設定

title、meta、OGP設定

アプリのベースは先日作ったNextスターターキットを使う。Next.js + CSS in JS + TypeScript環境。

※参考:【React】ReactでWebアプリを作るシリーズまとめ(随時更新) - Qiita


「/data/data.json」にurl、title、descriptionを設定

{
    "head": {
        "url": "https://aspect-ratio-generator.vercel.app/"
    },
    "header": {
        "title":"アスペクト比ジェネレーター",
        "text": "黄金比、白銀比、デジカメサイズなどの見え方とCSS設定値を調べることができます。"
    },
   // 後略

「/pages/index.tsx」でデータを読み込む

import Data from '../data/data.json';

ヘッダーとページタイトルはデータから読み込むがページテキストはベタ打ち

const headerTitle = Data.header.title;
const headerText = Data.header.text;
const pageTitle = Data.header.title;
const pageText = 'アスペクト比の設定を変更すると下の画像とCSS設定値が変化します';

画像をアップ

「/public」フォルダに画像をアップ。

※参考:aspect-ratio-generator/public at main · ryo-i/aspect-ratio-generator · GitHub

アップしたのは以前、アスペクト比の記事で用いた階段の画像(kaidan.jpg)

f:id:idr_zz:20210516192941j:plain

※参考:【黄金比、白銀比】代表的なアスペクト比(縦横比率)を一覧にしてみた - クモのようにコツコツと

この正方形(スクエア)な画像をアスペクト比によってトリミングする。

Innerコンポーネント構成

画像の初期値を設定する

先ほどの「data.json」でInnerコンポーネントで読み込む初期値を設定する

    "inner": {
        "aspect": {
            "square": {
                "name": "スクエア",
                "ratio": 1
            },
            "silverRatio": {
                "name": "白銀比",
                "ratio": 1.414
            },
            "goldenRatio": {
                "name": "黄金比",
                "ratio": 1.618
            },
            "camera4_3": {
                "name": "デジカメ4:3",
                "ratio": 1.333
            },
            "camera3_2": {
                "name": "デジカメ3:2",
                "ratio": 1.5
            },
            "camera16_9": {
                "name": "デジカメ16:9",
                "ratio": 1.777
            }
        },
        "direction": {
            "horizontal": "横",
            "vertical": "縦"
        },
        "step": 10,
        "size": 600,
        "max": 1000
    },
  • Innerキーの中にInnerコンポーネントの初期値を設定
  • aspectキーの中に各アスペクト比の項目名と比率を設定
  • directionキーの中に向きを設定
  • stepにサイズ(幅)の刻みの初期値10を設定
  • sizeに画像サイズの初期値600を設定
  • maxに画像サイズの最大値1000を設定

ここのキー名を手がかりにページのタグや処理と紐づける。

maxはページ上の設定項目にはないがスマホなど画面サイズが狭い時に可変させる。

各種インポート

「/components/Inner.tsx」を修正する

冒頭で各種インポート

import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
import Data from '../data/data.json';

フック(useStateuseEffect)を使うのでインポート

※参考:【React】フック(React Hooks)事始め:useState、useEffect、useContext - クモのようにコツコツと

styled-componentsdata.jsonはこれまで通り

Innerコンポーネント定義

コンポーネントは関数コンポーネント

// Component
function Inner() {
   // 中略(コンポーネント設定)
}

export default Inner;

Innerコンポーネントを定義して、最後にエクスポートする。

フック設定

フックの初期値を設定

useState()を使ってフックの初期値を設定していく

  const data = Data.inner;
  const [aspectName, setAspectName] = useState(data.aspect.square.name);
  const [aspectRatio, setAspectRatio] = useState(data.aspect.square.ratio);
  const [direction, setDirection] = useState(data.direction.horizontal);
  const [step, setStep] = useState(data.step);
  const [width, setWidth] = useState(data.size);
  const [height, setHeight] = useState(data.size);
  const [maxSize, setMaxSize] = useState(data.max);
  • aspectNameにはスクエアの名前(aspect.square.name)を設定
  • aspectRatioにはスクエアの比率(aspect.square.ratio)を設定
  • directionには横向き(direction.horizontal)を設定
  • stepwidthheightmaxSizeは初期値そのものなのでそのまま設定

画面幅によるサイズ変更

useEffect()を使って画面幅に基づいたサイズ変更

  useEffect(() => {
    const windowWidth = document.body.clientWidth;
    if (900 > windowWidth) {
      const resultSize = windowWidth - 120;
      setMaxSize(resultSize);
      setWidth(resultSize);
      setHeight(resultSize);
    }
  }, []);
  • 変数windowWidthで画面サイズclientWidthを取得
  • もしwindowWidthが900より少なかったら
  • 変数resultSizewindowWidthから120引いた数値を取得
  • setMaxSize()setWidth()setHeight()resultSizeをセット

画面サイズが900pxより狭い時だけ処理を実行する。

clientWidthはスクロールバーを抜いた画面幅を取得できるプロパティ

※参考:Element.clientWidth - Web API | MDN
※参考:ウインドウサイズを取得する 【JavaScript 動的サンプル】

inputタグから数値を取得

独自フックuseGetNumber()でinputから取得した値を数値(number)として返す

  const useGetNumber = (e: React.ChangeEvent<HTMLInputElement>) => {
    const getValue: number = Number(e.target.value);
    return getValue;
  };

独自フック参考。

※参考:【React】独自フックで関数を抽出、全てのツマミが動いた!(ジャンプ率ジェネレーター) - クモのようにコツコツと

画像の向きによる画像幅の計算

独自フックuseGetHeight()で画像の向き変更によって画像幅を変更する。

  const useGetHeight = (width: number, ratio: number, direction: string) => {
    if (direction === '横') {
      return Math.floor(width / ratio);
    } else if (direction === '縦') {
      return Math.floor(width * ratio);
    }
  };
  • 独自フックuseGetHeight()の引数はwidthratiodirection
  • もしdirectionが「横」ならwidth割るratioの結果を返す
  • またはもしdirectionが「縦」ならwidth掛けるratioの結果を返す

計算結果は少数の桁数が多いことがあるので、Math.floor()で少数を切り捨てする

※参考:【JS】Math.random()でつくるサイコロとおみくじ - クモのようにコツコツと

アスペクト比の変更

関数changeAspect()でアスペクト比を変更

  const changeAspect = (e: React.ChangeEvent<HTMLInputElement>) => {
    const getValue = e.target.value;
    const changeName = data.aspect[getValue].name;
    const changeRatio = data.aspect[getValue].ratio;
    const changeHeight = useGetHeight(width, changeRatio, direction);
    setAspectRatio(changeRatio);
    setAspectName(changeName);
    setHeight(changeHeight);
  };
  • changeDirection()の引数はイベントターゲット(inputタグ)
  • 変数getValueでinputタグのの値を取得
  • 変数changeNameaspectgetValuenameを取得
  • 変数changeRatioaspectgetValueratioを取得
  • 変数changeHeightuseGetHeight()実行、引数はwidth, changeRatio, direction
  • setAspectRatio()changeRatioをセット
  • setHeight()changeHeightをセット

オブジェクト(連想配列)の指定はドットで繋ぐのが楽だったが、今回のように変数名をキー名に代入したい場合は角カッコ[]を使う書き方が有効だった!

※参考:JavaScriptのオブジェクトのキーに変数の値を使うTips - Qiita

useGetHeight()の引数に今回取得したchangeRatioを設定している

画像の向きを変更

関数changeDirection()で画像の向きを変更

  const changeDirection = (e: React.ChangeEvent<HTMLInputElement>) => {
    const getValue = e.target.value;
    const changeDirection = data.direction[getValue];
    const changeHeight = useGetHeight(width, aspectRatio, changeDirection);
    setDirection(changeDirection);
    setHeight(changeHeight);
  };
  • 変数changeDirectiondirectiongetValueを取得
  • 変数changeHeightuseGetHeight()実行、引数はwidthaspectRatiochangeDirection
  • setDirection()changeDirectionをセット
  • setHeight()changeHeightをセット

changeAspect()と似ているがuseGetHeight()の引数に今回取得したchangeDirectionを設定している

画像サイズの変更

関数ChangeImageSize()で画像サイズを変更

  const ChangeImageSize = (e: React.ChangeEvent<HTMLInputElement>) => {
    const getValue = useGetNumber(e);
    const changeHeight = useGetHeight(getValue, aspectRatio, direction);
    setWidth(getValue);
    setHeight(changeHeight);
  };
  • 変数getValueuseGetNumber()によってターゲットを数値として取得
  • 変数changeHeightuseGetHeight()実行、引数はgetValueaspectRatiodirection
  • setWidth()getValueをセット
  • setHeight()changeHeightをセット

useGetHeight()の引数に今回取得したgetValueを設定している

画像サイズ(幅)の刻み値を変更

関数changeStep()で画像サイズを変更する際の幅の刻み値を変更する

  const changeStep = (e: React.ChangeEvent<HTMLInputElement>) => {
    const getValue = useGetNumber(e);
    setStep(getValue);
  };
  • 変数getValueuseGetNumber()によってターゲットを数値として取得
  • setStep()getValueをセット

画像サイズのCSS変更

変数getValueの連想配列にwidthheightを設定して画像サイズのインラインスタイルとして動的に変更

  // Change CSS
  const imgStyle = {
    width: width + 'px',
    height: height + 'px'
  }

インラインスタイルについてはこちらを参照

※参考:【React】ジャンプ率ジェネレーターをNext.jsで作り直した(Next.js + TypeScript + CSS in JS) - クモのようにコツコツと

JSX設定

JSXの構造

JSXは下記の2パートにわかれる

  // JSX
  return (
    <div className="inner">
      <Setting>
        // 設定変更箇所
      </Setting>
      <Example>
        // 画像表示箇所
      </Example>
    </div>
  );
  • Settingコンポーネントに設定変更のinputタグ類がある
  • Exampleコンポーネントに変更結果を反映する画像などがある

SettingExampleにはStyled-componentsでCSS in JSを設定している。スタイル詳細は下記を参照。

※参考:aspect-ratio-generator/Inner.tsx at main · ryo-i/aspect-ratio-generator · GitHub

設定変更のJSX

Settingコンポーネントに設定変更のinputタグ類を設定

      <Setting>
        <dl>
        <dt>
          主なアスペクト比
          </dt>
          <dd>
            <label><input type="radio" name="aspect" value="square" onChange={changeAspect} defaultChecked />スクエア(1:1)</label>
            <label><input type="radio" name="aspect" value="silverRatio" onChange={changeAspect} />白銀比(1.414:1)</label>
            <label><input type="radio" name="aspect" value="goldenRatio" onChange={changeAspect} />黄金比(1.618:1)</label>
            <label><input type="radio" name="aspect" value="camera4_3" onChange={changeAspect} />デジカメ4:3(1.333:1)</label>
            <label><input type="radio" name="aspect" value="camera3_2" onChange={changeAspect} />デジカメ3:2(1.5:1)</label>
            <label><input type="radio" name="aspect" value="camera16_9" onChange={changeAspect} />デジカメ16:9(1.777:1)</label>
          </dd>
          <dt>
            画像の向き
          </dt>
          <dd>
            <label><input type="radio" name="direction" value="horizontal" onChange={changeDirection} defaultChecked />横向き</label>
            <label><input type="radio" name="direction" value="vertical" onChange={changeDirection} />縦向き</label>
          </dd>
          <dt>
            画像サイズ
          </dt>
          <dd>
            <input type="range" className="widthValue" min="10" max={maxSize} step={step} defaultValue={width} onChange={ChangeImageSize} />
            <p>幅:{width}px、高さ:{height}px</p>
          </dd>
          <dt>
            サイズ(幅)の刻み
          </dt>
          <dd>
            <label><input type="radio" name="step" value="1" onChange={changeStep} />1px</label>
            <label><input type="radio" name="step" value="5" onChange={changeStep} />5px</label>
            <label><input type="radio" name="step" value="10" onChange={changeStep} defaultChecked />10px</label>
          </dd>
        </dl>
      </Setting>
  • 「主なアスペクト比」のradioボタンでchangeAspectを実行
  • 「画像の向き」のradioボタンでchangeDirectionを実行
  • 「画像サイズ」のrangeバーでChangeImageSizeを実行
    max属性でmaxSizestep属性でstepを読み込み
    テキスト「幅」でwidth、「高さ」でheightを読み込み
  • 「サイズ(幅)の刻み」のradioボタンでchangeStepを実行

inputタグ、radioボタンの初期状態はdefaultChecked、rangeバーの初期値はdefaultValueで設定。

※参考:非制御コンポーネント – React

変更結果のJSX

Exampleコンポーネントに変更結果を反映する画像などを設定

      <Example>
        <section>
          <h2>{aspectName + "(1:" + aspectRatio + " " + direction + "向き)"}</h2>
          <figure><img src="/kaidan.jpg"  style={imgStyle}/></figure>
          <p>CSS設定 {'{'} width: {width}px; height: {height}px; object-fit: cover; {'}'}</p>
        </section>
      </Example>
  • h2タグでaspectNameaspectRatiodirectionを読み込む
  • imgタグのstyle属性でimgStyleを読み込む
  • pタグにCSSスタイルのwidthheightを表示

pタグ波カッコ{}をテキストとして表示したかったのだがJSXだとJSの式として認識されてしまった。preタグで囲ってもうまくいかない。。

調べたらシングルコーテーションで囲ってさらに波カッコで囲う{'{'}とエスケープができた!こんな方法があったとは!多謝♪

※参考:React(JSX)で波括弧をエスケープしたい - Javaエンジニア、React+Redux+Firebaseでアプリを作る

最後に

といういうことで、ようやくReactアプリの2つ目が完成しました〜♪

一つ目がジャンプ率ジェネレーターで2つ目がアスペクト比ジェネレーター。だんだんとinputタグとフックを連携させる方法に慣れてきている感じがしてきています。

次もこちらのデザインまとめの中からジェネレーターを作ります。ちょっとこれまでよりも大きいテーマになるんですが、、HSBとRBGを変換する配色ツールにトライしてみたいと思います。

※参考:【デザインの基本】メリハリ、縦横比率、画面分割、タイポ、色相環、配色 まとめ - Qiita

HSBの補色は単純な180度の反対側ではないところに難しさを感じていますが、ぜひ補色や近似色を含んだ3色パレットの形で完成させたいと目論んでいます。

それではまた!


※参考:ReactでWebアプリを作るシリーズまとめ
qiita.com