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

WPF项目实现Canvas指定对象/点位的平滑缩放动画需求

WPF Canvas 围绕指定对象/点位实现平滑缩放动画

原代码的缩放动画默认以Canvas左上角为中心,原因是ScaleTransform的CenterX和CenterY默认值为0。要实现围绕指定对象或点位的缩放,核心是动态调整缩放中心,并同步修正Canvas的位置偏移,确保目标点在缩放过程中始终保持视觉上的固定位置。

核心思路

  1. 计算目标点在Canvas坐标系下的绝对位置
  2. 根据当前缩放比例和目标缩放比例,算出需要调整的偏移量
  3. 同时对缩放比例和偏移量做动画,实现平滑过渡

场景1:围绕Canvas内的指定UI元素缩放

假设要围绕Canvas中名为targetElement的元素缩放,代码修改如下:

private void btnAnimate_Click(object sender, RoutedEventArgs e)
{
    // 确保Canvas的LayoutTransform是包含平移和缩放的TransformGroup
    var transformGroup = mCanvas.LayoutTransform as TransformGroup;
    ScaleTransform scaler;
    TranslateTransform translator;

    if (transformGroup == null)
    {
        transformGroup = new TransformGroup();
        scaler = new ScaleTransform(1.0, 1.0);
        translator = new TranslateTransform(0, 0);
        transformGroup.Children.Add(translator);
        transformGroup.Children.Add(scaler);
        mCanvas.LayoutTransform = transformGroup;
    }
    else
    {
        scaler = transformGroup.Children.OfType<ScaleTransform>().First();
        translator = transformGroup.Children.OfType<TranslateTransform>().First();
    }

    // 计算目标元素在Canvas中的绝对位置和中心坐标
    Point elementTopLeft = targetElement.TransformToAncestor(mCanvas).Transform(new Point(0, 0));
    Point targetCenter = new Point(
        elementTopLeft.X + targetElement.ActualWidth / 2,
        elementTopLeft.Y + targetElement.ActualHeight / 2
    );

    // 切换缩放比例(1.0 ↔ 1.5)
    double targetScale = scaler.ScaleX == 1.0 ? 1.5 : 1.0;
    // 计算缩放需要的偏移修正量,保证目标中心位置不变
    double offsetX = targetCenter.X * (1 - targetScale / scaler.ScaleX);
    double offsetY = targetCenter.Y * (1 - targetScale / scaler.ScaleY);

    // 创建带缓动效果的动画,让缩放更自然
    var ease = new CubicEase() { EasingMode = EasingMode.EaseInOut };
    DoubleAnimation scaleAnim = new DoubleAnimation()
    {
        To = targetScale,
        Duration = TimeSpan.FromMilliseconds(600),
        EasingFunction = ease
    };

    DoubleAnimation translateXAnim = new DoubleAnimation()
    {
        To = translator.X + offsetX,
        Duration = TimeSpan.FromMilliseconds(600),
        EasingFunction = ease
    };
    DoubleAnimation translateYAnim = new DoubleAnimation()
    {
        To = translator.Y + offsetY,
        Duration = TimeSpan.FromMilliseconds(600),
        EasingFunction = ease
    };

    // 启动所有动画
    scaler.BeginAnimation(ScaleTransform.ScaleXProperty, scaleAnim);
    scaler.BeginAnimation(ScaleTransform.ScaleYProperty, scaleAnim);
    translator.BeginAnimation(TranslateTransform.XProperty, translateXAnim);
    translator.BeginAnimation(TranslateTransform.YProperty, translateYAnim);
}

场景2:围绕鼠标点击的点位缩放

如果要围绕鼠标点击位置缩放,先给Canvas绑定MouseDown事件,再实现缩放逻辑:

// Canvas的MouseDown事件处理
private void mCanvas_MouseDown(object sender, MouseButtonEventArgs e)
{
    if (e.ChangedButton == MouseButton.Left)
    {
        ZoomToPoint(e.GetPosition(mCanvas));
    }
}

// 围绕指定点位缩放的核心方法
private void ZoomToPoint(Point targetPoint)
{
    var transformGroup = mCanvas.LayoutTransform as TransformGroup;
    ScaleTransform scaler;
    TranslateTransform translator;

    if (transformGroup == null)
    {
        transformGroup = new TransformGroup();
        scaler = new ScaleTransform(1.0, 1.0);
        translator = new TranslateTransform(0, 0);
        transformGroup.Children.Add(translator);
        transformGroup.Children.Add(scaler);
        mCanvas.LayoutTransform = transformGroup;
    }
    else
    {
        scaler = transformGroup.Children.OfType<ScaleTransform>().First();
        translator = transformGroup.Children.OfType<TranslateTransform>().First();
    }

    double targetScale = scaler.ScaleX == 1.0 ? 1.5 : 1.0;
    double offsetX = targetPoint.X * (1 - targetScale / scaler.ScaleX);
    double offsetY = targetPoint.Y * (1 - targetScale / scaler.ScaleY);

    var ease = new CubicEase() { EasingMode = EasingMode.EaseInOut };
    DoubleAnimation scaleAnim = new DoubleAnimation()
    {
        To = targetScale,
        Duration = TimeSpan.FromMilliseconds(600),
        EasingFunction = ease
    };

    DoubleAnimation translateXAnim = new DoubleAnimation()
    {
        To = translator.X + offsetX,
        Duration = TimeSpan.FromMilliseconds(600),
        EasingFunction = ease
    };
    DoubleAnimation translateYAnim = new DoubleAnimation()
    {
        To = translator.Y + offsetY,
        Duration = TimeSpan.FromMilliseconds(600),
        EasingFunction = ease
    };

    scaler.BeginAnimation(ScaleTransform.ScaleXProperty, scaleAnim);
    scaler.BeginAnimation(ScaleTransform.ScaleYProperty, scaleAnim);
    translator.BeginAnimation(TranslateTransform.XProperty, translateXAnim);
    translator.BeginAnimation(TranslateTransform.YProperty, translateYAnim);
}

关键说明

  • 使用TransformGroup组合TranslateTransform和ScaleTransform,保证先平移再缩放的顺序,偏移修正才会正确
  • 偏移量计算公式目标点坐标 × (1 - 目标缩放比例 / 当前缩放比例),能确保目标点在缩放前后视觉位置不变
  • 添加EasingFunction可以避免生硬的线性动画,让缩放过程更流畅自然

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 14:25:30