Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web

Physical Simulations in Vanilla JS

5.00/5 (13 votes)
29 Sep 2020CPOL12 min read 14.4K   180  
JavaScript, HTML based physical computer models on canvas
This is an article about physical computer models, i.e., simulation of physical experiments, processes or idealized model situations encountered in the physical tasks.

Table of Contents

Introduction

To understand physics processes and phenomenons, different experiments and demonstrations are often used. Software simulation of processes allow you to see an event or experiment outside laboratory. In its narrowest sense, a physics computer simulation is a recreation of a real-world behavior of a physical phenomenon. With the help of such modeling, it is possible to change many parameters, observe how this affects the experiment and understand the essence of the phenomenon.

This article describes the creation of JavaScript, HTML based models from the world of physics. It allows to run them on any platform via web browsers. Three subjects are covered here:

  1. “Addition of forces by an angle” from the “Dynamics” chapter of physics
  2. “Archimedes’ Principle” from the chapter “Pressure of liquids”
  3. “Mathematical pendulum” from the chapter ”Mechanical oscillations”

Physical Topics

Addition of Forces by an Angle

A force is a push or pull upon an object resulting from the object's interaction with another object. In the International System of Units (SI) unit of force is the newton (N). It is a vector quantity. We can add or subtract forces. Resultant force is the combination of two or more forces, it produces the same acceleration as all those forces.

In the simple case in which an object is subject to two forces that act in the same direction, the resultant has the magnitude equal to the sum of the two magnitudes \(F= F_1 + F_2\).

Simple forces addition

Graphically, the resultant force of two forces applied by an angle can be found by using the parallelogram rule:

Forces addition

The resultant force is: \(F= \sqrt{F_1^2 + F_2^2 + 2F_1F_2 \cos \alpha }\)

If the force \(F_1 \) equals to the force \(F_2 \) , then they can be found by the equation: \(F_1 = F_2 = \frac{F}{2 \cos( \alpha/2 ) } \)

Archimedes' Law

Archimedes' principle states that the buoyant force on a submerged object is equal to the weight of the fluid that is displaced by the object. The force, which acts downward on an object, is the weight of an object. If the buoyant force is more than the weight, the object rises, if it is less, the object sinks. If the buoyant force is equal to the weight, then an object stays put.

The buoyant force can be expressed as \(F_b = \rho G V \), where \(\rho\) is the density of fluid \( (kg/m^3) \), g is the acceleration of gravity \( (9.81m/s^2) \), V is the volume of body \( (m^3) \). Volume of body is found by the equation \(V = m/\rho \), where \(\rho\) is the density of body, m is the weight of body.

Archimedes' Law

Mathematical Pendulum

A mathematical pendulum is a material point (a small body) that is suspended on a thin weightless inextensible string or on a weightless rod. The period of the motion for a pendulum is how long it takes to swing back-and-forth and measured in seconds.

Pendulum

The equation for the period of a simple pendulum starting at an angle \(\alpha\) is \(T= 2\Pi\sqrt{L/g }\), where L is the length of the rod, g is the acceleration of gravity \( (9.81m/s^2) \).

The frequency of a pendulum is how many back-and-forth swings there are in a second, measured in hertz. The equation for the frequency is \( f = 1/T \).

HTML, CSS Code

The project consists of several HTML pages with JavaScript coding. The main page is “index.html” in the root of project. It contains the main menu in the left side of the page. The menu is made out of DIV elements. In the left side is located IFRAME element. Here, the users can manipulate by parameters of each topic of physics. Drawing and manipulation of simulations' objects for physical experiments occurs on the object canvas in the center of the main page.

Main area

The folder “BoardArea” contains HTML pages to load into the Iframe element on the main page. These HTML pages contain input controls as parameters for models. In the folder “css” are located Cascading Style Sheets. The file “controls.css" applies styles regarding input elements, buttons, labels. “main.css" contains styles for creation structure of the “index.html” page, “menu.css" contains styles for building menu on the main page and “variables.css” file contains global CSS variables of the project. All JS code scripts are located in the folder "./js”.

Structure

JavaScript Code Design

The project code is divided into several objects, according to required purposes. In this JS design has been used some ideas of the article by the author Sergey A Kryukov.

General Methods

Some common methods for entire application are located in the objects: application , which is responsible for properties and methods in common, and the object contextLayout to apply functions and variables concerning canvas. The code is located in the file "./js/appMethods.js".

The fragment from application:

JavaScript
   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)),
         // ...
}

This is the code from the object contextLayout:

JavaScript
   const contextLayout = {

         /**
         * clears whole canvas area
         */
         clearCanvas: function (canvas) {
             let context = canvas.getContext("2d");
             context.clearRect(0, 0, canvas.width, canvas.height);
         },// clearCanvas

         // ...
}

Canvas Drawn Items of Stage

Time to time physics experiments require to manipulate with different secondary elements like plummets, balls, hooks. They help with experiments and demonstrations. There is special object for this purpose StageItem, the code is in "./js/Tools/stageElements.js". StageItem is a Canvas drawn object from which can derive additional items.

JavaScript
   /**
   * 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 has the following properties: x, y are the coordinates of left upper corner, width, height are the width and height of an item, fill is the background color, strokeColor is the stroke color, weight is the weight (kg, g), weight is the text drawn on item, lineWidth is the stroke width of drawn object, canvas is the canvas to draw on.

From StageItem, derive two additional objects: Brick to draw plummet on canvas, Ball to draw plummet ball on canvas.

JavaScript
 /**
 * 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 () {
       //...
}

These two classes have prototype method draw. Drawing of brick on canvas is shown below:

JavaScript
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();

Items of stage

Dragging on Canvas

To interact with different drawn objects on stage, i.e., to move them, to suspend them is used JS object dragRendering from the separate file "./js/dragging.js". The code allows catching an object that can follow the user's mouse. The object dragRendering accepts parameter canvas to interact with and an array of draggable items dragElements.

JavaScript
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();
  },

// ...
}

The array dragElements accepts parameters: id is unique ID of each draggable item, isDraggable can an item be dragged, isDragging is flag to check that item is currenly being dragged, elem is object to move (see Canvas drawn items of stage section above). To populate the array dragElements, there is specially implemented function addElements.

Now look at the three important functions: canvasMouseDown is raised at an element when mouse pointer is inside the element, canvasMouseMove is fired at an element when mouse pointer captured the element for dragging, canvasMouseUp is raised at an element when the user releases the element and stops its movement on canvas.

JavaScript
canvasMouseDown: function (e) {  /* ... */ }
canvasMouseMove: function (e) {  /* ... */ }
canvasMouseUp: function (e) {  /* ... */ }

The user can call custom functions refresh during dragging without redefining canvasMouseMove, startedDragging during mouse down event without redefining canvasMouseDown, stoppedDragging during mouse up event without rewriting canvasMousUp.

JavaScript
 /**
 * 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 () { },

In general, how to apply dragging on canvas is shown below:

JavaScript
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);

Communication between Main Window and Physical Topics

Each topic to simulate physical process corresponds to separate const object in the separate JS files. These objects are forcesbyAngleDemo, archimedesPrincipleDemo, pendulumDemo and appliancesDemo. The objects are located in the folder "./js/topicsRender/". Each object accepts parameters like canvas, context or has methods and functions to perform implementation like drawing on canvas, receiving data from the IFrame element.

In turn the file "./js/main.js" allows to build HTML menu corresponding to the physical topics and redraw the canvas area depending on the selected menu item. For this purpose, it has the special object applicationRendering.

JavaScript
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

Communication between Main Window and Iframe

During physical experiments, it is required to perform calculations based on different formulas. To each physical topics object corresponds separate HTML page with input elements which allows to change and pass variables into the required formula. These HTML pages can be loaded into inline IFrame depending on the selected physical topic.

The HTML page "./BoardArea/FluidMechanics/ArchimedesPrinciple.html" with input elements and parameters for the topic "Archimedes' Principle".

IFrame fro Archimedes' principle

In order to pass data to the main window from the IFrame is used method parent.postMessage. To receive data, the main window must have an event handler window.addEventListener('message'). The constant object frameRender from the separate file "framesRendering.js" catches change event for each input element on HTML page with parameters and sends message to the parent main window. The main window receives this message in JSON format and passes further to physical topic.

JavaScript
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();
  };

Passing data from the page "ArchimedesPrinciple.html" inside the IFrame to the parent window:

JavaScript
frameRender.passControlsMessage = function () {
      let bodyDensity = document.getElementById("bodyDensityTextBox").value;
      let pass_data = {
            'bodyDensity': bodyDensity
        };

        parent.postMessage(JSON.stringify(pass_data), "*");
}

Receiving data in the main window from IFrame:

JavaScript
window.addEventListener(
      "message",
      function (e) {
        var key = e.message ? "message" : "data";
        var data = e[key];
        applicationRendering.topicVariables = JSON.parse(data);
        applicationRendering.receiveData();
      },
      false
    );

Conversely, to send message to the IFrame, frameEl.postMessage method from the parent window is used. To receive message inside the frame, window.addEventListener('message') is applied.

JavaScript Drawn Physical Appliances

Contraptions for laboratory experiments are special devices that allows you to collect, analyze, calculate and process information obtained from certain phenomena and effects. The project has several such canvas drawn items and they are placed in separate files in the folder "./js/Tools".

Arrow Dynamometer

A dynamometer is a device for measuring force, moment of force (torque), or power. There are several types of dynamometers: mechanical dynamometers (arrow and spring) as well as electrical.

To create an arrow dynamometer are used two canvas drawn objects Appliance and Dynamometer. These objects are placed in the files "./js/Tools/appliance.js" and "./js/Tools/dynamometer.js". Appliance object simulates face of gauge control with arrow. It has two methods of prototype. The method draw draws face like a set of filled circles with shadows. The second method of prototype drawPointer draws arrow pointer with required width and angle.

JavaScript
  /**
  * 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) {
              // ...
        }
  }

The object Appliance has the following properties : centerX, centerY are coordinates of center, radius is the radius of gauge, angle applies rotation angle around appliance's center point, ringColor is the color of outer ring, innerRingColor is the color of the second outer ring, backRingColor is the color of background ring, pointerColor is the color of arrow's pointer, canvas is the canvas to draw on.

The example of how to draw appliance on canvas is shown below:

JavaScript
let myCanvas = document.getElementById("Canvas");

let appliance = new Appliance({
      centerX: 600,
      centerY: 300,
      radius: 100,
      canvas: myCanvas
  });

  appliance.draw();
  appliance.drawPointer(45);

Appliance

The object Dynamometer inherits from the class Appliance. it has three methods of prototype. To draw this dynamometer on canvas, there is the method draw. The second method is setStaticValue, which allows drawing arrow pointer for applied value. The method setValue allows drawing arrow pointer for applied value with animation. The method setValue is asynchronous and returns promise.

Dynamometer has additional properties: maxValue is the maximum value of dynamometer, value is the current value of dynamometer, rotateStep speed of rotation of the arrow (i.e., change of value by time period)

JavaScript
 /**
 * 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) {
    //
     }

The example of how to draw dynamometer on canvas is shown below:

JavaScript
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');
  })

 Arrow dynamometer

Spring

Spring object is a canvas drawn sine wave. Each turn of spring is a sinusoid in the range from 0 to 360 degrees. The required number of turns can simulate turns of spring. How to draw simulation of spring is shown below:

JavaScript
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();

For creation of spring, special object Spring with required properties is used. To draw the object, there is a special method draw of prototype.

JavaScript
/**
* 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 () {
          // ...
    }

The properties of Spring are: canvas is canvas to draw on, startX, startY are the beginning coordinates of sinus wave, swingFrontColor is the color of front turn, <color>swingBackColor is the color of back turn, length is the total length of spring, radius is the radius of sinus wave, swings is the count of turns, angle applies rotation angle around upper left corner, swingWidth is the width of each swing in pixels.

Spring

Ruler

The object Ruler draws metric scale on canvas to measure length. The user is allowed to apply maximum value of this object. Ruler is split by strokes proportionally to maximum value.

JavaScript
 /**
 * 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 has the properties: canvas is canvas to draw on, startX, startY are the beginning coordinates of left top corner, length, height are the length and height of ruler, maxValue is the maximum value , backColor is the background color, strokeColor is the line color, showBackground allows showing or hiding of background, showBottom allows showing or hiding bottom strokes of ruler, angle applies rotation angle around upper left corner.

Drawing of ruler on canvas is shown below:

JavaScript
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();

Ruler

Spring Dynamometer

In a spring dynamometer, force is transferred to the spring, which, depending on the direction of force, is compressed or stretched. The amount of spring deformation is proportional to force.

The prototype of SpringDynamometer has three functions: to draw this dynamometer on canvas, there is the method draw, which draws entire dynamometer on canvas, setStaticValue, which sets drawn indicator for applied value, setValue, which animates drawn indicator for applied value. The method setValue is asynchronous and returns promise.

JavaScript
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) {
    //
    }

The properties of SpringDynamometer are: canvas is the canvas to draw on, startX, startY are the beginning coordinates of left top corner, length, height are the length and height of spring dynamometer, maxValue is the maximum value ,backColor is the background color, strokeColor is the line color, angle applies rotation angle around upper left corner, animatedHolder determines whether or not holder with hook is animated , value is the current value of dynamometer, animationStep animation speed of rotation of the holder (i.e., change of value by time period).

The example how to apply properties of spring dynamometer and to draw it on canvas is shown below:

JavaScript
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);
  });

Spring dynamometer

JavaScript Physical Models

"Addition of Forces by an Angle" Model

The object forcesbyAngleDemo from the file "forcesbyAngleRender.js" is responsible for creation of model to simulate addition of forces. It applies two dynamometers, which can be placed by some angle and three bricks, which can be dragged on the stage and suspended under the dynamometers onto the thread. Through changing parameters of bricks’ weight and angle between dynamometers, the user can calculate total force applied to the dynamometers.

This is a fragment from forcesbyAngleDemo:

JavaScript
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();
                      })
                  // ...
              },
  }

Forces addition demo

"Archimedes' Law" Model

The constant archimedesPrincipleDemo applies one spring dynamometer and the brick, which can be dragged and suspended under the dynamometer to the thread. The dynamometer can be moved towards the box with liquid to show buoyant force applied to it.

This is a fragment from archimedesPrincipleDemo:

JavaScript
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()));
        }
}

Archimedes law demo

"Mathematical Pendulum" Model

The constant object pendulumDemo draws on canvas two animated pendulums and applies parameters like length of pendulum and its start angle of movement. The setTimeout function has been used for creation of periodic animation which is accurate to real timer.

JavaScript
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));
            }
      }
}

Pendulum law

Online Demo

The online demo is available here.

History

  • 28th September, 2020: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)