arkui 动画曲线

参考文档

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

基本介绍

java 复制代码
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(目标值):定义动画的起始状态和最终稳定的目标状态(如位置、大小、透明度等属性值)。

代码

弹簧曲线动画

py 复制代码
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()

贝塞尔曲线动画

python 复制代码
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()

弹簧动画模拟器

html 复制代码
<!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)

python 复制代码
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()
相关推荐
胖胖雕3 天前
LLM增强的网易云API部署用于鸿蒙原生音乐app: Melotopia
docker·node.js·harmony
敲代码的鱼哇15 天前
PDF 预览与签名批注写回 支持安卓 iOS 鸿蒙 UTS插件
android·ios·pdf·鸿蒙·harmony
aqi0021 天前
一文理清 HarmonyOS 6.0.2 涵盖的十个升级点
android·华为·harmonyos·鸿蒙·harmony
aqi001 个月前
一文速览 HarmonyOS 6.0.1 引入的十个新特性
android·华为·harmonyos·鸿蒙·harmony
aqi001 个月前
一文读懂 HarmonyOS 6.1 带来的十大重要升级
android·华为·harmonyos·鸿蒙·harmony
~央千澈~1 个月前
《2026鸿蒙NEXT纯血开发与AI辅助》第六章:「微距」项目启动——工程创建与整体架构设计-卓伊凡
人工智能·harmony·鸿蒙开发·鸿蒙next·harmony os
~央千澈~2 个月前
《2026鸿蒙NEXT纯血开发与AI辅助》第五章:选择成熟方案,创建第一个鸿蒙应用并成功运行-卓伊凡
人工智能·华为·harmonyos·harmony·harmony os
Huanzhi_Lin2 个月前
鸿蒙NEXT出包
华为·harmonyos·鸿蒙·harmony·鸿蒙next·hap
~央千澈~2 个月前
《2026鸿蒙NEXT纯血开发与AI辅助》第二章:DevEco Studio 的基本使用以及arkui的详细介绍-卓伊凡
人工智能·harmony·harmony os
aqi005 个月前
新书《鸿蒙HarmonyOS 6应用开发:从零基础到App上线》出版啦
harmonyos·鸿蒙·harmony