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
基本介绍
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 npimport matplotlib.pyplot as pltfrom matplotlib.animation import FuncAnimationfrom scipy.interpolate import make_interp_splineimport 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 npimport matplotlib.pyplot as pltfrom matplotlib.animation import FuncAnimationimport 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 = \' 重新模拟\'; }); // 初始位置设置 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 npimport matplotlib.pyplot as pltimport 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.0stiffness = 1.0damping = 228.0initial_velocity = 16.0time, 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)# 标记关键 pointspeak1_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()