65.9K
CodeProject is changing. Read more.
Home

Physical Simulations in Vanilla JS

starIconstarIconstarIconstarIconstarIcon

5.00/5 (13 votes)

Sep 29, 2020

CPOL

12 min read

viewsIcon

14353

downloadIcon

180

JavaScript, HTML based physical computer models on canvas

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:

      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:

      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.

      /**
      * 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.

      /**
      * 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:

      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.

      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.

      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.

      /**
      * 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:

      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.

      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.

      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:

      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:

      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.

      /**
      * 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:

      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)

      /**
      * 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:

      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:

      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.

      /**
      * 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.

      /**
      * 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:

      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.

      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:

      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:

      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:

      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.

      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