arkui 动画曲线

发布于:2025-08-01 ⋅ 阅读:(17) ⋅ 点赞:(0)

参考文档

https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-curve#curvesinterpolatingspring10

可视化工具网站

https://easingwizard.com/
https://www.desmos.com/calculator/k01p40v0ct?lang=zh-CN

基本介绍

import { curves } from '@kit.ArkUI'
curves.interpolatingSpring(10, 1, 228, 30) // 创建一个时长由弹簧参数决定的弹簧插值曲线
/**
代码核心功能:
该代码片段用于创建一个弹簧插值曲线,曲线的时长由弹簧参数决定。这在动画或界面过渡效果中非常有用,可以根据物理模拟的弹簧运动来平滑过渡。

代码逻辑走读:
1. 导入模块:代码首先从`@kit.ArkUI`模块中导入`curves`对象,这个对象包含了各种用于动画和过渡效果的函数和方法。
2. 创建弹簧插值曲线:调用`curves.interpolatingSpring`方法,传入四个参数:`10`(初始位置)、`1`(初始速度)、`228`(弹簧常数)和`30`(摩擦常数)。这个方法根据这些参数创建一个弹簧插值曲线,曲线的时长和形状由这些参数定义。
3. 曲线应用:生成的曲线可以用于界面元素的动画效果,使其在移动或变化时遵循弹簧运动的物理规律,从而实现平滑、自然的过渡。
本次解答由人工智能生成,仅供参考
*/
  • damping(阻尼):控制弹簧震荡的衰减速度。
    阻尼值越小,弹簧震荡次数越多,衰减越慢(如软弹簧,弹性强);
    阻尼值越大,震荡越快停止,甚至可能无明显回弹(如硬弹簧,接近刚性)。
  • stiffness(刚度 / 劲度系数):控制弹簧的 “硬度”。
    刚度越大,弹簧越 “硬”,运动速度快、回弹幅度小(如金属弹簧);
    刚度越小,弹簧越 “软”,运动更平缓、回弹幅度大(如橡胶弹簧)。
  • mass(质量):模拟被弹簧拉动的物体质量(部分实现中默认固定值)。
    质量越大,动画启动和停止的惯性越强,运动更迟缓;质量越小,响应越灵敏。
  • initialVelocity(初始速度):动画开始时的初始运动速度,影响初始震荡的幅度(如快速滑动后的惯性回弹)。
    from(起始值) 与 to(目标值):定义动画的起始状态和最终稳定的目标状态(如位置、大小、透明度等属性值)。

代码

弹簧曲线动画

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from scipy.interpolate import make_interp_spline
import matplotlib.widgets as widgets

# 设置中文显示
plt.rcParams["font.family"] = ["SimHei", "Heiti TC", "Heiti TC"]
plt.rcParams["axes.unicode_minus"] = False  # 正确显示负号

class ParametricSpringAnimation:
    def __init__(self):
        # 物理参数(默认值)
        self.initial_velocity = -2.0   # 初始速度(负值表示向左运动)
        self.mass = 1.0                # 质量
        self.stiffness = 5.0           # 刚度(劲度系数)
        self.damping = 0.5             # 阻尼系数
        
        # 弹簧基本参数
        self.start_point = np.array([2, 5])  # 弹簧固定端
        self.equilibrium_pos = 8            # 平衡位置X坐标
        self.spring_coils = 12              # 弹簧圈数
        self.coil_height = 0.6              # 线圈高度
        
        # 状态变量
        self.current_pos = self.equilibrium_pos  # 当前位置
        self.current_vel = self.initial_velocity  # 当前速度
        self.time = 0.0                           # 时间
        
        # 动画参数
        self.total_frames = 300                  # 总帧数
        self.fps = 60                            # 帧率
        self.dt = 1.0 / self.fps                 # 时间步长
        
        # 创建图形和轴
        self.fig = plt.figure(figsize=(12, 8))
        self.ax = self.fig.add_axes([0.1, 0.3, 0.8, 0.6])  # 主绘图区
        self.fig.suptitle('参数可控的插值弹簧曲线动画', fontsize=16)
        
        # 设置主坐标轴范围
        self.ax.set_xlim(0, 12)
        self.ax.set_ylim(2, 12)
        self.ax.set_xlabel('X轴')
        self.ax.set_ylabel('Y轴')
        self.ax.grid(True, alpha=0.3)
        self.ax.set_aspect('equal', adjustable='box')
        
        # 初始化绘图元素
        self.spring_line, = self.ax.plot([], [], 'b-', linewidth=3)  # 弹簧曲线
        self.fixed_point, = self.ax.plot([], [], 'ro', markersize=10)  # 固定端点
        self.mass_point, = self.ax.plot([], [], 'go', markersize=15)  # 重物
        self.trace_line, = self.ax.plot([], [], 'r-', linewidth=1, alpha=0.3)  # 轨迹线
        
        # 轨迹记录
        self.trace_points = []
        
        # 信息文本
        self.info_text = self.ax.text(0.02, 0.98, '', transform=self.ax.transAxes, 
                                     verticalalignment='top', bbox=dict(boxstyle='round', facecolor='white', alpha=0.8))
        
        # 添加参数控制面板
        self.add_parameter_controls()
        
    def add_parameter_controls(self):
        """添加参数控制滑块"""
        # 初始速度滑块
        ax_vel = self.fig.add_axes([0.2, 0.2, 0.65, 0.03])
        self.vel_slider = widgets.Slider(
            ax=ax_vel,
            label='初始速度',
            valmin=-5.0,
            valmax=5.0,
            valinit=self.initial_velocity,
            valstep=0.1
        )
        
        # 质量滑块
        ax_mass = self.fig.add_axes([0.2, 0.15, 0.65, 0.03])
        self.mass_slider = widgets.Slider(
            ax=ax_mass,
            label='质量',
            valmin=0.1,
            valmax=5.0,
            valinit=self.mass,
            valstep=0.1
        )
        
        # 刚度滑块
        ax_stiff = self.fig.add_axes([0.2, 0.1, 0.65, 0.03])
        self.stiff_slider = widgets.Slider(
            ax=ax_stiff,
            label='刚度',
            valmin=1.0,
            valmax=20.0,
            valinit=self.stiffness,
            valstep=0.5
        )
        
        # 阻尼滑块
        ax_damp = self.fig.add_axes([0.2, 0.05, 0.65, 0.03])
        self.damp_slider = widgets.Slider(
            ax=ax_damp,
            label='阻尼',
            valmin=0.1,
            valmax=2.0,
            valinit=self.damping,
            valstep=0.1
        )
        
        # 重置按钮
        ax_reset = self.fig.add_axes([0.85, 0.05, 0.1, 0.04])
        self.reset_btn = widgets.Button(ax_reset, '重置')
        
        # 绑定事件处理函数
        self.vel_slider.on_changed(self.update_parameters)
        self.mass_slider.on_changed(self.update_parameters)
        self.stiff_slider.on_changed(self.update_parameters)
        self.damp_slider.on_changed(self.update_parameters)
        self.reset_btn.on_clicked(self.reset_animation)
        
    def init_animation(self):
        """初始化动画元素"""
        self.spring_line.set_data([], [])
        self.fixed_point.set_data([], [])
        self.mass_point.set_data([], [])
        self.trace_line.set_data([], [])
        self.info_text.set_text('')
        return self.spring_line, self.fixed_point, self.mass_point, self.trace_line, self.info_text

    def update_parameters(self, val):
        """更新物理参数"""
        self.initial_velocity = self.vel_slider.val
        self.mass = self.mass_slider.val
        self.stiffness = self.stiff_slider.val
        self.damping = self.damp_slider.val
        self.reset_animation(None)  # 参数改变后重置动画
        
    def reset_animation(self, event):
        """重置动画状态"""
        self.current_pos = self.equilibrium_pos
        self.current_vel = self.initial_velocity
        self.time = 0.0
        self.trace_points = []
        
    def generate_spring_points(self, end_x):
        """生成弹簧上的点并进行插值平滑"""
        end_point = np.array([end_x, self.start_point[1]])
        
        # 计算弹簧总长度和方向
        length = np.linalg.norm(end_point - self.start_point)
        
        # 生成弹簧的控制点
        t = np.linspace(0, 1, self.spring_coils * 2 + 1)
        x = self.start_point[0] + t * (end_point[0] - self.start_point[0])
        
        # 生成弹簧的波动形状
        y = self.start_point[1] + np.sin(t * self.spring_coils * 2 * np.pi) * self.coil_height
        
        # 使用三次样条插值使曲线更平滑
        spl = make_interp_spline(t, np.column_stack((x, y)), k=3)
        t_smooth = np.linspace(0, 1, 200)  # 更密集的点
        smooth_points = spl(t_smooth)
        
        return smooth_points
    
    def calculate_physics(self):
        """根据物理规律计算下一帧状态"""
        # 胡克定律:F = -k(x - x0) - c*v
        displacement = self.current_pos - self.equilibrium_pos
        force = -self.stiffness * displacement - self.damping * self.current_vel
        
        # 牛顿第二定律:a = F/m
        acceleration = force / self.mass
        
        # 更新速度和位置
        self.current_vel += acceleration * self.dt
        self.current_pos += self.current_vel * self.dt
        
        # 限制位置范围,防止弹簧过度拉伸
        if self.current_pos < self.start_point[0] + 1.0:
            self.current_pos = self.start_point[0] + 1.0
            self.current_vel = 0.0
            
        if self.current_pos > 11.0:
            self.current_pos = 11.0
            self.current_vel = 0.0
            
        self.time += self.dt
    
    def update_animation(self, frame):
        """更新动画帧"""
        # 计算物理状态
        self.calculate_physics()
        
        # 生成弹簧曲线点
        spring_points = self.generate_spring_points(self.current_pos)
        
        # 更新弹簧曲线
        self.spring_line.set_data(spring_points[:, 0], spring_points[:, 1])
        
        # 更新固定端点
        self.fixed_point.set_data(self.start_point[0], self.start_point[1])
        
        # 更新重物位置
        self.mass_point.set_data(self.current_pos, self.start_point[1])
        
        # 更新轨迹
        self.trace_points.append([self.time, self.current_pos - self.equilibrium_pos])
        if len(self.trace_points) > 1000:  # 限制轨迹点数量
            self.trace_points.pop(0)
        
        # 绘制轨迹
        if len(self.trace_points) > 1:
            trace_array = np.array(self.trace_points)
            self.trace_line.set_data(trace_array[:, 0], trace_array[:, 1] + self.equilibrium_pos)
        
        # 更新信息文本
        displacement = self.current_pos - self.equilibrium_pos
        self.info_text.set_text(
            f'时间: {self.time:.1f}s\n'
            f'位移: {displacement:.2f}\n'
            f'速度: {self.current_vel:.2f}'
        )
        
        return self.spring_line, self.fixed_point, self.mass_point, self.trace_line, self.info_text
    
    def create_animation(self, save_path=None):
        """创建并显示动画"""
        anim = FuncAnimation(
            self.fig,
            self.update_animation,
            frames=self.total_frames,
            init_func=self.init_animation,
            interval=1000/self.fps,  # 每帧间隔毫秒
            blit=True,
            repeat=True  # 动画循环播放
        )
        
        # 修复matplotlib版本兼容性问题
        anim._resize_id = None
        
        # 如果提供了保存路径,则保存动画
        if save_path:
            try:
                anim.save(save_path, writer='ffmpeg', fps=self.fps)
                print(f"动画已保存至: {save_path}")
            except Exception as e:
                print(f"保存动画失败: {e}")
                print("请确保已安装ffmpeg")
        
        plt.show()

if __name__ == "__main__":
    # 创建并显示弹簧动画
    spring_anim = ParametricSpringAnimation()
    
    # 如需保存动画,取消下面一行的注释并指定路径
    spring_anim.create_animation('parametric_spring_animation.mp4')
    
    # 显示动画
    spring_anim.create_animation()

贝塞尔曲线动画

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import matplotlib.animation as animation

# 设置中文显示
plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]
plt.rcParams["axes.unicode_minus"] = False  # 正确显示负号

class BezierAnimation:
    def __init__(self):
        # 初始化控制点 - 可以修改这些点来获得不同的曲线
        self.controls = np.array([
            [0, 0],    # 起点
            [2, 0],    # 控制点1
            [2, 5],    # 控制点2
            [5, 5]     # 终点
        ])
        
        self.num_points = 100  # 曲线上的点数量
        self.t = np.linspace(0, 1, self.num_points)  # 参数t从0到1
        
        # 创建图形和轴
        self.fig, self.ax = plt.subplots(figsize=(8, 6))
        self.fig.suptitle('贝塞尔曲线动画演示', fontsize=15)
        
        # 设置坐标轴范围
        self.ax.set_xlim(-1, 6)
        self.ax.set_ylim(-1, 5)
        self.ax.set_xlabel('X轴')
        self.ax.set_ylabel('Y轴')
        self.ax.grid(True)
        
        # 初始化绘图元素
        self.control_line, = self.ax.plot([], [], 'r--', alpha=0.6)  # 控制点连接线
        self.control_points, = self.ax.plot([], [], 'ro', markersize=8)  # 控制点
        self.bezier_curve, = self.ax.plot([], [], 'b-', linewidth=2)  # 贝塞尔曲线
        self.animated_point, = self.ax.plot([], [], 'go', markersize=10)  # 曲线上的动画点
        
        # 添加控制点标签
        self.control_labels = [self.ax.text(0, 0, '', fontsize=12) for _ in range(len(self.controls))]
        
        # 动画帧数量
        self.animation_frames = 100
        
    def bezier_curve_calc(self, t):
        """计算贝塞尔曲线上的点"""
        n = len(self.controls) - 1  # 曲线阶数 = 控制点数量 - 1
        result = np.zeros(2)
        
        for i in range(n + 1):
            # 计算二项式系数
            binom = np.math.comb(n, i)
            # 计算贝塞尔基函数
            basis = binom * (t ** i) * ((1 - t) ** (n - i))
            # 累加计算曲线上的点
            result += basis * self.controls[i]
            
        return result
    
    def init_animation(self):
        """初始化动画"""
        self.control_line.set_data([], [])
        self.control_points.set_data([], [])
        self.bezier_curve.set_data([], [])
        self.animated_point.set_data([], [])
        
        for label in self.control_labels:
            label.set_text('')
            
        return (self.control_line, self.control_points, self.bezier_curve, 
                self.animated_point, *self.control_labels)
    
    def update_animation(self, frame):
        """更新动画帧"""
        # 计算当前帧对应的t值
        current_t = frame / self.animation_frames
        
        # 更新控制点显示
        self.control_points.set_data(self.controls[:, 0], self.controls[:, 1])
        self.control_line.set_data(self.controls[:, 0], self.controls[:, 1])
        
        # 更新控制点标签
        for i, label in enumerate(self.control_labels):
            label.set_position((self.controls[i, 0] + 0.1, self.controls[i, 1] + 0.1))
            label.set_text(f'P{i}')
        
        # 计算当前t值范围内的贝塞尔曲线
        curve_points = np.array([self.bezier_curve_calc(t) for t in self.t if t <= current_t])
        
        if len(curve_points) > 0:
            self.bezier_curve.set_data(curve_points[:, 0], curve_points[:, 1])
            
            # 更新动画点(当前t对应的点)
            current_point = self.bezier_curve_calc(current_t)
            self.animated_point.set_data(current_point[0], current_point[1])
        
        return (self.control_line, self.control_points, self.bezier_curve, 
                self.animated_point, *self.control_labels)
    
    def create_animation(self, save_path=None):
        """创建并显示动画"""
        anim = FuncAnimation(
            self.fig, 
            self.update_animation, 
            frames=self.animation_frames + 1,
            init_func=self.init_animation, 
            interval=50,  # 每帧间隔毫秒
            blit=True
        )
        
        # 如果提供了保存路径,则保存动画
        if save_path:
            # 需要安装ffmpeg才能保存为mp4
            anim.save(save_path, writer='ffmpeg', fps=20)
        
        plt.tight_layout()
        plt.show()

if __name__ == "__main__":
    # 创建并显示贝塞尔曲线动画
    bezier_anim = BezierAnimation()
    
    # 如需保存动画,取消下面一行的注释并指定路径
    # 保存并显示动画
    bezier_anim.create_animation('bezier_animation.mp4')
    
    # 显示动画
    bezier_anim.create_animation()

弹簧动画模拟器

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>弹簧曲线模拟器</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
    
    <script>
        tailwind.config = {
            theme: {
                extend: {
                    colors: {
                        primary: '#3B82F6',
                        secondary: '#10B981',
                        accent: '#8B5CF6',
                        dark: '#1E293B',
                        light: '#F8FAFC'
                    },
                    fontFamily: {
                        sans: ['Inter', 'system-ui', 'sans-serif'],
                    },
                }
            }
        }
    </script>
    
    <style type="text/tailwindcss">
        @layer utilities {
            .card-shadow {
                box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.05), 0 8px 10px -6px rgba(0, 0, 0, 0.02);
            }
            .slider-thumb {
                @apply appearance-none w-5 h-5 rounded-full bg-primary cursor-pointer;
            }
            .fixed-dimension {
                width: 400px;
                height: 200px;
                flex-shrink: 0;
                overflow: hidden;
            }
        }
        
        input[type="range"]::-webkit-slider-thumb {
            @apply slider-thumb;
        }
        
        input[type="range"]::-moz-range-thumb {
            @apply slider-thumb;
        }
    </style>
</head>
<body class="bg-gray-50 font-sans text-dark">
    <div class="container mx-auto px-4 py-8 max-w-5xl">
        <header class="text-center mb-10">
            <h1 class="text-[clamp(1.8rem,4vw,2.5rem)] font-bold text-dark mb-2">弹簧曲线模拟器</h1>
            <p class="text-gray-600 max-w-2xl mx-auto">调整参数以模拟不同弹簧特性,实时查看位移-时间曲线</p>
        </header>
        
        <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
            <!-- 控制面板 -->
            <div class="lg:col-span-1">
                <div class="bg-white rounded-xl-6 card-shadowshadow">
                    <h2 class="text-xl font-semibold mb-6 flex items-center">
                        <i class="fa fa-sliders text-primary mr-2"></i>参数控制
                    </h2>
                    
                    <div class="space-y-6">
                        <!-- 初始速度 -->
                        <div>
                            <div class="flex justify-between mb-1">
                                <label for="velocity" class="text-sm font-medium text-gray-700">初始速度</label>
                                <span id="velocity-value" class="text-sm font-medium text-primary">-10</span>
                            </div>
                            <input 
                                type="range" 
                                id="velocity" 
                                min="-50" 
                                max="50" 
                                value="-10" 
                                class="w-full h-2 bg-gray-200 rounded-lg"
                            >
                            <div class="flex justify-between text-xs text-gray-500 mt-1">
                                <span>-50</span>
                                <span>0</span>
                                <span>50</span>
                            </div>
                        </div>
                        
                        <!-- 质量 -->
                        <div>
                            <div class="flex justify-between mb-1">
                                <label for="mass" class="text-sm font-medium text-gray-700">质量</label>
                                <span id="mass-value" class="text-sm font-medium text-primary">1.0</span>
                            </div>
                            <input 
                                type="range" 
                                id="mass" 
                                min="0.1" 
                                max="5" 
                                step="0.1" 
                                value="1.0" 
                                class="w-full h-2 bg-gray-200 rounded-lg"
                            >
                            <div class="flex justify-between text-xs text-gray-500 mt-1">
                                <span>0.1</span>
                                <span>2.5</span>
                                <span>5.0</span>
                            </div>
                        </div>
                        
                        <!-- 刚度 -->
                        <div>
                            <div class="flex justify-between mb-1">
                                <label for="stiffness" class="text-sm font-medium text-gray-700">刚度</label>
                                <span id="stiffness-value" class="text-sm font-medium text-primary">100</span>
                            </div>
                            <input 
                                type="range" 
                                id="stiffness" 
                                min="10" 
                                max="500" 
                                value="100" 
                                class="w-full h-2 bg-gray-200 rounded-lg"
                            >
                            <div class="flex justify-between text-xs text-gray-500 mt-1">
                                <span>10</span>
                                <span>250</span>
                                <span>500</span>
                            </div>
                        </div>
                        
                        <!-- 阻尼 -->
                        <div>
                            <div class="flex justify-between mb-1">
                                <label for="damping" class="text-sm font-medium text-gray-700">阻尼</label>
                                <span id="damping-value" class="text-sm font-medium text-primary">10</span>
                            </div>
                            <input 
                                type="range" 
                                id="damping" 
                                min="0" 
                                max="50" 
                                value="10" 
                                class="w-full h-2 bg-gray-200 rounded-lg"
                            >
                            <div class="flex justify-between text-xs text-gray-500 mt-1">
                                <span>0</span>
                                <span>25</span>
                                <span>50</span>
                            </div>
                        </div>
                        
                        <!-- 初始位置 -->
                        <div>
                            <div class="flex justify-between mb-1">
                                <label for="position" class="text-sm font-medium text-gray-700">初始位置</label>
                                <span id="position-value" class="text-sm font-medium text-primary">100</span>
                            </div>
                            <input 
                                type="range" 
                                id="position" 
                                min="-200" 
                                max="200" 
                                value="100" 
                                class="w-full h-2 bg-gray-200 rounded-lg"
                            >
                            <div class="flex justify-between text-xs text-gray-500 mt-1">
                                <span>-200</span>
                                <span>0</span>
                                <span>200</span>
                            </div>
                        </div>
                        
                        <!-- 动画速度 -->
                        <div>
                            <div class="flex justify-between mb-1">
                                <label for="speed" class="text-sm font-medium text-gray-700">动画速度</label>
                                <span id="speed-value" class="text-sm font-medium text-primary">1.0x</span>
                            </div>
                            <input 
                                type="range" 
                                id="speed" 
                                min="0.1" 
                                max="3" 
                                step="0.1" 
                                value="1.0" 
                                class="w-full h-2 bg-gray-200 rounded-lg"
                            >
                            <div class="flex justify-between text-xs text-gray-500 mt-1">
                                <span>慢速</span>
                                <span>正常</span>
                                <span>快速</span>
                            </div>
                        </div>
                        
                        <div class="pt-4">
                            <button id="simulate-btn" class="w-full bg-primary hover:bg-primary/90 text-white font-medium py-2 px-4 rounded-lg transition-allduration-300 flex items-center justify-center">
                                <i class="fa fa-play mr-2"></i> 开始模拟
                            </button>
                        </div>
                    </div>
                </div>
                
                <div class="bg-white rounded-xl p-6 mt-6 card-shadow">
                    <h2 class="text-xl font-semibold mb-4 flex items-center">
                        <i class="fa fa-info-circle text-accent mr-2"></i>参数说明
                    </h2>
                    <ul class="text-sm text-gray-600 space-y-2">
                        <li class="flex items-start">
                            <i class="fa fa-arrow-right text-primary mt-1 mr-2"></i>
                            <span><strong>初始速度</strong>:物体开始运动的速度,正值向右,负值向左</span>
                        </li>
                        <li class="flex items-start">
                            <i class="fa fa-arrow-right text-primary mt-1 mr-2"></i>
                            <span><strong>质量</strong>:物体的质量,越大惯性越大</span>
                        </li>
                        <li class="flex items-start">
                            <i class="fa fa-arrow-right text-primary mt-1 mr-2"></i>
                            <span><strong>刚度</strong>:弹簧的硬度,越大弹簧越硬</span>
                        </li>
                        <li class="flex items-start">
                            <i class="fa fa fa-arrow-right text-primary mt-1 mr-2"></i>
                            <span><strong>阻尼</strong>:阻力大小,越大震荡衰减越快</span>
                        </li>
                        <li class="flex items-start">
                            <i class="fa fa fa fa-arrow-right text-primary mt-1 mr-2"></i>
                            <span><strong>初始位置</strong>:物体的起始位置,偏离平衡位置的距离</span>
                        </li>
                        <li class="flex items-start">
                            <i class="fa fa-arrow-right text-primary mt-1 mr-2"></i>
                            <span><strong>动画速度</strong>:控制动画播放速度,1.0x为正常速度</span>
                        </li>
                    </ul>
                </div>
            </div>
            
            <!-- 可视化区域 -->
            <div class="lg:col-span-2">
                <div class="bg-white rounded-xl p-6 card-shadow">
                    <!-- 弹簧动画动画演示(固定大小) -->
                    <div class="mb-6">
                        <h2 class="text-lg font-semibold mb-2 flex items-center">
                            <i class="fa fa-film text-secondary mr-2"></i>弹簧动画
                        </h2>
                        <div class="fixed-dimension border borderborderborderborderborder-gray-200 rounded-lg bg-gray-50 relative">
                            <div id="spring-container" class="absolute inset-0 flex items-center px-4">
                                <!-- 墙面 -->
                                <div class="w-3 h-12 bg-gray-400 rounded-sm"></div>
                                
                                <!-- 弹簧 -->
                                <div id="spring" class="flex-1 h-3 flex justifyjustify-between items-centeritems-center mx-1">
                                    <div class="h-full w-1 bg-primary"></div>
                                    <div class="h-full w-1 bg-primary"></div>
                                    <div class="h-full w-1 bg-primary"></div>
                                    <div class="h-full w-1 bg-primary"></div>
                                    <div class="h-full w-1 bg-primary"></div>
                                    <div class="h-full w-1 bg-primary"></div>
                                    <div class="h-full w-1 bg-primary"></div>
                                    <div class="h-full w-1 bg-primary"></div>
                                    <div class="h-full w-1 bg-primary"></div>
                                    <div class="h-full w-1 bg-primary"></div>
                                    <div class="h-full w-1 bg-primary"></div>
                                    <div class="h-full w-1 bg-primary"></div>
                                    <div class="h-full w-1 bg-primary"></div>
                                </div>
                                
                                <!-- 物体 -->
                                <div id="mass-object" class="w-12 h-12 bg-accent rounded-full-full-fullflexitemsitemsitemsitems-centertext-white font-bold text-sm">
                                    m
                                </div>
                            </div>
                        </div>
                    </div>
                    
                    <!-- 曲线图 -->
                    <div>
                        <h2 class="text-lg font-semibold mb-2 flex items-center">
                            <i class="fa fa-line-chart text-primary mr-2"></i>位移-时间曲线
                        </h2>
                        <div class="fixed-dimension border border-gray-200 rounded-lg">
                            <canvas id="spring-chart"></canvas>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        
        <footer class="mt-12 text-center text-gray-500 text-sm">
            <p>弹簧曲线曲线模拟器基于胡克定律模拟:F = -kx - cv</p>
        </footer>
    </div>

    <script>
        // 获取DOM元素
        const velocitySlider = document.getElementById('velocity');
        const velocityValue = document.getElementById('velocity-value');
        const massSlider = document.getElementById('mass');
        const massValue = document.getElementById('mass-value');
        const stiffnessSlider = document.getElementById('stiffness');
        const stiffnessValue = document.getElementById('stiffness-value');
        const dampingSlider = document.getElementById('damping');
        const dampingValue = document.getElementById('damping-value');
        const positionSlider = document.getElementById('position');
        const positionValue = document.getElementById('position-value');
        const speedSlider = document.getElementById('speed');
        const speedValue = document.getElementById('speed-value');
        const simulateBtn = document.getElementById('simulate-btn');
        const massObject = document.getElementById('mass-object');
        const springContainer = document.getElementById('spring-container');
        
        // 更新显示的参数值
        velocitySlider.addEventListener('input', () => {
            velocityValue.textContent = velocitySlider.value;
        });
        
        massSlider.addEventListener('input', () => {
            massValue.textContent = parseFloat(massSlider.value).toFixed(1);
        });
        
        stiffnessSlider.addEventListener('input', () => {
            stiffnessValue.textContent = stiffnessSlider.value;
        });
        
        dampingSlider.addEventListener('input', () => {
            dampingValue.textContent = dampingSlider.value;
        });
        
        positionSlider.addEventListener('input', () => {
            positionValue.textContent = positionSlider.value;
        });
        
        speedSlider.addEventListener('input', () => {
            speedValue.textContent = parseFloat(speedSlider.value).toFixed(1) + 'x';
            // 如果模拟正在运行,实时时更新速度
            if (simulation && simulation.isRunning) {
                simulation.speedFactor = parseFloat(speedSlider.value);
            }
        });
        
        // 初始化图表
        const ctx = document.getElementById('spring-chart').getContext('2d');
        let springChart = new Chart(ctx, {
            type: 'line',
            data: {
                labels: [],
                datasets: [{
                    label: '位移',
                    data: [],
                    borderColor: '#3B82F6',
                    backgroundColor: 'rgba(59, 130, 246, 0.1)',
                    borderWidth: 2,
                    fill: true,
                    tension: 0.1,
                    pointRadius: 0
                }]
            },
            options: {
                responsive: true,
                maintainAspectRatio: false,
                scales: {
                    x: {
                        title: {
                            display: true,
                            text: '时间 (ms)',
                            font: {
                                size: 10
                            }
                        },
                        ticks: {
                            font: {
                                size: 8
                            }
                        }
                    },
                    y: {
                        title: {
                            display: true,
                            text: '位移',
                            font: {
                                size: 10
                            }
                        },
                        min: -250,
                        max: 250,
                        ticks: {
                            font: {
                                size: 8
                            }
                        }
                    }
                },
                animation: false,
                interaction: {
                    intersect: false,
                    mode: 'index'
                },
                plugins: {
                    legend: {
                        labels: {
                            font: {
                                size: 10
                            }
                        }
                    }
                }
            }
        });
        
        // 弹簧模拟类
        class SpringSimulation {
            constructor(params) {
                // 物理参数
                this.stiffness = params.stiffness;  // 刚度
                this.damping = params.damping;      // 阻尼
                this.mass = params.mass;            // 质量
                this.initialPosition = params.position; // 初始位置
                this.initialVelocity = params.velocity; // 初始速度
                this.speedFactor = params.speed || 1.0; // 动画速度因子
                
                // 状态变量
                this.position = params.position;    // 当前位置
                this.velocity = params.velocity;    // 当前速度
                this.time = 0;                      // 时间
                this.history = [];                  // 历史数据
                this.isRunning = false;             // 模拟是否运行
                this.animationFrameId = null;       // 动画帧ID
                this.lastTime = 0;                  // 上一帧时间
                
                // 固定容器宽度(400px减去内边距和元素宽度)
                this.containerWidth = 400 - 30 - 48; // 固定计算,不受窗口影响
                this.centerX = this.containerWidth / 2; // 平衡位置
            }
            
            // 更新模拟状态
            update(currentTime) {
                if (!this.lastTime) this.lastTime = currentTime;
                // 应用速度因子调整时间增量
                const deltaTime = ((currentTime - this.lastTime) / 1000) * this.speedFactor;
                this.lastTime = currentTime;
                
                // 计算加速度: F = -kx - cv, a = F/m
                const acceleration = (-this.stiffness * this.position - this.damping * this.velocity) / this.mass;
                
                // 更新速度和位置
                this.velocity += acceleration * deltaTime;
                this.position += this.velocity * deltaTime;
                
                // 记录时间和位置
                this.time += (currentTime - (this.lastTime - (currentTime - this.lastTime))) / 1000 * 1000;
                this.history.push({
                    time: this.time,
                    position: this.position
                });
                
                // 更新物体位置
                const objectX = this.centerX + this.position;
                massObject.style.transform = `translateX(${objectX}px)`;
                
                // 检查是否应该停止模拟
                if (Math.abs(this.velocity) < 0.1 && Math.abs(this.position) < 0.5) {
                    this.stop();
                    return false;
                }
                
                return true;
            }
            
            // 开始模拟
            start() {
                this.isRunning = true;
                this.lastTime = 0;
                this.history = [];
                this.time = 0;
                
                // 重置图表
                springChart.data.labels = [];
                springChart.data.datasets[0].data = [];
                springChart.update();
                
                // 初始位置
                const initialX = this.centerX + this.initialPosition;
                massObject.style.transform = `translateX(${initialX}px)`;
                
                // 动画循环
                const animate = (timestamp) => {
                    if (!this.isRunning) return;
                    
                    const shouldContinue = this.update(timestamp);
                    
                    // 更新图表
                    if (this.history.length % 2 === 0) {
                        springChart.data.labels.push(Math.round(this.time));
                        springChart.data.datasets[0].data.push(this.position);
                        
                        // 限制图表数据点数量
                        if (springChart.data.labels.length > 100) {
                            springChart.data.labels.shift();
                            springChart.data.datasets[0].data.shift();
                        }
                        springChart.update();
                    }
                    
                    if (shouldContinue) {
                        this.animationFrameId = requestAnimationFrame(animate);
                    }
                };
                
                this.animationFrameId = requestAnimationFrame(animate);
            }
            
            // 停止模拟
            stop() {
                this.isRunning = false;
                if (this.animationFrameId) {
                    cancelAnimationFrame(this.animationFrameId);
                }
            }
        }
        
        // 模拟控制
        let simulation = null;
        
        simulateBtn.addEventListener('click', () => {
            // 如果已有模拟在运行,先停止
            if (simulation && simulation.isRunning) {
                simulation.stop();
            }
            
            // 获取参数
            const params = {
                velocity: parseFloat(velocitySlider.value),
                mass: parseFloat(massSlider.value),
                stiffness: parseFloat(stiffnessSlider.value),
                damping: parseFloat(dampingSlider.value),
                position: parseFloat(positionSlider.value),
                speed: parseFloat(speedSlider.value)
            };
            
            // 创建并启动新模拟
            simulation = new SpringSimulation(params);
            simulation.start();
            
            // 更新按钮文本
            simulateBtn.innerHTML = '<i class="fa fa-refresh mr-2"></i> 重新模拟';
        });
        
        // 初始位置设置
        window.addEventListener('load', () => {
            const containerWidth = 400 - 30 - 48; // 固定值
            const centerX = containerWidth / 2;
            const initialX = centerX + parseFloat(positionSlider.value);
            massObject.style.transform = `translateX(${initialX}px)`;
        });
        
        // 移除窗口大小变化的影响
        window.removeEventListener('resize', () => {});
    </script>
</body>
</html>

弹簧曲线curves.interpolatingSpring(10, 1, 228, 30)

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches

# 设置中文显示
plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]
plt.rcParams["axes.unicode_minus"] = False  # 正确显示负号

def calculate_spring_curve(mass, stiffness, damping, initial_velocity, duration=1.0, fps=100):
    """计算弹簧曲线的数值点"""
    dt = 1.0 / fps
    total_frames = int(duration * fps)
    
    time_points = []
    position_points = []
    velocity_points = []
    
    position = 0.0  # 初始位置在平衡位置
    velocity = initial_velocity
    
    for _ in range(total_frames):
        time = len(time_points) * dt
        
        # 计算力和加速度 (F = -kx - cv, a = F/m)
        force = -stiffness * position - damping * velocity
        acceleration = force / mass
        
        # 更新速度和位置
        velocity += acceleration * dt
        position += velocity * dt
        
        time_points.append(time)
        position_points.append(position)
        velocity_points.append(velocity)
        
    return np.array(time_points), np.array(position_points), np.array(velocity_points)

# 计算ArkUI interpolatingSpring(10, 1, 228, 30)的曲线数据
mass = 10.0
stiffness = 1.0
damping = 228.0
initial_velocity = 16.0

time, position, velocity = calculate_spring_curve(
    mass, stiffness, damping, initial_velocity, duration=1.0
)

# 创建图形
fig, ax = plt.subplots(figsize=(10, 6))
fig.suptitle('curves.interpolatingSpring(10, 1, 228, 30) 数值曲线', fontsize=16)

# 绘制位移曲线
ax.plot(time, position, 'b-', linewidth=2, label='位移')
ax.set_xlabel('时间 (秒)')
ax.set_ylabel('位移')
ax.grid(True, alpha=0.3)
ax.set_xlim(0, max(time))
ax.set_ylim(min(position)*1.1, max(position)*1.1)

# 标记关键 points
peak1_idx = np.argmax(position)
peak1_time = time[peak1_idx]
peak1_pos = position[peak1_idx]

trough1_idx = np.argmin(position)
trough1_time = time[trough1_idx]
trough1_pos = position[trough1_idx]

# 添加关键点标注
ax.plot(peak1_time, peak1_pos, 'ro', markersize=8)
ax.annotate(f'峰值: ({peak1_time:.2f}s, {peak1_pos:.2f})',
            xy=(peak1_time, peak1_pos),
            xytext=(peak1_time+0.05, peak1_pos+0.2),
            arrowprops=dict(arrowstyle='->', color='red'))

ax.plot(trough1_time, trough1_pos, 'go', markersize=8)
ax.annotate(f'谷值: ({trough1_time:.2f}s, {trough1_pos:.2f})',
            xy=(trough1_time, trough1_pos),
            xytext=(trough1_time+0.05, trough1_pos-0.3),
            arrowprops=dict(arrowstyle='->', color='green'))

# 添加参数说明
param_text = (f'参数: mass={mass}, stiffness={stiffness}\n'
              f'damping={damping}, initialVelocity={initial_velocity}')
plt.figtext(0.15, 0.01, param_text, fontsize=10, 
            bbox=dict(facecolor='white', alpha=0.8, boxstyle='round,pad=0.5'))

# 添加图例
ax.legend()

plt.tight_layout(rect=[0, 0.05, 1, 0.95])
plt.show()


网站公告

今日签到

点亮在社区的每一天
去签到