transformで中央配置できないときの注意点 : translate, scale併用時

transformを使用して要素を中央配置する機会は多いと思います。

しかし、思ったように中央配置できない場合、translateとscaleの記述の順番が影響しているかもしれません。

ポイント:scaleとtranslateの併用時は順番に注意する

中央配置したい要素をscaleを使用して大きさを変更し、translateで中央配置したい場合、先にscaleを行わないとtranslateで中央配置したいと思っても、うまく中央に配置できません。cssとKotlinで具体例を見ていきます。

cssでの具体例

1. scale → translate の順番 : 中央に配置できる

サンプルコード
<style>
    .container {
      width: 300px;
      height: 300px;
      background: #fdd;
      position: relative;
      margin-bottom: 40px;
    }

    .circle-correct {
      width: 50px;
      height: 50px;
      background: #14b2a4;
      border-radius: 50%;
      position: absolute;
      left: 50%;
      top: 50%;
      /* 下記の順番だと中央に配置できる */
      transform: scale(2) translate(-50%, -50%); /* 先にscaleを行う */
      transform-origin: top left;
    }

    .label {
      margin-bottom: 8px;
      font-weight: bold;
    }
  </style>
  <div class="label">✅ scale → translate(ズレなし)</div>
  <div class="container">
    <div class="circle-correct"></div>
  </div>
サンプルコードをブラウザで表示したとき
✅ scale → translate(ズレなし)
 

2. translate → scale の順番 : ずれてしまい、中央に配置できない

サンプルコード
<style>
    .container {
      width: 300px;
      height: 300px;
      background: #fdd;
      position: relative;
    }

    .circle-wrong {
      width: 50px;
      height: 50px;
      background: #14b2a4;
      border-radius: 50%;
      position: absolute;
      left: 50%;
      top: 50%;
      /* 下記の順番だと中央に配置できない */
      transform: translate(-50%, -50%) scale(2); /* 後からscaleを行う */
      transform-origin: top left;
    }

    .label {
      margin-bottom: 8px;
      font-weight: bold;
    }
  </style>
  <div class="label">❌ translate → scale(ズレる)</div>
  <div class="container">
    <div class="circle-wrong"></div>
  </div>
サンプルコードをブラウザで実行したとき
❌ translate → scale(ズレる)
 

Kotlinでの具体例

下記のコードで中央に配置できます。

Canvas(modifier = modifier) {
        val canvasSize = size
        // パスのバウンディングボックス(パス全体の矩形範囲)を取得
        val bounds = path.getBounds()

        // 中央に配置するためのオフセットを計算
        val offsetX = (canvasSize.width - bounds.width) / 2f - bounds.left
        val offsetY = (canvasSize.height - bounds.height) / 2f - bounds.top

        // パスのサイズをキャンバス(縦と横のいずれか小さい方)のサイズに合わせる
        val scale = minOf(
            canvasSize.width / bounds.width,
            canvasSize.height / bounds.height
        )

        // パスを変換して描画
        withTransform({
            scale(scale) // ← translateの前にスケーリングする
            translate(offsetX, offsetY)
        }) {
            drawPath(path)
        }
    }