原生 JS 中的物理模拟





5.00/5 (13投票s)
基于 HTML、JavaScript 的 Canvas 物理计算机模型
目录
引言
为了理解物理过程和现象,通常会进行各种实验和演示。软件模拟过程允许您在实验室外观察事件或实验。从最狭义的角度讲,物理计算机模拟是对物理现象真实世界行为的重现。通过这种建模,可以改变许多参数,观察它们如何影响实验,并理解现象的本质。
本文介绍了使用 JavaScript 和 HTML 创建的物理世界模型。这些模型可以在任何平台上的 Web 浏览器中运行。本文涵盖了三个主题:
- 物理学“动力学”章节中的“力的合成(按角度)”
- “液体压强”章节中的“阿基米德原理”
- “机械振动”章节中的“数学摆”
物理主题
力的合成(按角度)
力是物体之间相互作用引起的推或拉。在国际单位制(SI)中,力的单位是牛顿(N)。它是一个矢量量。我们可以对力进行加减。合力是两个或多个力的组合,它产生的加速度与所有这些力产生的加速度相同。
在简单情况下,当一个物体受到两个同方向作用力时,合力的大小等于两个力的大小之和 \(F= F_1 + F_2\)。
在图形上,可以通过平行四边形法则找到两个按角度作用的力的合力。
合力为: \(F= \sqrt{F_1^2 + F_2^2 + 2F_1F_2 \cos \alpha }\)
如果力 \(F_1 \) 等于力 \(F_2 \) ,则可以通过以下公式找到它们: \(F_1 = F_2 = \frac{F}{2 \cos( \alpha/2 ) } \)
阿基米德定律
阿基米德原理指出,浸没在流体中的物体所受的浮力等于物体排开的流体的重量。作用在物体上的向下的力是物体的重量。如果浮力大于重力,物体会上浮;如果浮力小于重力,物体会下沉。如果浮力等于重力,则物体保持静止。
浮力可以表示为 \(F_b = \rho G V \),其中 \(\rho\) 是流体密度 \( (kg/m^3) \),g 是重力加速度 \( (9.81m/s^2) \),V 是物体体积 \( (m^3) \)。物体体积由公式 \(V = m/\rho \) 得到,其中 \(\rho\) 是物体密度,m 是物体质量。
数学摆
数学摆是一个质点(小物体),悬挂在一根细的无质量、不可伸长的绳子或无质量的杆上。摆的运动周期是指摆动一次(来回)所需的时间,以秒为单位。
从角度 \(\alpha\) 开始的单摆周期的公式为 \(T= 2\Pi\sqrt{L/g }\),其中 L 是摆杆的长度,g 是重力加速度 \( (9.81m/s^2) \)。
摆的频率是指每秒来回摆动的次数,以赫兹(Hz)为单位。频率的公式为 \( f = 1/T \)。
HTML、CSS 代码
该项目由几个带有 JavaScript 代码的 HTML 页面组成。主页面是项目根目录下的“index.html”。它在页面左侧包含主菜单。菜单由 DIV
元素组成。左侧放置了一个 IFRAME
元素。用户可以在此操作每个物理主题的参数。物理实验模拟对象的绘制和操作发生在主页面中心的 canvas 对象上。
“BoardArea”文件夹包含加载到主页面 Iframe
元素的 HTML 页面。这些 HTML 页面包含模型的输入控件作为参数。在“css”文件夹中是层叠样式表。文件“controls.css" 应用了与输入元素、按钮、标签相关的样式。“main.css" 包含了“index.html”页面的结构样式,“menu.css" 包含了主页面菜单的构建样式,而“variables.css”文件包含了项目的全局 CSS 变量。所有 JS 代码脚本都位于 "./js" 文件夹中。
JavaScript 代码设计
该项目的代码根据所需目的被分成几个对象。在这个 JS 设计中,使用了一些 Sergey A Kryukov 作者的文章中的思想。
通用方法
整个应用程序的一些通用方法位于对象: application
(负责通用属性和方法)和 contextLayout
(用于应用与 canvas 相关的函数和变量)。代码位于文件“./js/appMethods.js”中。
来自 application
的代码片段
const application = {
arcPath: (2 * Math.PI),
degrToRad: (degrees) => (Math.PI / 180) * degrees,
kgToNewton: (kg) => kg * 9.81,
timeout : ms => new Promise(resolve => setTimeout(resolve, ms)),
// ...
}
这是来自 contextLayout
对象中的代码
const contextLayout = {
/**
* clears whole canvas area
*/
clearCanvas: function (canvas) {
let context = canvas.getContext("2d");
context.clearRect(0, 0, canvas.width, canvas.height);
},// clearCanvas
// ...
}
Canvas 绘制的舞台元素
物理实验有时需要操作各种辅助元素,如重锤、球、钩子。它们有助于实验和演示。为此目的有一个特殊的对象 StageItem
,代码位于“./js/Tools/stageElements.js”。StageItem
是一个 Canvas
绘制的对象,可以派生出额外的项目。
/**
* Canvas drawn object for physics experiments like cargo, brick etc.
*/
function StageItem(options = {}) {
/**
* default values for an item
*/
Object.assign(this, {
x: 0, // left top start X coordinate of item
y: 0, // left top start Y coordinate of item
width: 40, // width of item in pixels
height: 40, // height of item in pixels
fill: "#7aa7ca", // background color of item
strokeColor: "black", // stroke color of item
weight: 1, // weight of item ( can be kg, g, pound etc.)
itemText: "item", // text on item
lineWidth: 2, // stroke width of item
canvas: undefined,
}, options);
//...
}
//...
}
StageItem
具有以下属性: x
, y
是左上角的坐标,width
, height
是项目的宽度和高度,fill
是背景颜色,strokeColor
是边框颜色,weight
是重量(kg、g),text
是绘制在项目上的文本,lineWidth
是绘制对象的边框宽度,canvas
是用于绘制的 canvas。
从 StageItem
派生出两个附加对象: Brick
用于在 canvas 上绘制重锤,Ball
用于在 canvas 上绘制重锤球。
/**
* Canvas drawn brick object
*/
function Brick(options) {
// inheritance from StageItem
StageItem.call(this, options);
// default values for Brick
Object.assign(this, {
roundRadius: 5, // round radius of corners
},
options);
}// Brick
Brick.prototype = Object.create(StageItem.prototype);
Brick.prototype.constructor = Brick;
Brick.prototype.draw = function () {
//...
}
/**
* Canvas drawn ball object
*/
function Ball(options) {
// inheritance from StageItem
StageItem.call(this, options);
// default values for Ball
Object.assign(this, options);
}// Ball
Ball.prototype = Object.create(StageItem.prototype);
Ball.prototype.constructor = Ball;
Ball.prototype.draw = function () {
//...
}
这两个类都有原型方法 draw。下面显示了如何在 canvas 上绘制砖块。
let myCanvas = document.getElementById("Canvas");
let brick = new Brick({
x: 450,
y: 130,
width: 40,
height: 40,
fill: "LightGray",
strokeColor: "black",
itemText: "Brick",
lineWidth: 2,
canvas: myCanvas,
});
brick.draw();
Canvas 上的拖拽
为了与舞台上不同的绘制对象进行交互,即移动它们、悬挂它们,使用了 JS 对象 dragRendering
,它位于单独的文件“./js/dragging.js”中。该代码允许捕获一个可以跟随用户鼠标的对象。dragRendering
对象接受参数 canvas
用于交互,以及一个可拖动项数组 dragElements
。
const dragRendering = {
//canvas for dragging
canvas: undefined,
//elements of stage with id of an element
dragElements: [{
id: undefined, // unique id of an item
isDraggable : true,// is it draggable element on canvas
isDragging : false,// is item currently being dragged on canvas
elem: undefined // draggable element (see stageElement.js, Brick in particular)
}],
/**
* adds dragging elements
*/
addElements: function (elements) {
this.dragElements = elements.slice();
this.dragElements.forEach(element => { element.isDraggable = true; });
this.drawElements();
},
// ...
}
数组 dragElements
接受参数: id
是每个可拖动项的唯一 ID,isDraggable
表示项是否可拖动,isDragging
是一个标志,用于检查项当前是否被拖动,elem
是要移动的对象(参见上面“Canvas 绘制的舞台元素”部分)。为了填充数组 dragElements
,有一个专门实现的功能 addElements
。
现在看三个重要函数: canvasMouseDown
在鼠标指针位于元素内部时在该元素上触发;canvasMouseMove
在鼠标指针捕获元素进行拖动时在该元素上触发;canvasMouseUp
在用户释放元素并停止其在 canvas 上的移动时在该元素上触发。
canvasMouseDown: function (e) { /* ... */ }
canvasMouseMove: function (e) { /* ... */ }
canvasMouseUp: function (e) { /* ... */ }
用户可以在拖动过程中调用自定义函数 refresh
,而无需重新定义 canvasMouseMove
;在鼠标按下事件中调用 startedDragging
,而无需重新定义 canvasMouseDown
;在鼠标抬起事件中调用 stoppedDragging
,而无需重写 canvasMousUp
。
/**
* custom function to determine end of dragging
* @param {*} elem stopped dragging element
*/
stoppedDragging: function (elem) { },
/**
* custom function to determine begin of dragging
* @param {*} elem started dragging element
*/
startedDragging: function (elem) { },
/**
* custom function to draw on canvas during dragging
*/
refresh: function () { },
总的来说,如何在 canvas 上应用拖动效果如下所示。
let myCanvas = document.getElementById("Canvas");
myCanvas.onmousedown = function (e) {
dragRendering.canvasMouseDown(e);
};
myCanvas.onmouseup = function (e) {
dragRendering.canvasMouseUp(e);
};
myCanvas.onmousemove = function (e) {
dragRendering.canvasMouseMove(e);
};
let elementSize = 30;
// Clears dragging elements for the canvas
dragRendering.dragElements = [];
dragRendering.refresh = function () { console.log('dragging'); };
dragRendering.stoppedDragging = function (elem) { console.log('stopped dragging'); };
dragRendering.startedDragging = function (elem) { console.log('started dragging'); };
const bricks = [
{
id: "X", elem: new Brick({
x: 10,
y: 10,
height: elementSize,
width: elementSize,
canvas: myCanvas,
itemText: "X"
})
},
{
id: "y", elem: new Ball({
x: 50,
y: 50,
height: elementSize,
width: elementSize,
canvas: myCanvas,
itemText: "Y"
})
},
];
// Adds bricks to the canvas
dragRendering.addElements(bricks);
主窗口与物理主题之间的通信
每个模拟物理过程的主题对应于一个单独的 const
对象,位于单独的 JS 文件中。这些对象是 forcesbyAngleDemo
、archimedesPrincipleDemo
、pendulumDemo
和 appliancesDemo
。这些对象位于“./js/topicsRender/”文件夹中。每个对象都接受如 canvas、context 等参数,或者具有执行绘制到 canvas、从 IFrame
元素接收数据等实现的方法和函数。
反过来,文件“./js/main.js”允许构建与物理主题对应的 HTML 菜单,并根据所选菜单项重新绘制 canvas 区域。为此,它有一个特殊的对象 applicationRendering
。
const applicationRendering = {
topics: { forcesAdditionByAngleId: 1, ArchimedesPrincipleId: 2 ,
pendulumDemoId : 3, AppliancesDemoId: 4}, // data attributes from the
// HTML menu corresponding to the physical topics
currentTopic: undefined, // to set current selected topic from HTML the menu
canvas: application.canvas, // main canvas in the application
// ...
/**
* initial initialization of the application
*/
initialInit: function () {
contextLayout.stretchCanvas(this.canvas, "divCanvas");
dragRendering.canvas = forcesbyAngleDemo.canvas =
archimedesPrincipleDemo.canvas = pendulumDemo.canvas =
appliancesDemo.canvas = this.canvas;
forcesbyAngleDemo.ctx = archimedesPrincipleDemo.ctx =
pendulumDemo.ctx = appliancesDemo.ctx = this.context;
this.renderMenu();
}, // initialInit
//...
/**
* renders the HTML menu
*/
renderMenu: function () {
//...
elementDiv.onclick = function () {
//...
//renders canvas depending on the current topic
switch (applicationRendering.currentTopic) {
case applicationRendering.topics.forcesAdditionByAngleId:
forcesbyAngleDemo.init();
break;
case applicationRendering.topics.ArchimedesPrincipleId:
archimedesPrincipleDemo.init();
break;
case applicationRendering.topics.pendulumDemoId:
pendulumDemo.init();
break;
case applicationRendering.topics.AppliancesDemoId:
appliancesDemo.init();
break;
default:
// no topic provided
} // switch
} //elementDiv.onclick
//...
}
}
}; // applicationRendering
主窗口与 Iframe 之间的通信
在物理实验中,需要根据不同的公式进行计算。每个物理主题对象对应一个单独的 HTML 页面,其中包含输入元素,允许更改变量并将其传递到所需公式。这些 HTML 页面可以根据所选的物理主题加载到内联 IFrame
中。
“./BoardArea/FluidMechanics/ArchimedesPrinciple.html” HTML 页面,包含“阿基米德原理”主题的输入元素和参数。
为了将数据从 IFrame
传递到主窗口,使用了方法 parent.postMessage
。要接收数据,主窗口必须有一个事件处理程序 window.addEventListener('message')
。来自单独文件“framesRendering.js”的常量对象 frameRender
会捕获参数页面中每个输入元素的 change
事件,并将消息发送到父主窗口。主窗口以 JSON 格式接收此消息,并将其进一步传递给物理主题。
const frameRender = {
/**
* custom function to send message from IFrame to parent
*/
passControlsMessage: function () { },
bindEvents: function () {
let passMsg = this.passControlsMessage.bind(this);
let divElement = document.querySelector('body');
// all input elements
let inputElements = divElement.querySelectorAll('input');
for (let elem of inputElements) {
elem.oninput = function () {
setRangeLabelValue();
passMsg();
}
}
}
window.onload = function () {
frameRender.bindEvents();
frameRender.passControlsMessage();
};
从 IFrame
中的“ArchimedesPrinciple.html”页面向父窗口传递数据
frameRender.passControlsMessage = function () {
let bodyDensity = document.getElementById("bodyDensityTextBox").value;
let pass_data = {
'bodyDensity': bodyDensity
};
parent.postMessage(JSON.stringify(pass_data), "*");
}
在主窗口中从 IFrame
接收数据
window.addEventListener(
"message",
function (e) {
var key = e.message ? "message" : "data";
var data = e[key];
applicationRendering.topicVariables = JSON.parse(data);
applicationRendering.receiveData();
},
false
);
反之,要向 IFrame
发送消息,则使用父窗口中的 frameEl.postMessage
方法。要在框架内接收消息,会应用 window.addEventListener('message')
。
JavaScript 绘制的物理仪器
实验室实验的装置是特殊的设备,允许您收集、分析、计算和处理从特定现象和效应中获得的信息。该项目中有几种这样的 canvas 绘制项,它们被放置在“./js/Tools”文件夹的单独文件中。
箭头测力计
测力计是用于测量力、力矩(扭矩)或功率的设备。有几种类型的测力计:机械测力计(箭头式和弹簧式)以及电子式。
要创建箭头式测力计,使用了两个 canvas 绘制对象 Appliance
和 Dynamometer
。这些对象分别放置在文件“./js/Tools/appliance.js”和“./js/Tools/dynamometer.js”中。Appliance
对象模拟了仪表盘的表盘,带有指针。它有两个原型方法。draw
方法绘制表盘,它是一组带有阴影的填充圆。第二个原型方法 drawPointer
绘制具有所需宽度和角度的指针。
/**
* Canvas drawn object to create face of gauge tools like clocks, dynamometers, timers
* @options {obj} destructing parameters
*/
function Appliance(options = {}) {
// default values for the appliance
Object.assign(this, {
centerX: 0, // X center coordinate
centerY: 0, // Y center coordinate
radius: 50, // radius of the appliance's face
angle: 0, // applies rotation angle around center's point
ringColor: "#5f5f5f", // color of outer ring
innerRingColor: "#e9e9e9", // color of the second outer ring
backRingColor: "#7e7e7e", // color of background ring
pointerColor: "#d95358", // color of arrow pointer
canvas: undefined // canvas to draw on
}, options);
// ....
Appliance.prototype = {
/**
* Draws appliance
*/
draw : function () {
// ...
}
// Draws arrow indicator
drawPointer : function (angle = 0, arcWidth = 10) {
// ...
}
}
Appliance
对象具有以下属性: centerX
, centerY
是中心坐标,radius
是仪表盘的半径,angle
应用于围绕设备中心点的旋转角度,ringColor
是外环的颜色,innerRingColor
是第二个外环的颜色,backRingColor
是背景环的颜色,pointerColor
是指针的颜色,canvas
是用于绘制的 canvas。
下面展示了如何在 canvas 上绘制仪表盘的示例。
let myCanvas = document.getElementById("Canvas");
let appliance = new Appliance({
centerX: 600,
centerY: 300,
radius: 100,
canvas: myCanvas
});
appliance.draw();
appliance.drawPointer(45);
Dynamometer
对象继承自 Appliance
类。它有三个原型方法。draw
方法用于在 canvas 上绘制此测力计。第二个方法是 setStaticValue
,它允许绘制给定值的指针。方法 setValue
允许通过动画绘制给定值的指针。方法 setValue
是异步的,并返回一个 Promise。
Dynamometer
具有附加属性: maxValue
是测力计的最大值,value
是测力计的当前值,rotateStep
是指针的旋转速度(即,在时间段内的值变化)。
/**
* Canvas drawn dynamometer
*/
function Dynamometer(options) {
// inheritance from Appliance
Appliance.call(this, options);
// default values for Dynamometer
Object.assign(this, {
maxValue: 10, // maximum possible value of dynamometer
value: 0, // current value of dynamometer
rotateStep : undefined // speed of rotation of the arrow
// (i.e. change of value by time period)
}, options);
// ...
}
Dynamometer.prototype = {
/**
* draws entire Dynamometer
*/
draw: function () {
// …
},
/**
* sets arrow indicator to value without animation
*/
setStaticValue: function (valuePointer = 0) {
// …
}, // setStaticValue
/**
* sets arrow indicator to value with animation
*/
setValue: async function (valuePointer = 0) {
// …
}
下面展示了如何在 canvas 上绘制 dynamometer
的示例。
let myCanvas = document.getElementById("Canvas");
let dynamometer = new Dynamometer({
centerX: 250,
centerY: 300,
radius: 100,
ringColor: "red",
innerRingColor: "yellow",
canvas: myCanvas,
rotateStep : 0.1,
angle: 45,
maxValue: 20
});
dynamometer.draw();
dynamometer.setValue(-12).then(function () {
alert('Value was set');
})
Spring
弹簧对象是 canvas 绘制的正弦波。弹簧的每个匝都是从 0 到 360 度范围内的正弦曲线。所需匝数可以模拟弹簧的匝数。下面展示了如何绘制弹簧模拟。
let ctx = document.getElementById("Canvas").getContext("2d");
let startX = 200; // begin x coord. of spring
let startY = 300; // begin y coord. of spring
let radius = 50; // radius of sin wave
ctx.save();
ctx.lineCap = "round";
let totalLength = 300; //total length on spring
let swings = 5; // number of swings on spring
let swingLength = totalLength / swings; // length of each short swing
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = 'black';
let currentSwing = 0;
// drawing of an each swing in cycle
while (currentSwing < swings) {
let beginX = startX + swingLength * currentSwing;
let endX = beginX + swingLength;
let waveStepX = (endX - beginX) / 360;
// drawing of particular swing
for (let x = 0; x <= endX - beginX; x += waveStepX) {
y = startY - (Math.sin((1 / waveStepX) * (x * Math.PI / 180))) * radius;
ctx.lineTo(beginX + x, y);
}
currentSwing++;
}
ctx.stroke();
ctx.restore();
为了创建弹簧,使用了特殊的 Spring
对象及其所需属性。为了绘制对象,有一个特殊的原型方法 draw
。
/**
* Canvas drawn spring object
*/
function Spring(options = {}) {
// default values for the spring
Object.assign(this, {
canvas: undefined,
swingFrontColor: "lightGray", // color of front swing
swingBackColor: "gray", // color to simulate back swing
startX: 0, // star X coordinate of spring
startY: 0, // star Y coordinate of spring
length: 100, // length of spring
radius: 50, // radius of sine wave
swings: 5, // count of swings
angle: 0, // rotation angle relatively of upper left corner
swingWidth: 3 // width of each swing
}, options);
}
Spring.prototype.draw = function () {
// ...
}
Spring
的属性是: canvas
是用于绘制的 canvas,startX
, startY
是正弦波的起始坐标,swingFrontColor
是正面匝的颜色,<color>swingBackColor
是背面匝的颜色,length
是弹簧的总长度,radius
是正弦波的半径,swings
是匝数,angle
应用于围绕左上角的旋转角度,swingWidth
是每个匝的像素宽度。
标尺
Ruler
对象在 canvas 上绘制度量标尺以测量长度。用户可以设置此对象允许的最大值。Ruler
被分成与最大值成比例的刻度线。
/**
* Canvas drawn ruler object
*/
function Ruler(options = {}) {
// default values for ruler
Object.assign(this, {
canvas: undefined,
startX: 0, // left top start X coordinate of ruler
startY: 0, // left top start Y coordinate of ruler
length: 200, // length of ruler
height: 50, // height of ruler
maxValue: 10, // max ruler value
backColor: "orange", // background color of ruler
strokeColor: "black",// line color
showBackground: true,//shows/hides background of ruler
showBottom: true, //shows/hides bottom strokes of ruler
angle: 0, // rotation angle relatively of upper left corner
}, options);
}
Ruler.prototype.draw = function () {
// ...
}
Ruler
具有以下属性: canvas
是用于绘制的 canvas,startX
, startY
是左上角的起始坐标,length
, height
是标尺的长度和高度,maxValue
是最大值,backColor
是背景颜色,strokeColor
是线条颜色,showBackground
允许显示或隐藏背景,showBottom
允许显示或隐藏标尺的底部刻度,angle
应用于围绕左上角的旋转角度。
下面展示了如何在 canvas
上绘制 ruler
。
let myCanvas = document.getElementById("Canvas");
let ruler = new Ruler({
startX: 200,
startY: 50,
canvas: myCanvas,
length: 500,
height: 60,
strokeColor: "black",
maxValue: 500
});
ruler.draw();
弹簧测力计
在弹簧测力计中,力传递到弹簧,弹簧根据力的方向被压缩或拉伸。弹簧变形量与力成正比。
SpringDynamometer
的原型有三个函数: draw
方法用于在 canvas 上绘制此测力计,它绘制整个测力计;setStaticValue
方法用于绘制给定值的指示器;setValue
方法用于通过动画绘制给定值的指示器。方法 setValue
是异步的,并返回一个 Promise。
function SpringDynamometer(options = {}) {
// default values for the ruler
Object.assign(this, {
canvas: undefined, // canvas to draw on
startX: 0, // left top start X coordinate of spring dynamometer
startY: 0, // // left top start Y coordinate of spring dynamometer
length: 200, // length of spring dynamometer
height: 50, // height of spring dynamometer
maxValue: 10, // maximum possible value of spring dynamometer
backColor: "orange", // background color of ruler
strokeColor: "black", // line color
angle: 0, // rotation angle relatively of upper left corner
animatedHolder: false, // are holder and hook animated
value: 0, // current value of spring dynamometer
animationStep: undefined // speed of animation for spring
}, options);
SpringDynamometer.prototype = {
/**
* draws entire Spring Dynamometer
*/
draw: function () {
// …
}
/**
* sets spring indicator to value without animation
*/
setStaticValue: function (valuePointer = 0) {
// …
}
/**
* sets spring indicator to value with animation
*/
setValue: async function (valuePointer = 0) {
// …
}
SpringDynamometer
的属性是: canvas
是用于绘制的 canvas,startX
, startY
是左上角的起始坐标,length
, height
是弹簧测力计的长度和高度,maxValue
是最大值,backColor
是背景颜色,strokeColor
是线条颜色,angle
应用于围绕左上角的旋转角度,animatedHolder
决定是否动画显示带有挂钩的支架,value
是测力计的当前值,animationStep
是支架旋转的动画速度(即,在时间段内的值变化)。
下面展示了如何应用弹簧测力计的属性并将其绘制到 canvas 上的示例。
let myCanvas = document.getElementById("Canvas");
let springDynamometer = new SpringDynamometer({
startX: 100,
startY: 50,
canvas: myCanvas,
angle: 90,
length: 400,
height: 60,
strokeColor: "black",
value: 0,
animatedHolder: true,
maxValue: 50
});
springDynamometer.draw();
springDynamometer.setValue(30).then(function () {
springDynamometer.setValue(0);
});
JavaScript 物理模型
“力的合成(按角度)”模型
文件“forcesbyAngleRender.js”中的 forcesbyAngleDemo
对象负责创建模拟力合成的模型。它应用两个测力计,可以按一定角度放置,以及三个可以拖拽到舞台上并在测力计下方的细线上悬挂的砖块。通过改变砖块的重量和测力计之间的角度等参数,用户可以计算施加到测力计上的总力。
这是来自 forcesbyAngleDemo
的代码片段。
const forcesbyAngleDemo = {
dynamometers: undefined,
canvas: undefined,
ctx: undefined,
// …
init: function () {
this.applySettings();
this.dynamometers = forcesbyAngleDemo.initRender();
dragRendering.refresh =
function () { forcesbyAngleDemo.refreshDrawDrag(); };
dragRendering.stoppedDragging =
function (elem) { forcesbyAngleDemo.stoppedDrawDrag(elem); };
dragRendering.startedDragging =
function (elem) { forcesbyAngleDemo.startedDrawDrag(elem); };
},
/**
* Initial placement of canvas' elements
*/
initRender: function () {
//...
// Draws box and thread on canvas
this.drawBox();
this.drawRope();
// ...
// Draws dynamometers
dynamLeft.draw();
dynamRight.draw();
},
/**
* Function to call when started dragging
*/
startedDrawDrag: function (dragElem) {
let el = dragRendering.findElem(dragElem.id).elem;
// ...
},
/**
* Function to call when stopped dragging
*/
stoppedDrawDrag: function (dropElem) {
// suspends bricks under the dynamometers
// ...
// sets value for the dynamometers
this.setDynamValues();
// sends data to the IFrame
this.passFrameValue();
},
setDynamValues: function () {
this.dynamometers.dynamLeft.setValue(this.getForce()).then(() => {
this.setDraggingFlags();
this.passFrameValue();
})
// ...
},
}
“阿基米德定律”模型
常量 archimedesPrincipleDemo
应用了一个弹簧测力计和一个砖块,该砖块可以被拖拽并悬挂在测力计下方的细线上。测力计可以移向装有液体的盒子,以显示施加在它上面的浮力。
这是来自 archimedesPrincipleDemo
的代码片段。
const archimedesPrincipleDemo = {
dynamometer: undefined,
canvas: undefined,
ctx: undefined,
cancelTimer: false,
init: function () {
this.applySettings();
this.dynamometers = archimedesPrincipleDemo.initRender();
dragRendering.refresh =
function () { archimedesPrincipleDemo.refreshDrawDrag(); };
dragRendering.stoppedDragging =
function (elem) { archimedesPrincipleDemo.stoppedDrawDrag(elem); };
dragRendering.startedDragging =
function (elem) { archimedesPrincipleDemo.startedDrawDrag(elem); };
},
refreshDrawDrag: function () {
this.drawBox();
this.drawLiquidBoxBig();
this.dynamometers.springDynamometer.draw();
},
/**
* Function to call when stopped dragging
*/
stoppedDrawDrag: function (dropElem) {
// suspends the brick under the dynamometer
// ...
// sets value for the dynamometer and moves it
this.dynamometers.springDynamometer.setValue
(gravityForce).then(function () {
archimedesPrincipleDemo.animatePosition().then(function () {
//...
})
})
// sends data to the IFrame
this.passFrameValue(volume, buyonantForce,
application.roundTwoDigits(this.getForce()));
}
}
“数学摆”模型
常量对象 pendulumDemo
在 canvas 上绘制两个动画摆,并应用摆的长度及其起始运动角度等参数。setTimeout
函数已用于创建周期性动画,该动画与真实计时器精确同步。
const pendulumDemo = {
canvas: undefined,
ctx: undefined,
timer: undefined,
settings: {
pendulumLength: 0,
pendulumLength2: 0,
pendulumCoord: { x: 0, y: 0 }, // pendulum's center coord
},
// ...
animatePendulum: function (startAngle, timeInterval, startAngle2, timeInterval2) {
let interval = 20; // interval for the timer in ms
var expected = Date.now() + interval;
this.timer = setTimeout(step, interval);
let canvas = this.canvas;
// ...
function step() {
var dt = Date.now() - expected;
contextLayout.clearCanvas(pendulumDemo.canvas);
expected += interval;
//...
pendulumDemo.drawPendulum
(currentPosition.x, currentPosition.y, 'red');
pendulumDemo.drawPendulum
(currentPosition2.x, currentPosition2.y, 'blue');
pendulumDemo.timer = setTimeout(step, Math.max(0, interval - dt));
}
}
}
在线演示
在线演示可在 此处 找到。
历史
- 2020年9月28日:初始版本