You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

QML如何围绕鼠标点对已旋转的Item实现正确缩放

问题原因
  • Qt Quick的transform变换按列表顺序从后向前执行,你当前代码中先声明Scale、后声明Rotation,实际渲染时会先应用旋转、再应用缩放,两套变换的参考坐标系不统一。
  • 原有位置补偿逻辑仅适配了无旋转的场景,没有考虑旋转带来的坐标系偏转,直接沿x/y轴加偏移量的计算方式在旋转后完全不匹配实际坐标偏移方向,必然出现位置跳变。
  • 原有代码存在属性引用错误:maxZoom/minZoom是定义在sample矩形上的属性,calculateZoom函数中错误引用了mouseArea下的同名属性,会触发属性不存在的运行警告。
修复方案

不用手动计算旋转对应的三角函数偏移,直接用Qt Quick内置的坐标映射APImapToItem做位置补偿,这套逻辑会自动把旋转、缩放等所有变换的影响计算在内,不需要针对旋转单独调整公式,鲁棒性更强:

  1. 缩放前先记录鼠标点击位置在父坐标系下的坐标,这个点在缩放全程要保持固定
  2. 更新缩放比例和缩放原点
  3. 计算缩放后同一点在父坐标系下的新位置,将前后位置的差值补到矩形的x/y坐标上,保证鼠标点位置不发生跳变

修复后的完整代码如下:

import QtQuick 2.0

Rectangle {
    id: root
    color: "gray"
    width: 1200
    height: 800

    Rectangle {
        color: "black"
        width: 360
        height: 200
        opacity: 0.5
        x: 500
        y: 350
    }

    Rectangle {
        id: sample
        color: "green"
        width: 360
        height: 200
        opacity: 0.5
        x: 500
        y: 350

        property real currZoom: 1

        property real maxZoom: 5
        property real minZoom: 0.5

        transform: [
            Scale {
                id: scaler
                xScale: sample.currZoom
                yScale: sample.currZoom
            },
            Rotation{
                id: rotation
                origin.x: 180
                origin.y: 100
                angle: 30
            }
        ]

        MouseArea{
            id: mouseArea
            anchors.fill: parent
            drag.target: sample

            onClicked: mouse => {
                zoom(true, mouse.x, mouse.y)
            }

            onWheel: (wheel) => {
                var isIn = wheel.angleDelta.y > 0
                zoom(isIn, wheel.x, wheel.y)
                wheel.accepted = true
            }

            function zoom(isIn, x, y) {
                // 记录缩放前鼠标点在父坐标系下的位置,该点需保持固定
                const fixedPos = sample.mapToItem(root, x, y)
                // 更新缩放参数
                const oldZoom = sample.currZoom
                sample.currZoom = calculateZoom(isIn, oldZoom)
                scaler.origin.x = x
                scaler.origin.y = y
                // 计算缩放后的位置偏差并补偿
                const newPos = sample.mapToItem(root, x, y)
                sample.x += fixedPos.x - newPos.x
                sample.y += fixedPos.y - newPos.y
            }

            function calculateZoom(isIn, prevZoom) {
                var newZoom = isIn ? prevZoom + 0.1 : prevZoom - 0.1
                // 修正属性引用路径
                if (newZoom > sample.maxZoom)
                    newZoom = sample.maxZoom
                if (newZoom < sample.minZoom)
                    newZoom = sample.minZoom
                return newZoom
            }
        }
    }
}
补充说明
  • 这套实现不需要手动维护冗余的currX/currY属性存储缩放原点,直接给scaler的origin赋值即可。
  • 滚轮事件加了wheel.accepted = true避免事件冒泡导致的异常缩放行为。
  • 不管后续调整旋转角度、修改变换顺序,这套缩放逻辑都可以正常工作,不需要额外调整坐标计算逻辑。

内容的提问来源于stack exchange,提问作者Alexander Satsukevich

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 21:15:42