Qt QMLアニメーションを自在に操る!transformOriginと代替手段

2025-06-06

Qt Quick (QML) における Item.transformOrigin プロパティは、Item が変形(回転、拡大縮小など)を行う際の「原点」を設定するために使用されます。この原点は、Item のローカル座標系内で定義されます。

具体的に言うと、Item.rotationItem.scale のようなプロパティを使って Item を操作する際、デフォルトでは Item の中心(x + width/2, y + height/2 の位置)が変形の中心となります。しかし、transformOrigin を設定することで、この変形の中心を任意の位置に変更することができます。

transformOrigin の値

transformOriginenum 型のプロパティで、以下の定義済み定数を使用できます。これらは、Item の境界ボックスに対する相対的な位置を示します。

  • Item.BottomRight: 右下隅
  • Item.Bottom: 下辺の中央
  • Item.BottomLeft: 左下隅
  • Item.Right: 右辺の中央
  • Item.Center: 中央(デフォルト値)
  • Item.Left: 左辺の中央
  • Item.TopRight: 右上隅
  • Item.Top: 上辺の中央
  • Item.TopLeft: 左上隅

使用例

例えば、Rectangle をその左上隅を原点として回転させたい場合、以下のように記述します。

Rectangle {
    width: 100
    height: 100
    color: "blue"
    rotation: 45 // 45度回転

    // 変形の原点を左上隅に設定
    transformOrigin: Item.TopLeft
}

この例では、Rectangle は自身の左上隅を軸として45度回転します。もし transformOrigin が設定されていなければ、Rectangle は自身の中心を軸として回転します。

なぜ transformOrigin が重要なのか

  • コードの簡潔化
    transformOrigin を使用しない場合、手動でItem の位置を調整したり、複雑な Transform 要素(RotationScaleoriginX, originY プロパティ)を使ったりする必要があり、コードが複雑になる可能性があります。transformOrigin はこれらのケースをより簡潔に記述できる方法を提供します。
  • 配置の柔軟性
    特定の要素を別の要素の角や辺を基準に操作したい場合に役立ちます。
  • より自然なアニメーション
    UI要素が特定のポイントを中心に回転したり拡大縮小したりする際に、transformOrigin を設定することで、より意図した通りで自然なアニメーションを実現できます。例えば、ドアが開閉するアニメーションでは、蝶番の位置を変形の原点とすることで、現実のドアの動きを再現できます。

注意点

  • transformOrigin は、Itemtransform プロパティに追加される任意の Transform 要素(例: Rotation, Scale, Translation)の originX および originY プロパティとは異なる概念です。transformOriginItem 自体の「便利」なプロパティであり、QMLエンジンによって内部的に対応する Transform 要素の原点に変換されます。より複雑な変形が必要な場合は、Item.transform リストに複数の Transform 要素を追加して、それぞれの originX および originY を設定することもできます。
  • transformOrigin は、Itemx, y, width, height に基づいて計算されます。これらのプロパティが変更されると、transformOrigin によって示される絶対的な位置も変更されます。


Item.transformOrigin は非常に便利なプロパティですが、意図しない動作を引き起こすこともあります。ここでは、よくあるエラーとその解決策をいくつかご紹介します。

エラー: 期待通りの変形原点にならない

原因

  • Item.transform との混同
    Item.transformOriginItem 全体に対する変形原点ですが、Item.transform リスト内の個々の Transform 要素(RotationScale など)もそれぞれ originX/originY プロパティを持っています。これらが同時に設定されている場合、どちらが優先されるか混乱することがあります。
  • デフォルト値の誤解
    Item.Center がデフォルトですが、これを常に意識していないと、意図しない回転や拡大縮小の軸になります。
  • 親要素からの影響
    レイアウトプロパティ(anchors など)や親の変形によって Item の絶対位置が変わる場合、視覚的に原点がズレているように見えることがあります。
  • width/height の変更
    transformOriginItemwidthheight に基づいて計算されます。これらのプロパティが動的に変更される場合、transformOrigin が指す絶対的な位置も変わってしまいます。

トラブルシューティング

  • デフォルトの理解
    transformOrigin を設定しない場合、中心が原点であることを常に念頭に置いてください。
  • Item.transform との併用
    複数の変形を適用する場合、Item.transformOrigin よりも Item.transform リスト内の個々の Transform 要素の originXoriginY を使用する方が、より細かく制御できる場合があります。Item.transformOriginItem 全体の「おおまかな」変形原点と考え、複雑な場合は Transform 要素を使うと良いでしょう。
  • 視覚的な確認
    Rectangle などで transformOrigin の位置に小さな目印(例えば、Rectangle の子として小さな Rectangle を配置し、x: parent.width * <origin_factor_x>, y: parent.height * <origin_factor_y> のように配置)を置いて、実際に変形原点がどこにあるのか視覚的に確認してみてください。
  • width/height の固定または追跡
    Item のサイズが動的に変わる場合は、変形を行っている間に widthheight を固定するか、widthheight の変化に合わせて rotationscale を調整することを検討してください。

エラー: パフォーマンスの問題

原因

  • 複雑なシーンでの多用
    多数の Item が同時に変形を行い、それぞれの transformOrigin が異なる場合、QMLエンジンの計算コストが増加し、特に低スペックなデバイスではパフォーマンスが低下する可能性があります。

トラブルシューティング

  • C++での最適化
    非常に高いパフォーマンスが要求される場合、QMLだけで複雑な変形を行うのではなく、C++側でOpenGLなどのグラフィックAPIを直接操作して変形を処理することを検討します。
  • 必要な場合のみ使用
    変形原点を変更する必要がある Item にのみ transformOrigin を設定し、それ以外はデフォルトの Item.Center を使用します。
  • プロファイリング
    Qt Creator の QML Profiler を使用して、どこでパフォーマンスのボトルネックが発生しているかを確認します。

エラー: アニメーションが不自然に見える

原因

  • 変形の種類と原点の不一致
    例えば、水平方向にスライドさせるアニメーションで transformOriginItem.Bottom に設定しても、変形自体には影響がありません。
  • アニメーション中の transformOrigin の変更
    アニメーションの途中で transformOrigin を変更しようとすると、ジャンプしたり、不自然な動きになったりすることがあります。transformOrigin は通常、アニメーションの開始前に設定され、アニメーション中は一定に保たれるべきです。

トラブルシューティング

  • 変形の種類と原点の確認
    行いたい変形(回転、拡大縮小、移動)に対して、設定している transformOrigin が論理的に合っているか確認してください。
  • アニメーション前の設定
    transformOrigin は、アニメーションが始まる前に確定させるようにしてください。

エラー: Item の位置が変形後にずれる

原因

  • 変形と位置の混同
    rotationscale などの変形は Item の描画を変化させますが、Itemxy プロパティの論理的な値は変更しません。変形後も Itemx/y は元の位置のままです。これにより、UI上の見た目の位置と、プログラム上で認識されている位置にずれが生じ、他の要素の配置やイベント処理で問題が発生することがあります。

トラブルシューティング

  • Transform 要素の translation
    あるいは、Item.transform プロパティに Translation 要素を追加して、変形後の位置を調整することもできます。
  • 必要に応じた x/y の調整
    変形後に他の要素の配置やイベント処理で、変形後の見た目の位置を基準にしたい場合は、Itemxy を変形量に応じて手動で調整する必要があるかもしれません。これは特に複雑なケースで必要になることがあります。
  • transform プロパティの理解
    Item.transformOrigin とそれに伴う変形は、あくまで描画の変換であることを理解してください。論理的な位置 (x, y) は変わりません。

エラー: エディタやデザインビューでの表示と実行時の表示が異なる

原因

  • 異なるQtバージョン/QMLエンジン
    開発環境と実行環境でQtのバージョンが異なる場合、QMLエンジンの挙動に微妙な違いが生じることがあります。
  • QMLホットリロードやキャッシュの問題
    環境によっては、エディタのデザインビューが最新のQMLファイルを正しく反映していないことがあります。
  • バージョンの一貫性
    開発環境とターゲット環境でQtのバージョンを一致させるようにしてください。
  • Qt Creator の再起動
    Qt Creator IDE自体を再起動すると、キャッシュの問題が解決することがあります。
  • クリーンビルドと再実行
    プロジェクトをクリーンビルドし、再度実行してみてください。


Item.transformOrigin プロパティは、QMLでアイテムの変形(回転や拡大縮小など)の中心点を指定するために使用されます。以下に、いくつかの一般的な使用例とコードを示します。

基本的な回転と原点の指定

この例では、Rectangle を回転させ、transformOrigin を変更することで回転の中心がどのように変化するかを示します。

import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    width: 640
    height: 480
    visible: true
    title: "TransformOrigin Examples"

    // 左上の長方形: デフォルト(中心)を原点として回転
    Rectangle {
        id: defaultRotationRect
        x: 50
        y: 50
        width: 100
        height: 100
        color: "red"
        rotation: 0 // 初期角度

        Text {
            anchors.centerIn: parent
            text: "Center (Default)"
            color: "white"
            font.pixelSize: 12
        }

        // 常に回転
        Timer {
            interval: 20
            repeat: true
            running: true
            onTriggered: defaultRotationRect.rotation += 1
        }
    }

    // 中央の長方形: 左上隅を原点として回転
    Rectangle {
        id: topLeftRotationRect
        x: 200
        y: 50
        width: 100
        height: 100
        color: "green"
        rotation: 0 // 初期角度

        // 変形の原点を左上隅に設定
        transformOrigin: Item.TopLeft

        Text {
            anchors.centerIn: parent
            text: "TopLeft"
            color: "white"
            font.pixelSize: 12
        }

        Timer {
            interval: 20
            repeat: true
            running: true
            onTriggered: topLeftRotationRect.rotation += 1
        }
    }

    // 右上の長方形: 右下隅を原点として回転
    Rectangle {
        id: bottomRightRotationRect
        x: 350
        y: 50
        width: 100
        height: 100
        color: "blue"
        rotation: 0 // 初期角度

        // 変形の原点を右下隅に設定
        transformOrigin: Item.BottomRight

        Text {
            anchors.centerIn: parent
            text: "BottomRight"
            color: "white"
            font.pixelSize: 12
        }

        Timer {
            interval: 20
            repeat: true
            running: true
            onTriggered: bottomRightRotationRect.rotation += 1
        }
    }

    // 左下の長方形: 拡大縮小の例
    Rectangle {
        id: scaleRect
        x: 50
        y: 200
        width: 100
        height: 100
        color: "purple"
        scale: 1.0 // 初期スケール

        // 変形の原点を左下隅に設定
        transformOrigin: Item.BottomLeft

        Text {
            anchors.centerIn: parent
            text: "Scale from BottomLeft"
            color: "white"
            font.pixelSize: 10
            wrapMode: Text.WordWrap
            horizontalAlignment: Text.AlignHCenter
        }

        // 拡大縮小アニメーション
        PropertyAnimation {
            target: scaleRect
            property: "scale"
            from: 1.0
            to: 1.5
            duration: 1000
            loops: Animation.Infinite
            running: true
            easing.type: Easing.InOutQuad
            direction: PropertyAnimation.Alternating // 行き来するアニメーション
        }
    }

    // ドアのアニメーション例 (回転原点をヒンジに設定)
    Rectangle {
        id: doorFrame
        x: 250
        y: 200
        width: 150
        height: 200
        color: "brown"
        border.color: "darkgray"
        border.width: 5

        Text {
            anchors.top: parent.top
            anchors.left: parent.left
            anchors.leftMargin: 5
            text: "Door Example"
            color: "white"
            font.pixelSize: 12
        }

        Rectangle {
            id: door
            x: 0 // ドアの左端がドア枠の左端に合うように
            y: 0
            width: parent.width * 0.9 // ドア枠より少し狭く
            height: parent.height // ドア枠と同じ高さ
            color: "lightgray"
            border.color: "black"
            border.width: 1

            // ドアのヒンジ(蝶番)の位置を回転の原点に設定
            // ここでは左端の中央をヒンジとする
            transformOrigin: Item.Left

            rotation: 0 // 初期角度 (閉じている状態)

            // ドアの開閉アニメーション
            MouseArea {
                anchors.fill: parent
                onClicked: {
                    if (door.rotation === 0) {
                        doorAnimation.to = -90 // ドアを開く (外側に開く場合)
                    } else {
                        doorAnimation.to = 0 // ドアを閉じる
                    }
                    doorAnimation.start()
                }
            }

            PropertyAnimation {
                id: doorAnimation
                target: door
                property: "rotation"
                duration: 500
                easing.type: Easing.OutQuad
            }
        }
    }
}

コードの説明

  1. 基本的な回転と原点の指定:

    • defaultRotationRect: transformOrigin を設定しない場合、Item.Center がデフォルトで適用され、長方形の中心を軸に回転します。
    • topLeftRotationRect: transformOrigin: Item.TopLeft を設定することで、長方形の左上隅を軸に回転します。
    • bottomRightRotationRect: transformOrigin: Item.BottomRight を設定することで、長方形の右下隅を軸に回転します。
    • これらは Timer を使って継続的に回転させており、視覚的に原点の違いがよくわかります。
  2. 拡大縮小の例:

    • scaleRect: transformOrigin: Item.BottomLeft を設定し、scale プロパティをアニメーションさせています。これにより、長方形は左下隅を固定点として拡大縮小します。
  3. ドアのアニメーション例:

    • これは transformOrigin の実用的な応用例です。
    • doorFrame は単なる見た目の枠です。
    • door アイテムが実際に回転するドアを表します。
    • transformOrigin: Item.Left を設定することで、ドアの回転軸を左端(ヒンジの位置)に設定しています。
    • MouseArea をクリックすると、PropertyAnimationdoor.rotation0 から -90 (またはその逆) にアニメーションさせます。これにより、ドアがヒンジを中心に開閉するリアルな動きが再現されます。もし transformOrigin を設定しないと、ドアは自身の中心を軸に回転してしまい、不自然な動きになります。

コードの実行方法

このQMLコードを main.qml などのファイルに保存し、以下のようなシンプルなC++の main.cpp.pro ファイルでプロジェクトを設定して実行できます。

main.cpp:

#include <QGuiApplication>
#include <QQmlApplicationEngine>

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    const QUrl url(QStringLiteral("qrc:/main.qml")); // main.qml のパス
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed,
                     &app, []() { QCoreApplication::exit(-1); },
                     Qt::QueuedConnection);
    engine.load(url);

    return app.exec();
}

yourprojectname.pro:

QT += quick

CONFIG += c++17

SOURCES += main.cpp

RESOURCES += qml.qrc

# The following makes sure that your QML files are compiled into the application
# so that they are available without having to deploy the QML files separately.
# For more information, see http://doc.qt.io/qt-6/qtquick-deployment.html
qt_add_qml_module(app_transformorigin_example
    URI transformorigin.example
    VERSION 1.0
    QML_FILES main.qml
)

qml.qrc: (このファイルに main.qml を追加します)

<RCC>
    <qresource prefix="/">
        <file>main.qml</file>
    </qresource>
</RCC>

Qt Creator を使っている場合、"Qt Quick Application" テンプレートからプロジェクトを作成し、main.qml の内容を上記の例に置き換えるのが最も簡単です。



Item.transformOrigin は便利ですが、特定のシナリオやより複雑な変形の場合には、いくつかの代替手段や、より低レベルな制御を提供する方法が存在します。

Item.transform プロパティと個別の Transform 要素を使用する

これは、Item.transformOrigin の最も直接的な代替であり、より柔軟性を提供します。Item.transform プロパティは、RotationScaleTranslation などの複数の Transform 要素のリストを受け取ります。それぞれの Transform 要素は、自身の originX および originY プロパティを持つことができます。

特徴

  • QMLでのみ使用可能
    これらの要素はQMLでのみ定義されます。
  • 順序の重要性
    transform リスト内の要素の適用順序が結果に影響します。
  • より詳細な制御
    複数の異なる変形を組み合わせる際に、それぞれの変形に対して異なる原点を設定できます。

使用例
あるアイテムを、まず左上隅を軸に回転させ、次にその中心を軸に拡大縮小させたい場合など。

Rectangle {
    width: 100
    height: 100
    color: "orange"
    x: 100
    y: 100

    transform: [
        // まず、アイテムの左上隅 (0, 0) を原点として回転
        Rotation {
            angle: 45 // 45度回転
            originX: 0
            originY: 0
        },
        // 次に、アイテムの中心 (width/2, height/2) を原点として拡大縮小
        Scale {
            xScale: 1.2
            yScale: 1.2
            originX: parent.width / 2 // もしくは Item.Center に相当する計算
            originY: parent.height / 2
        },
        // 必要に応じて移動(平行移動)も追加
        Translation {
            x: 10
            y: 5
        }
    ]

    Text {
        anchors.centerIn: parent
        text: "Complex Transform"
        color: "white"
        font.pixelSize: 10
        wrapMode: Text.WordWrap
        horizontalAlignment: Text.AlignHCenter
    }

    // アニメーションを追加することも可能
    // PropertyAnimation {
    //     target: parent.transform[0] // Rotation 要素をターゲット
    //     property: "angle"
    //     from: 0
    //     to: 360
    //     duration: 2000
    //     loops: Animation.Infinite
    // }
}

この方法では、originXoriginY を明示的に数値で指定する必要があります。Item.TopLeft のような便利な列挙値は使えません。

x と y プロパティを手動で調整する

これは最も原始的な方法で、回転や拡大縮小の前にアイテムの位置を「ずらし」、変形後に元の位置に戻すことで、あたかも特定の点を原点としているかのように見せかける手法です。

特徴

  • アニメーションとの組み合わせが困難
    特に連続的なアニメーションの場合、xy を同時に正確に調整し続けるのは非常に難しいです。
  • 最も柔軟性があるが、最も複雑
    任意の点を原点として設定できますが、計算が複雑になります。

使用例
アイテムの右上隅を中心に回転させたいが、transformOrigin: Item.TopRight を使いたくない(あるいは使えない)場合。

Rectangle {
    id: manualRotateRect
    width: 100
    height: 100
    color: "teal"
    x: 200
    y: 200

    property real currentAngle: 0 // 現在の回転角度

    // 右上隅を変形原点として扱う
    // 1. アイテムを原点に移動 (右上隅が (0,0) になるように)
    // 2. 回転
    // 3. 元の位置に戻す
    transform: [
        Translation { x: -manualRotateRect.width; y: 0 }, // 右上隅を原点に移動
        Rotation { angle: manualRotateRect.currentAngle }, // 回転
        Translation { x: manualRotateRect.width; y: 0 }  // 元の位置に戻す
    ]

    Text {
        anchors.centerIn: parent
        text: "Manual Transform"
        color: "white"
        font.pixelSize: 10
        wrapMode: Text.WordWrap
        horizontalAlignment: Text.AlignHCenter
    }

    // アニメーション
    Timer {
        interval: 20
        repeat: true
        running: true
        onTriggered: {
            manualRotateRect.currentAngle += 1
            if (manualRotateRect.currentAngle >= 360) manualRotateRect.currentAngle = 0
        }
    }
}

この方法は、数学的な変換行列を理解している場合に、非常に特定のニーズを満たすために利用されます。通常は Item.transformOriginTransform 要素で十分です。

QMLでのカスタムC++要素やシェーダーの利用

非常に高度な変形や、QMLの標準機能では実現できない特殊な視覚効果が必要な場合、カスタムのC++要素をQMLに公開したり、ShaderEffectShaderEffectSource を使ってカスタムシェーダー(GLSL)を記述したりする方法があります。

特徴

  • QMLの範囲を超える
    純粋なQMLの範囲を逸脱します。
  • ** steepest learning curve:** OpenGL/WebGL、GLSL、C++とQMLの統合についての深い知識が必要です。
  • 最高の柔軟性とパフォーマンス
    GPUを直接利用できるため、非常に複雑で高性能な視覚効果を実現できます。
  • 大量の要素をGPU上で効率的に描画・変形する場合。
  • カスタムの波紋効果や歪み効果など、QMLの標準的な回転や拡大縮小だけでは表現できない変形。
// 例 (概念的、完全なシェーダーコードは含まない)
ShaderEffect {
    width: 200
    height: 200
    x: 350
    y: 200

    property variant source: ShaderEffectSource { sourceItem: myImage; hideSource: true }
    property real rotationAngle: 0.0

    vertexShader: "
        uniform highp mat4 qt_Matrix;
        attribute highp vec4 qt_Vertex;
        attribute highp vec2 qt_TexCoord0;
        varying highp vec2 qt_UV;

        void main() {
            // ここで独自の変換行列を構築し、qt_Vertex に適用する
            // 例: 特定のピボットポイントを中心に回転させる行列を適用
            // vec4 translated_vertex = qt_Vertex - pivot_point;
            // vec4 rotated_vertex = rotation_matrix * translated_vertex;
            // gl_Position = qt_Matrix * (rotated_vertex + pivot_point);

            gl_Position = qt_Matrix * qt_Vertex; // 通常の変換
            qt_UV = qt_TexCoord0;
        }
    "
    fragmentShader: "..."

    // アニメーション
    PropertyAnimation {
        target: parent
        property: "rotationAngle"
        from: 0
        to: 360
        duration: 3000
        loops: Animation.Infinite
    }
}

Image {
    id: myImage
    source: "qrc:/images/example.png"
    width: 200
    height: 200
    visible: false // ShaderEffectSourceで参照されるので非表示
}
方法利点欠点最適なケース
Item.transformOrigin最も簡単で直感的。複数の異なる変形に異なる原点は設定できない。単一の変形(回転、拡大縮小)の原点設定。
Item.transform柔軟性が高い。 複数の変形を組み合わせる。originX/originY の数値指定が必要。順序が重要。複雑な変形、複数の変形軸が必要な場合。
手動での x/y 調整究極の制御。非常に複雑でエラーを起こしやすい。非常に特殊な、または教育的な目的。
カスタムC++/シェーダー最高のパフォーマンスと視覚効果の柔軟性。学習曲線が急。 複雑な実装が必要。高度なグラフィック効果、大量の要素の描画。