0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【実務でハマったSVG】scale(1,-1)でY座標が逆になる|ドラッグ座標を補正した話

0
Posted at

💥 発生した事象

SVGをドラッグして表示位置を移動する処理を実装していたとき、
一部のSVGだけY方向の動きが逆になることがありました。

左右方向は問題ありません。

ただ、上下にドラッグすると、

マウスを下へ動かす
↓
SVGは上へ動く

という状態になります。

同じ処理を使っている別のSVGでは正常に動いていたため、
最初は mousemove やドラッグ量の計算を疑いました。

調べていくと、
問題が起きていたSVGには、

transform="scale(1, -1)"

が指定されていました。


🚀 急いでいる人向け

SVGに、

transform="scale(1, -1)"

が指定されていると、
Y方向の座標系が反転します。

今回の処理では、2点の画面上の位置からY方向の倍率を計算していました。

sf.y =
    100 / (g2Box.y - g1Box.y);

通常は、

g2Box.y - g1Box.y
→ 正の値

でしたが、Y反転されたSVGでは、

g2Box.y - g1Box.y
→ 負の値

になりました。

その結果、

sf.y
→ マイナス

となり、

viewBoxY =
    viewBoxY - dy * sf.y;

でY方向がもう一度反転していました。

表示方向だけを正規化したい場合は、
Y方向の倍率を正の値として扱う方法があります。

sf.y =
    100 / Math.abs(
        g2Box.y - g1Box.y
    );

ポイントは、

見た目が上下反転している

だけでなく、

座標差分の符号まで反転する

ことです。


💡 この記事で分かること

  • scale(1,-1) がSVGのY座標に与える影響
  • 見た目は正常でもドラッグ方向だけ逆になる理由
  • getBoundingClientRect() のY差分が負になるケース
  • 座標倍率にマイナス値が入ると何が起こるか
  • 反転されたSVGを扱うときの座標補正の考え方

はじめに

今回扱っていたSVGには、
生成元の違う2種類のデータがありました。

Visio由来のSVGではドラッグ操作が正常に動いていました。

一方、AutoCAD変換版のSVGでは、
上下方向だけドラッグが逆になります。

ドラッグ処理自体は共通です。

そのため最初は、

mousemoveの座標取得がおかしい?
dyの計算がおかしい?
viewBoxの更新方向が逆?

あたりを疑っていました。

ただ、イベント側の clientY などを確認しても、
マウスの移動量自体は正常でした。

違っていたのはSVG側の座標系でした。


🔍 原因

問題が起きていたSVGの大枠に、

<g transform="scale(1, -1)">

が指定されていました。

これは、

X方向:そのまま
Y方向:反転

という変換です。

つまり、

scale(1, 1)
→ 通常

scale(1, -1)
→ 上下反転

です。

今回のSVGでは、
個々の図形ではなく大きなグループ側に指定されていました。

そのためSVG全体がY方向へ反転していました。


ドラッグ処理はどうなっていたか

ドラッグ時には、
マウスの移動量 dx / dy を使って viewBox の位置を変更していました。

処理を簡略化するとこんな形です。

function updateViewBoxMin(dx, dy) {

    const viewBoxList =
        svg
            .getAttribute("viewBox")
            .split(" ");

    viewBoxList[0] =
        Number(viewBoxList[0])
        - dx * sf.x;

    viewBoxList[1] =
        Number(viewBoxList[1])
        - dy * sf.y;

    svg.setAttribute(
        "viewBox",
        viewBoxList.join(" ")
    );
}

X方向は、

dx * sf.x

Y方向は、

dy * sf.y

を使っています。

問題になったのは sf.y でした。


Y方向の倍率を計算していた

SVG上の基準点2つを使い、
画面上の距離から倍率を計算していました。

イメージとしては、

const g1Box =
    point1.getBoundingClientRect();

const g2Box =
    point2.getBoundingClientRect();

const baseLength = 100;

sf.x =
    baseLength
    / (g2Box.x - g1Box.x);

sf.y =
    baseLength
    / (g2Box.y - g1Box.y);

通常のSVGでは、
Y座標の差分が正の値になっていました。

例えば正常なケースでは、

g2Box.y - g1Box.y
→ 正

sf.y
→ 0.718

でした。

ところが問題のSVGでは、

g1Box.y = 1390.304...
g2Box.y = 918.231...

となっていました。

差分は、

918.231...
-
1390.304...
=
-472.073...

です。

その結果、

sf.y
→ -0.212

になりました。

ここで初めて、

ドラッグ処理ではなく、倍率の符号が逆になっている

ことに気付きました。


なぜ sf.y が負になったのか

原因は、

scale(1, -1)

です。

Y軸が反転しているため、
画面上で基準点1と基準点2の上下関係も反転していました。

通常は、

point1
↓
point2

だったものが、

point2
↓
point1

のような関係になります。

その状態で、

g2Box.y - g1Box.y

を計算すると、
負の値になります。


📖 なぜドラッグ方向まで逆になったのか

ドラッグ処理では、

viewBoxY =
    viewBoxY
    - dy * sf.y;

としていました。

通常、

dy > 0
sf.y > 0

なら、

dy * sf.y
→ 正

です。

ところが今回、

sf.y < 0

になっています。

すると、

dy * sf.y
→ 負

になります。

さらに、

viewBoxY - 負の値

なので、

viewBoxY + 正の値

になります。

つまり、意図していた方向と逆です。

整理すると、

scale(1,-1)
↓
基準点のY方向が反転
↓
g2Box.y - g1Box.y が負になる
↓
sf.y が負になる
↓
dy × sf.y の符号も反転
↓
viewBoxの移動方向が逆になる

という流れでした。


✅ 補正方法

今回のように、

倍率としては距離だけ欲しい
Y方向の向きはドラッグ処理側で決める

のであれば、
基準点間の距離を正の値として扱えます。

例えば、

sf.y =
    baseLength
    / Math.abs(
        g2Box.y - g1Box.y
    );

です。

修正前は、

sf.y =
    baseLength
    / (g2Box.y - g1Box.y);

なので、

-472.073...

がそのまま使われます。

Math.abs() を使えば、

472.073...

として扱えます。

そのため sf.y も、

正の倍率

として計算できます。


ただし、とりあえず全部 Math.abs() にすればいいわけではない

ここは注意したいところです。

今回のケースでは、

基準点間の距離

として倍率を使っていたため、
符号を持たせる必要がありませんでした。

そのため絶対値へ正規化する考え方が使えます。

一方、

座標系がどちら向きなのか

自体を判定したい処理では、
マイナスの符号に意味があります。

例えば、

const invertedY =
    g2Box.y < g1Box.y;

のように、
反転状態を別に持つ方法もあります。

const yDiff =
    g2Box.y - g1Box.y;

const invertedY =
    yDiff < 0;

sf.y =
    baseLength
    / Math.abs(yDiff);

これなら、

倍率
→ 常に正

座標方向
→ invertedYで管理

と役割を分けられます。

実装が大きくなる場合は、
こちらの方が分かりやすいと思います。


scale(1,-1) 自体を消せばいい?

今回のSVGでは、
scale(1,-1) はSVG生成時の座標系を合わせるために入っているものでした。

そのため、

ドラッグが逆になる
↓
scale(1,-1)を削除する

では済みません。

削除すると、
今度は図面自体の表示が上下反転してしまいます。

必要なのは、

表示用のtransform

と、

操作用の座標計算

を分けて考えることでした。


getBoundingClientRect() にもtransformは反映される

今回もう1つ重要だったのがここです。

element.getBoundingClientRect();

で取得する値は、
ブラウザ上に表示されている位置を基準にしています。

つまりSVG側で、

transform="scale(1, -1)"

が適用されていれば、
その変換後の位置関係が取得結果にも現れます。

元SVG内の、

x
y

だけを見ていると、

point2の方が下にあるはず

と思っていても、
ブラウザ上では上下が逆になっている場合があります。

SVGで座標がおかしいときは、

元の属性値
transform
getBoundingClientRect()

を分けて確認した方が追いやすいです。


💼 実務での確認順

今回のように、
一部のSVGだけドラッグ方向がおかしい場合は、
自分なら次の順番で確認します。

1. マウス座標

clientX
clientY
dx
dy

が想定した値になっているか。

ここが正常なら、
イベント取得以外を疑います。

2. viewBox更新式

viewBoxY - dy * sf.y

など、
符号を確認します。

3. 倍率

sf.x
sf.y

をログへ出します。

今回の場合、

sf.x → 正常
sf.y → マイナス

だったため、かなり絞り込めました。

4. 基準点の位置

getBoundingClientRect();

の結果を比較します。

g1Box.y
g2Box.y

の大小関係を見るだけでも、
Y反転に気付きやすいです。

5. 親要素のtransform

最後に対象要素だけでなく、
親 <g> まで確認します。

今回も問題の指定は、
個別の図形ではなく大枠側の、

transform="scale(1, -1)"

でした。


⚠️ 見た目が正しいから座標も正しいとは限らない

今回一番ハマったのはここでした。

SVG自体は問題なく表示されています。

そのため最初は、

SVGデータは正常
↓
JavaScriptのドラッグ処理がおかしい

と思っていました。

実際には、

SVGはtransform込みで正しく表示されている
↓
でも座標系は反転している
↓
その座標を使った操作処理でズレる

という状態でした。

表示確認だけでは気付きにくい問題です。


📝 まとめ

今回の原因は、
SVG全体へ指定されていた、

transform="scale(1, -1)"

でした。

ポイントは次のとおりです。

  • scale(1,-1) はY方向を反転する
  • 見た目だけでなく座標の上下関係も反転する
  • getBoundingClientRect() のY差分が負になることがある
  • Y差分から倍率を計算すると、その倍率まで負になる
  • 負の倍率をドラッグ量へ掛けると移動方向が逆になる
  • 距離として使う倍率なら Math.abs() で正規化する考え方がある
  • 座標方向そのものが必要なら、反転状態は別で管理する
  • SVGの表示用transformと操作用の座標計算は分けて考える

最初は mousemove 側を追っていましたが、
原因はSVGのかなり外側に付いていた transform でした。

一部のSVGだけ上下方向がおかしい場合は、
ドラッグ処理だけでなく親要素の scale も確認すると原因を追いやすいです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?