How to calculate movement of pendulum? - javascript
I am trying to make a simple simulation of pendulum using Runge–Kutta fourth-order method. I am using p5.js. Usually it calculates the angles properly but sometimes it just starts spinning randomly etc. I have no idea how to resolve this issue it seems to be a problem with my implementation of said algorithm I think?
I used code from pang tao's introduction to Computational Physics as an inspiration and it seems quite similar
First part of said code in Fortran
Second part
let screenWidth = 1300;
let screenHight = 1970;
let angleChangeDifference;
let gSlider;
let lSlider;
let aFrequencySlider;
let dumpingSlider;
let startAngleSlider;
let timestepSlider;
let timeMaxSlider;
let dForceSlider;
let initForceSlider;
let startAngle = 0;
let currentAngle = 0;
let circleX = 0;
let circleY = 200;
let circleRWidth = 100;
let circleRHeight = 100;
let lineXStart = 0;
let lineYStart = 0;
let lineXEnd = circleX;
let lineYEnd = (circleY - circleRHeight / 2);
function setup() {
createCanvas(screenWidth, screenHight);
changeScreenDeafultStartingPoint(screenWidth / 2, 100);
frameRate(60)
createSliders();
setInterval(showSliderValue, 100);
startButton.mouseClicked(start);
restartButton.mouseClicked(restart);
chartButton.mouseClicked(enableChart);
background(200);
}
let angleSign = '\u00B0';
let omegaSign = '\u03C9';
let chartOn = false;
let step = 0;
function draw() {
startupConfiguration()
showSliderValue()
if (step >= 1) {
rotatePendulum();
if (step == 1) {}
if (step == 2)
startButton.remove();
chartButton.position(20, 340);
if (chartOn == true) {
createChart(0, 1, 'czas (s)', 'kat (' + angleSign + ')', degreesArr);
createChart(0, 320, 'czas (s)', omegaSign + ' (' + angleSign + '/s)', omegaArr);
scale(2);
createPhaseChart(290, 80, 'kat (' + angleSign + ')', omegaSign + ' (' + angleSign + '/s)', degreesArr);
scale(0.5);
}
}
line(lineXStart, lineYStart, lineXEnd, lineYEnd + (20 * lSlider.value()));
fill(200, 76, 43)
ellipse(circleX, circleY + (20 * lSlider.value()), circleRWidth, circleRHeight);
}
function createSliders() {
gSlider = createSlider(0.05, 20, 9.81, 0.01);
gSlider.position(1100, -90);
lSlider = createSlider(0.5, 10, 5, 0.5);
lSlider.position(1100, -50);
initForceSlider = createSlider(-5, 5, 0, 0.5);
initForceSlider.position(1100, 50);
dForceSlider = createSlider(-2, 2, 0.9, 0.05)
dForceSlider.position(1100, 90);
aFrequencySlider = createSlider(-2, 2, 2 / 3, 1 / 3);
aFrequencySlider.position(1100, 130);
dumpingSlider = createSlider(0.00, 1.5, 0.5, 0.05);
dumpingSlider.position(1100, 170);
startAngleSlider = createSlider(-Math.PI / 2, Math.PI / 2, 0, Math.PI / 32);
startAngleSlider.position(1100, 210);
timestepSlider = createSlider(0, 1000, 100, 10);
timestepSlider.position(1100, 250);
timeMaxSlider = createSlider(10, 10000, 1000, 10);
timeMaxSlider.position(1100, 290);
startButton = createButton('ZATWIERDZ', false);
startButton.position(100, 310)
restartButton = createButton('RESTART', false);
restartButton.position(20, 310)
chartButton = createButton('WYKRES', false);
chartButton.position(-200, 340);
}
function showSliderValue() {
background(200);
fill(0, 0, 0)
text('sila poczatkowa', 440, -60)
text(initForceSlider.value(), 400, -42)
text('sila sprawcza', 440, -20)
text(dForceSlider.value(), 400, -2)
text('czestosc katowa', 440, 20)
text(aFrequencySlider.value(), 400, 42)
text('tlumienie', 440, 60)
text(dumpingSlider.value(), 400, 82)
text('kat poczatkowy', 440, 100)
text(int(degrees(startAngleSlider.value())), 400, 122)
text('krok czasowy (N1)', 440, 140)
text(timestepSlider.value(), 400, 162)
text('dlugosc symulacji (N2)', 440, 180)
text(timeMaxSlider.value(), 400, 202)
}
function start() {
angleIndex = 0;
step++;
startAngle = startAngleSlider.value();
currentAngle = startAngle;
angleChangeDifference = simulate();
rotatePendulum()
startButton.html("START")
}
function restart() {
window.location.reload();
}
function enableChart() {
chartOn = true;
}
function createChart(moveByX, moveByY, xName, yName, table) {
rotate(-currentAngle);
scale(1.1);
translate(moveByX, moveByY);
strokeWeight(1);
line(-500, 500, 530, 500);
line(-500, 700, 530, 700);
line(-500, 500, -500, 700);
line(530, 500, 530, 700);
strokeWeight(1);
let counter = 0;
for (i = 510; i < 700; i += 10) {
if (counter < 9 && counter % 2 == 0)
text(90 - 10 * counter, -520, i + 5)
else if (counter == 9 && counter % 2 == 0)
text(90 - 10 * counter, -515, i + 5)
else if (counter > 9 && counter % 2 == 0)
text(90 - 10 * counter, -525, i + 5)
line(-505, i, 530, i);
counter++;
}
textSize(25);
text(xName, -20, 750)
textSize(12);
counter = 0;
for (i = -490; i < 535; i += 25) {
line(i, 500, i, 705);
if (counter % 4 == 0) {
line(i, 500, i, 705);
text(counter * 2.5, i - 5, 715);
}
counter++;
}
rotate(-90);
textSize(25);
text(yName, -670, -550)
textSize(12);
rotate(90);
fillChartByTableValues(table);
translate(-moveByX, -moveByY);
scale(0.91);
rotate(currentAngle);
}
function fillChartByTableValues(table) {
strokeWeight(2);
stroke(0, 0, 255);
for (i = 0; i < timeArr.length - 1; i++) {
FirstPointX = -490 + timeArr[i] * 10;
FirstPointY = 600 + table[i] * (-1);
SecondPointX = -490 + timeArr[i + 1] * 10;
SecondPointY = 600 + table[i + 1] * (-1);
line(FirstPointX, FirstPointY, SecondPointX, SecondPointY);
}
stroke(0, 0, 0);
strokeWeight(0.1);
}
function createPhaseChart(moveByX, moveByY, xName, yName, table) {
rotate(-currentAngle);
scale(1.1);
translate(moveByX, moveByY);
strokeWeight(1);
line(-500, 500, -300, 500);
line(-500, 700, -300, 700);
line(-500, 500, -500, 700);
line(-300, 500, -300, 700);
strokeWeight(1);
let counter = 0;
textSize(8);
for (i = 510; i < 700; i += 10) {
if (counter < 9 && counter % 2 == 0)
text(90 - 10 * counter, -517, i + 3)
else if (counter == 9 && counter % 2 == 0)
text(90 - 10 * counter, -512, i + 3)
else if (counter > 9 && counter % 2 == 0)
text(90 - 10 * counter, -520, i + 3)
line(-505, i, -300, i);
counter++;
}
textSize(12);
textSize(15);
text(xName, -430, 735)
textSize(12);
counter = 0;
textSize(8);
for (i = -490; i < -300; i += 10) {
line(i, 500, i, 705);
if (counter < 9 && counter % 2 == 0)
text(-90 + 10 * counter, i - 7, 715);
else if (counter == 9 && counter % 2 == 0)
text(-90 + 10 * counter, i - 2, 715);
else if (counter > 9 && counter % 2 == 0)
text(-90 + 10 * counter, i - 4, 715);
counter++;
}
textSize(12);
rotate(-90);
textSize(15);
text(yName, -620, -528)
textSize(12);
rotate(90);
fillPhaseChartByTableValues(degreesArr, omegaArr)
translate(-moveByX, -moveByY);
scale(0.91);
rotate(currentAngle);
}
function fillPhaseChartByTableValues(tableX, tableY) {
translate(-400, 600);
strokeWeight(1);
stroke(0, 0, 255);
for (i = 0; i < tableX.length; i++) {
ellipse(tableX[i], tableY[i], 0.5, 0.5);
}
translate(400, -600);
stroke(0, 0, 0);
strokeWeight(0.1);
}
function startupConfiguration() {
background(200);
angleMode(DEGREES);
changeScreenDeafultStartingPoint(screenWidth / 2, 100);
}
function changeScreenDeafultStartingPoint(x, y) {
translate(x, y);
}
let angleIndex = 0;
function rotatePendulum() {
currentAngle = angleChangeDifference[angleIndex] * (180 / PI);
rotate(currentAngle);
if (step > 1) {
angleIndex++
}
}
function calculateIntegral(t, q, dt, f) {
let k1 = f(t, q).map(val => val * dt);
let temp = k1.map(val => val * 0.5);
temp = temp.map((val, index) => val + q[index])
let k2 = f(t + 0.5 * dt, temp).map(val => val * dt);
temp = k2.map(val => val * 0.5);
temp = temp.map((val, index) => val + q[index])
let k3 = f(t + 0.5 * dt, temp).map(val => val * dt);
temp = q.map((val, index) => val + k3[index]);
let k4 = f(t + dt, temp).map(val => val * dt);
temp = k2.map((val, index) => val + k3[index])
temp = temp.map(val => val * 2)
temp = temp.map((val, index) => (val + k1[index] + k4[index]) / 6)
temp = temp.map((val, index) => val + q[index])
return [t + dt, temp];
}
function modelPendulum(t, q) {
let c = dumpingSlider.value();
let fw = dForceSlider.value();
let w = aFrequencySlider.value();
let x1 = q[0];
let x2 = q[1];
return [x2, -(Math.sin(x1)) - (c * x2) + (fw * Math.cos(w * t))];
}
let degreesArr, timeArr;
function simulate() {
let t = 0.0;
let dt = (3 * Math.PI) / timestepSlider.value()
let tf = timeMaxSlider.value() * dt
let q = [startAngle, initForceSlider.value()];
let Nt = int(Math.round((tf - t) / dt)) + 1;
let solution = new Array(q.length + 1);
for (i = 0; i < q.length + 1; i++) {
solution[i] = new Array(Nt).fill(0);
}
solution[0][0] = t;
solution[1][0] = q[0];
solution[2][0] = q[1];
k = 1;
while (t <= tf) {
let temporaryResult = [];
temporaryResult = calculateIntegral(t, q, dt, modelPendulum);
t = temporaryResult[0];
q = temporaryResult[1];
solution[0][k] = t;
solution[1][k] = q[0];
solution[2][k] = q[1];
k = k + 1
}
timeArr = solution[0];
degreesArr = solution[1];
omegaArr = solution[2];
let counter = 0;
let ifChaos = false;
while (counter != degreesArr.length - 1 && ifChaos != true) {
if (degreesArr[counter] > 13.5 || degreesArr[counter] < -13.5) {
ifChaos = true;
}
counter++;
}
if (ifChaos == true) {
degreesArr = degreesArr.map(val => val * 5.32);
omegaArr = omegaArr.map(val => val * 23.32);
} else {
degreesArr = degreesArr.map(val => val * 35.32);
omegaArr = omegaArr.map(val => val * 35.32);
}
return solution[1]
}
<script src="https://cdn.jsdelivr.net/npm/p5#1.1.9/lib/p5.js"></script>
Am not familiar with P5, so here's a solution using ThreeJS leveraging the Runge-Kutta algorithm from the Mathematics Stack Exchange.
For convenience, I have wrapped the Runge-Kutta algorithm in a class, with the constructor taking the initial parameters of:
the gravity acceleration constant g (for earth, 9.81 meters/sec/sec),
the pendulum length (in meters),
the initial angle of the pendulum (where 0 is straight down),
the initial angular velocity (in meters/sec), and
the max time increment. (Since the Runge-Kutta is employed to solve a second order differential equation using time as the variable, it appears based on this author's experimentation that one cannot overextend the delta time increment and still retain accuracy of the resulting pendulum position and velocity. This parameter simply limits the maximum t value passed in the updatePosition method, with a default of 0.1s.)
To assist in the use of the Runta-Kutta algorithm, the code below simulates two 1 meter pendulums:
The first having an initial position of -PI/2 (-90 deg) with no starting angular velocity.
The second having an initial position of PI (180 deg) with a very small starting angular velocity.
<script type="module">
import * as THREE from 'https://cdn.jsdelivr.net/npm/three#0.115.0/build/three.module.js';
class RungeKutta {
constructor( g, pendulumLength, initialAngle, angularVelocity, maxTimeDelta ) {
this.g = g;
this.pendulumLength = pendulumLength;
this.theta = initialAngle;
this.omega = angularVelocity;
this.maxTimeDelta = maxTimeDelta || 0.1;
}
updatePosition( t ) {
let self = this;
function omegaDot( theta ){
return -( self.g / self.pendulumLength ) * Math.sin( theta );
}
function thetaDot( omega ){
return omega;
}
// If the browser tab becomes inactive, then there will be a large
// time delta, which will disrupt the RungeKutta algorithm. If more
// than max allowed seconds has lapsed, then reset the timer.
if ( self.maxTimeDelta < t ) {
t = self.maxTimeDelta;
}
let aomega = omegaDot( self.theta );
let atheta = thetaDot( self.omega );
let bomega = omegaDot( self.theta + 0.5 * t * atheta );
let btheta = thetaDot( self.omega + 0.5 * t * aomega );
let comega = omegaDot( self.theta + 0.5 * t * btheta );
let ctheta = thetaDot( self.omega + 0.5 * t * bomega );
let domega = omegaDot( self.theta + t * ctheta );
let dtheta = thetaDot( self.omega + t * comega );
self.omega = self.omega + ( t / 6 ) * ( aomega + 2 * bomega + 2 * comega + domega );
self.theta = self.theta + ( t / 6 ) * ( atheta + 2 * btheta + 2 * ctheta + dtheta );
return self;
}
}
//
// Set up the ThreeJS environment.
//
var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
var camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 500 );
camera.position.set( 0, 0, 100 );
camera.lookAt( 0, 0, 0 );
var scene = new THREE.Scene();
//
// Create the pendulum mesh.
//
var length = 30, width = 1;
var shape = new THREE.Shape();
shape.moveTo( -width / 2, 0 );
shape.lineTo( +width / 2, 0 );
shape.lineTo( +width / 2, -length );
shape.lineTo( -width / 2, -length );
shape.lineTo( -width / 2, 0 );
var extrudeSettings = {
steps: 2,
depth: 2,
bevelEnabled: true,
bevelThickness: .25,
bevelSize: .25,
bevelOffset: 0,
bevelSegments: 1
};
var geometry = new THREE.ExtrudeBufferGeometry( shape, extrudeSettings );
var material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
var mesh0 = new THREE.Mesh( geometry, material );
mesh0.position.x = -17;
scene.add( mesh0 );
var mesh1 = mesh0.clone();
mesh1.position.x = +17;
scene.add( mesh1 );
//
// And now animate the pendulum using RungeKutta.
//
let pendulumState0 = new RungeKutta( 9.81, 1, -Math.PI / 2, 0, 0.1 );
let pendulumState1 = new RungeKutta( 9.81, 1, Math.PI, 0.01, 0.1 );
let now = performance.now();
let lastTimer = now;
var animate = function () {
requestAnimationFrame( animate );
now = performance.now();
pendulumState0.updatePosition( ( now - lastTimer ) / 1000 );
pendulumState1.updatePosition( ( now - lastTimer ) / 1000 );
lastTimer = now;
mesh0.rotation.z = pendulumState0.theta;
mesh1.rotation.z = pendulumState1.theta;
renderer.render( scene, camera );
};
animate();
</script>
Hopefully this will assist with your P5 implementation.
This is an example of a pendulum with p5, using Leapfrog method, but it can be easily adapted to use Runge-Kutta 4 (Leapfrog has the advantage that it is symplectic, at variance with RK).
For a simple pendulum you have to solve the second order differential (Newton or Euler-Lagrange) equation
d^2 O / dt^2 = -(g/l) sin(O).
where O is the angle from the vertical.
To apply numerical methods is convenient first to translate it into an equivalent system of two first order differential equations
dw/dt = -(g/l) sin(O)
dO/dt = w
you can solve this system for instance with Runge-Kutta4 or other methods. A very simple one to start with is the simple 2nd order Leapfrog method. You discretize times in steps dt so
w_n == w((n-1/2)dt)
O_n == O(n dt)
and then do the following iteratively in a loop
w_{n+1} = w_n -(g/l) sin(O_n) dt
O_{n+1} = O_n + w_{n+1} dt
This method is implemented in a pendulum class in this code, together with its parameters, a very simple render() function for visualization in p5, and a method for calculating energy for instance (can be used to check conservation of energy if not forced). It is very simple and you can test it. Of course, small time-steps dt should be used in order to have good accuracy.
If you wish to use Runge-Kutta4 or other high order method it is convenient to write functions Fw(t,w,O) and FO(t,w,O) such that the equations read
dw/dt = Fw(t,w,O) (== -(g/l) sin(O) + F0 cos(W t))
dO/dt = FO(t,w,O) (== w )
where I have now included a forcing F0 cos(W t) to show, just in case, how to add any time dependent force.
You can then discretize in time (no leapfrog steps)
t_n == n dt
w_n == w(n dt)
O_n == O(n dt)
and calculate the quantities k1, k2, k3, k4 of RK4 each time step n.
Note that for this case the ki are two dimensional vectors {kiw,kiO}, and the function f(t,w,O) =={Fw(t,w,O),FO(t,w,O)} is also a 2d vector.
Taking this into account, and defining separately the functions Fw and FO you can easily replace the Leapfrog method of the p5 example 1 by a RK4 method, and add forcing if desired.
If you see very strange behaviour, you should check signs and a proper setup of the RK4 method.
I hope this can help.
let P1;
function setup() {
createCanvas(720, 400);
P1=new PenduloSimple(3.14,0.0);
}
function draw() {
background(220);
P1.render();
P1.leapFrog();
}
class PenduloSimple {
constructor(ang, velang) {
this.ang = ang;
this.velang = velang;
this.g = 9.8;
this.dt = 0.01;
this.l = 1.0;
this.m = 0.1;
this.E0 = this.Energy();
}
Energy() {
let E=
this.m * this.l * this.l * this.velang * this.velang * 0.5 - this.m * this.g * this.l * cos(this.ang);
return E;
}
leapFrog() {
// Método de Leapfrog
this.velang =
this.velang + this.dt*(-this.g/this.l)*sin(this.ang);
this.ang = this.ang + this.dt*this.velang;
}
render(){
var x0 = width / 2;
var y0 = height / 2;
var mult0 = 100;
var x = this.l * sin(this.ang);
var y = this.l * cos(this.ang);
var xplot = x0 + x * mult0;
var yplot = y0 + y * mult0;
// Draw a circle
stroke(50);
fill(100);
ellipse(xplot, yplot, 24, 24);
// La cuerda
stroke(50);
line(x0, y0, xplot, yplot);
let E = this.Energy();
stroke(0);
fill(255, 0, 0);
rect(10, height * 0.5, 20, -E * height * 0.45 / this.E0);
fill(0, 255, 0);
rect(30, height * 0.5, 20, -this.ang * height * 0.4 / 6.28);
fill(0, 0, 255);
rect(50, height * 0.5, 20, -this.velang * mult0 * 0.1);
fill(1);
stroke(255);
fill(255, 0, 0);
text("E: " + E, width * 0.1, 20);
fill(50, 200, 50);
text("ang: " + this.ang, width * 0.1, 30);
fill(0, 0, 255);
text("velang: " + this.velang, width * 0.1, 40);
}
}
Related
How to add JavaScript file in angular cli application?
I am trying to replicate this effect - https://codepen.io/jonathasborges1/pen/YzryRpX in my angular app application. But I'm having a hard time applying the effect Someone can help me? "use strict"; var LeafScene = function (el) { this.viewport = el; this.world = document.createElement("div"); this.leaves = []; this.options = { numLeaves: 60, wind: { magnitude: 1.2, maxSpeed: 12, duration: 300, start: 0, speed: 0 } }; this.width = this.viewport.offsetWidth; this.height = this.viewport.offsetHeight; // animation helper this.timer = 0; this._resetLeaf = function (leaf) { // place leaf towards the top left leaf.x = this.width * 2 - Math.random() * this.width * 1.75; leaf.y = -10; leaf.z = Math.random() * 200; if (leaf.x > this.width) { leaf.x = this.width + 10; leaf.y = (Math.random() * this.height) / 2; } // at the start, the leaf can be anywhere if (this.timer == 0) { leaf.y = Math.random() * this.height; } // Choose axis of rotation. // If axis is not X, chose a random static x-rotation for greater variability leaf.rotation.speed = Math.random() * 10; var randomAxis = Math.random(); if (randomAxis > 0.5) { leaf.rotation.axis = "X"; } else if (randomAxis > 0.25) { leaf.rotation.axis = "Y"; leaf.rotation.x = Math.random() * 180 + 90; } else { leaf.rotation.axis = "Z"; leaf.rotation.x = Math.random() * 360 - 180; // looks weird if the rotation is too fast around this axis leaf.rotation.speed = Math.random() * 3; } // random speed leaf.xSpeedVariation = Math.random() * 1 - 0.2; leaf.ySpeed = Math.random(); return leaf; }; this._updateLeaf = function (leaf) { var leafWindSpeed = this.options.wind.speed(this.timer - this.options.wind.start, leaf.y); var xSpeed = leafWindSpeed + leaf.xSpeedVariation; leaf.x -= xSpeed; leaf.y += leaf.ySpeed; leaf.rotation.value += leaf.rotation.speed; var t = "translateX( " + leaf.x + "px ) translateY( " + leaf.y + "px ) translateZ( " + leaf.z + "px ) rotate" + leaf.rotation.axis + "( " + leaf.rotation.value + "deg )"; if (leaf.rotation.axis !== "X") { t += " rotateX(" + leaf.rotation.x + "deg)"; } leaf.el.style.webkitTransform = t; leaf.el.style.MozTransform = t; leaf.el.style.oTransform = t; leaf.el.style.transform = t; // reset if out of view if (leaf.x < -10 || leaf.y > this.height + 10) { this._resetLeaf(leaf); } }; this._updateWind = function () { if (this.timer === 0 || this.timer > this.options.wind.start + this.options.wind.duration) { this.options.wind.magnitude = Math.random() * this.options.wind.maxSpeed; this.options.wind.duration = this.options.wind.magnitude * 50 + (Math.random() * 20 - 10); this.options.wind.start = this.timer; var screenHeight = this.height; this.options.wind.speed = function (t, y) { var a = ((this.magnitude / 2) * (screenHeight - (2 * y) / 3)) / screenHeight; return (a * Math.sin(((2 * Math.PI) / this.duration) * t + (3 * Math.PI) / 2) + a); }; } }; }; LeafScene.prototype.init = function () { for (var i = 0; i < this.options.numLeaves; i++) { var leaf = { el: document.createElement("div"), x: 0, y: 0, z: 0, rotation: { axis: "X", value: 0, speed: 0, x: 0 }, xSpeedVariation: 0, ySpeed: 0, path: { type: 1, start: 0 }, image: 1 }; this._resetLeaf(leaf); this.leaves.push(leaf); this.world.appendChild(leaf.el); } this.world.className = "leaf-scene"; this.viewport.appendChild(this.world); // reset window height/width on resize var self = this; window.onresize = function (event) { self.width = self.viewport.offsetWidth; self.height = self.viewport.offsetHeight; }; }; LeafScene.prototype.render = function () { this._updateWind(); for (var i = 0; i < this.leaves.length; i++) { this._updateLeaf(this.leaves[i]); } this.timer++; requestAnimationFrame(this.render.bind(this)); }; // start up leaf scene var leafContainer = document.querySelector(".falling-leaves"), leaves = new LeafScene(leafContainer); leaves.init(); leaves.render();
You can create a file like <script> ..that js code.. </script> and save it as script.js and then add it to your index.html or dynamicly load it: public loadScript(){ return new Promise(resolve => { const scriptElement = document.createElement('script'); scriptElement.src = '/assets/js/script.js' scriptElement.onload = resolve; document.body.appendChild(scriptElement); }); } and then call and subscribe this function in your ts file to know when it is loaded or do something when its loaded in your page.
Using Set Interval Function to Animate
<html> canvas { background-color: lightgreen } <script> var ctx = canvas.getContext("2d"); var n = 5; const PI = 3.14; var randomWalk = function (n) { var list = [0, 1]; var l = list.length; start = Math.round(n / 2); count = 0; while (start > 0 && start < n + 1) { rand = list[Math.floor(Math.random() * l)]; if (rand == 1) { start = start + 1 } else { start = start - 1 }; count = count + 1; }; console.log(count); }; var drawSquares = function (n) { for (i = 50; i <= n * 50; i = i + 50) { ctx.beginPath(); ctx.rect(i, 50, 50, 50); ctx.stroke(); }; }; var drawDot = function (n) { x = ((50 * n) / 2) + 50; ctx.beginPath(); ctx.arc(x, 75, 5, 0, 2 * PI); ctx.stroke(); ctx.fill(); }; var resetCanvas = function () { ctx.clearRect(0, 0, canvas.width, canvas.height); }; var moveDot = function (n) { drawSquares(n); drawDot(n); var list = [0, 1]; var l = list.length; start = ((50 * n) / 2) + 50; count = 0; while (start > 50 && start < ((n * 50) + 50)) { rand = list[Math.floor(Math.random() * l)]; if (rand == 1) { start = start + 50 } else { start = start - 50 }; count = count + 1; //console.log(start); resetCanvas(); drawSquares(n); ctx.beginPath(); ctx.arc(start, 75, 5, 0, 2 * PI); ctx.stroke(); ctx.fill(); }; console.log(count); }; //moveDot(n); setInterval(moveDot(n), 1500); </script> The function below is the function that isn't working properly within the set interval function. This function takes the number of boxes as the input and based on a random choice of 0 or 1, it moves the dot to the left or to the right however it's not being shown. I created the function titled moveDot to move the dot, however when placed in the set interval function the dot does not move. Any ideas as to why the function is not working properly would be greatly appreciated! Thanks in advance for your help!
Use same animation for multiple elements
I have this working canvas javascript animation but i would like to use it multiple times, currently it's only possible to have one canvas element with the id "stars" and use that one. Could i perhaps add a class for them instead and get the elements class and loop or what would be my best solution for achieving this? I would like to make this work without repeating to much since i could end up using the animation on different pages. // Settings var particleCount = 40, flareCount = 0, motion = 0.05, tilt = 0.05, color = '#00FF7B', particleSizeBase = 1, particleSizeMultiplier = 0.5, flareSizeBase = 100, flareSizeMultiplier = 100, lineWidth = 1, linkChance = 75, // chance per frame of link, higher = smaller chance linkLengthMin = 5, // min linked vertices linkLengthMax = 7, // max linked vertices linkOpacity = 0.25; // number between 0 & 1 linkFade = 90, // link fade-out frames linkSpeed = 0, // distance a link travels in 1 frame glareAngle = -60, glareOpacityMultiplier = 0.4, renderParticles = true, renderParticleGlare = true, renderFlares = false, renderLinks = false, renderMesh = false, flicker = false, flickerSmoothing = 15, // higher = smoother flicker blurSize = 0, orbitTilt = true, randomMotion = true, noiseLength = 1000, noiseStrength = 3; var canvas = document.getElementById('stars'), context = canvas.getContext('2d'), mouse = { x: 0, y: 0 }, m = {}, r = 0, c = 1000, // multiplier for delaunay points, since floats too small can mess up the algorithm n = 0, nAngle = (Math.PI * 2) / noiseLength, nRad = 100, nScale = 0.5, nPos = { x: 0, y: 0 }, points = [], vertices = [], triangles = [], links = [], particles = [], flares = []; function init() { var i, j, k; // requestAnimFrame polyfill window.requestAnimFrame = (function() { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60); }; })(); // Size canvas resize(); mouse.x = canvas.clientWidth / 2; mouse.y = canvas.clientHeight / 2; // Create particle positions for (i = 0; i < particleCount; i++) { var p = new Particle(); particles.push(p); points.push([p.x * c, p.y * c]); } vertices = Delaunay.triangulate(points); var tri = []; for (i = 0; i < vertices.length; i++) { if (tri.length == 3) { triangles.push(tri); tri = []; } tri.push(vertices[i]); } // Tell all the particles who their neighbors are for (i = 0; i < particles.length; i++) { // Loop through all tirangles for (j = 0; j < triangles.length; j++) { // Check if this particle's index is in this triangle k = triangles[j].indexOf(i); // If it is, add its neighbors to the particles contacts list if (k !== -1) { triangles[j].forEach(function(value, index, array) { if (value !== i && particles[i].neighbors.indexOf(value) == -1) { particles[i].neighbors.push(value); } }); } } } var fps = 15; var now; var then = Date.now(); var interval = 1000 / fps; var delta; // Animation loop (function animloop() { requestAnimFrame(animloop); now = Date.now(); delta = now - then; if (delta > interval) { then = now - (delta % interval); resize(); render(); } })(); } function render() { if (randomMotion) { n++; if (n >= noiseLength) { n = 0; } nPos = noisePoint(n); } if (renderParticles) { // Render particles for (var i = 0; i < particleCount; i++) { particles[i].render(); } } } function resize() { canvas.width = window.innerWidth * (window.devicePixelRatio || 1); canvas.height = canvas.width * (canvas.clientHeight / canvas.clientWidth); } // Particle class var Particle = function() { this.x = random(-0.1, 1.1, true); this.y = random(-0.1, 1.1, true); this.z = random(0, 4); this.color = color; this.opacity = random(0.1, 1, true); this.flicker = 0; this.neighbors = []; // placeholder for neighbors }; Particle.prototype.render = function() { var pos = position(this.x, this.y, this.z), r = ((this.z * particleSizeMultiplier) + particleSizeBase) * (sizeRatio() / 1000), o = this.opacity; context.fillStyle = this.color; context.globalAlpha = o; context.beginPath(); context.fill(); context.closePath(); if (renderParticleGlare) { context.globalAlpha = o * glareOpacityMultiplier; context.ellipse(pos.x, pos.y, r * 100, r, (glareAngle - ((nPos.x - 0.5) * noiseStrength * motion)) * (Math.PI / 180), 0, 2 * Math.PI, false); context.fill(); context.closePath(); } context.globalAlpha = 1; }; // Flare class // Link class var Link = function(startVertex, numPoints) { this.length = numPoints; this.verts = [startVertex]; this.stage = 0; this.linked = [startVertex]; this.distances = []; this.traveled = 0; this.fade = 0; this.finished = false; }; // Utils function noisePoint(i) { var a = nAngle * i, cosA = Math.cos(a), sinA = Math.sin(a), rad = nRad; return { x: rad * cosA, y: rad * sinA }; } function position(x, y, z) { return { x: (x * canvas.width) + ((((canvas.width / 2) - mouse.x + ((nPos.x - 0.5) * noiseStrength)) * z) * motion), y: (y * canvas.height) + ((((canvas.height / 2) - mouse.y + ((nPos.y - 0.5) * noiseStrength)) * z) * motion) }; } function sizeRatio() { return canvas.width >= canvas.height ? canvas.width : canvas.height; } function random(min, max, float) { return float ? Math.random() * (max - min) + min : Math.floor(Math.random() * (max - min + 1)) + min; } // init if (canvas) init(); html, body { margin: 0; padding: 0; height: 100%; } body { background: #000; background-image: linear-gradient(-180deg, rgba(0, 0, 0, 0.00) 0%, #000000 100%); } #stars { display: block; position: relative; width: 100%; height: 100%; z-index: 1; position: absolute; } <script src="https://rawgit.com/ironwallaby/delaunay/master/delaunay.js"></script> <script src="http://requirejs.org/docs/release/2.1.15/minified/require.js"></script> <canvas id="stars" width="300" height="300"></canvas>
id="identifier" is unique class="identifier" could be shared by a list of items As you mention using class could be an option but you'll need to change your code to select all elements by that class before: $(".identifier").each(function(a,b) { // Actions for each element } With javascript: var elementList = document.getElementsByClassName("identifier"); var elementListSize=elementList.length; for(var i=0;i<elementListSize;i++) { // Actions for each element (elementList[i]) }
JavaScript - Show link based on spinner
I found this js spinner and I like how it has the counter at the end. It counts down from 15 seconds. I was wondering if it would be possible to make it so that if you land on geography for example it counts down 5 seconds and then redirects you to a separate website. And history would bring you to a different link and so on. Thanks JavaScript var colors = ["#ffff00" , "#1be11b", "#0000ff", "#7e7e7e", "#8a2be2", "#006400", "#2980B9", "#E74C3C"]; // NEED to pre load this data prior var prize_descriptions = ["GENERAL", "GEOGRAPHY", "HISTORY", "ARTS", "SCIENCE", "SPORTS", "RELIGION", "MEDIA"]; var current_user_status = {}; var startAngle = 0; var arc = Math.PI / 4; var spinTimeout = null; var spinArcStart = 10; var spinTime = 0; var spinTimeTotal = 0; var current_user_status = null; var spin_results = null; var wheel; var counter, tt; function drawSpinnerWheel() { var canvas = document.getElementById("canvas"); if (canvas.getContext) { var outsideRadius = 200; var textRadius = 160; var insideRadius = 125; wheel = canvas.getContext("2d"); wheel.clearRect(0, 0, 500, 500); wheel.strokeStyle = "#ecf0f1"; wheel.lineWidth = 5; wheel.font = '12px Helvetica, Arial'; for (var i = 0; i < 8; i++) { var angle = startAngle + i * arc; wheel.fillStyle = colors[i]; wheel.beginPath(); wheel.arc(250, 250, outsideRadius, angle, angle + arc, false); wheel.arc(250, 250, insideRadius, angle + arc, angle, true); wheel.stroke(); wheel.fill(); wheel.save(); wheel.shadowOffsetX = -1; wheel.shadowOffsetY = -1; wheel.shadowBlur = 0; wheel.shadowColor = "rgb(220,220,220)"; wheel.fillStyle = "#ecf0f1"; wheel.translate(250 + Math.cos(angle + arc / 2) * textRadius, 250 + Math.sin(angle + arc / 2) * textRadius); wheel.rotate(angle + arc / 2 + Math.PI / 2); var text = prize_descriptions[i]; if (text === undefined) text = "Not this time!"; wheel.fillText(text, -wheel.measureText(text).width / 2, 0); wheel.restore(); } //Arrow wheel.fillStyle = "#ecf0f1"; wheel.beginPath(); wheel.moveTo(250 - 4, 250 - (outsideRadius + 5)); wheel.lineTo(250 + 4, 250 - (outsideRadius + 5)); wheel.lineTo(250 + 4, 250 - (outsideRadius - 5)); wheel.lineTo(250 + 9, 250 - (outsideRadius - 5)); wheel.lineTo(250 + 0, 250 - (outsideRadius - 13)); wheel.lineTo(250 - 9, 250 - (outsideRadius - 5)); wheel.lineTo(250 - 4, 250 - (outsideRadius - 5)); wheel.lineTo(250 - 4, 250 - (outsideRadius + 5)); wheel.fill(); } } function spin() { $("#spin").unbind('click'); $("#spin").attr("id", "nospin"); document.getElementById('timer').innerHTML = " "; document.getElementById('category').innerHTML = " "; spinMovement = Math.floor(Math.random() * 20) + prize_descriptions.length * 2; spinAngleStart = 1 * 10 + spinMovement; spinTime = 0; spinTimeTotal = Math.floor(Math.random() * 4) * Math.floor(Math.random() * 6) + Math.floor(Math.random() * 8) * Math.floor(Math.random() * 2000) + 2000; console.log(spinMovement + " - " + spinTimeTotal); rotateWheel(); } function rotateWheel() { spinTime += 30; if (spinTime >= spinTimeTotal) { stopRotateWheel(); return; } var spinAngle = spinAngleStart - easeOut(spinTime, 0, spinAngleStart, spinTimeTotal); startAngle += (spinAngle * Math.PI / 180); drawSpinnerWheel(); spinTimeout = setTimeout('rotateWheel()', 30); } function stopRotateWheel() { clearTimeout(spinTimeout); var degrees = startAngle * 180 / Math.PI + 90; var arcd = arc * 180 / Math.PI; var index = Math.floor((360 - degrees % 360) / arcd); wheel.save(); wheel.font = '30px "Homestead-Inline", Helvetica, Arial'; var text = prize_descriptions[index]; //wheel.fillText(text, 250 - wheel.measureText(text).width / 2, 250 + 10); wheel.restore(); document.getElementById('timer').innerHTML = "15"; document.getElementById('category').innerHTML = "Your Category is: " + text; counter = 15; tt=setInterval(function(){startTime()},1000); } function easeOut(t, b, c, d) { var ts = (t /= d) * t; var tc = ts * t; return b + c * (tc + -3 * ts + 3 * t); } drawSpinnerWheel(); function startTime() { if(counter == 0) { clearInterval(tt); $("#nospin").attr("id", "spin"); $("#spin").bind('click', function(e) { e.preventDefault(); spin(); }); } else { counter--; } document.getElementById('timer').innerHTML = counter; } $("#spin").bind('click', function(e) { e.preventDefault(); spin(); }); To see it in action click here
It seems that all your changes should be made in the function stopRotateWheel() and startTime() When the function is called, a variable called text holds the result ("Geography" or "Science", etc.). From that, we can perform conditions based on the value of text and determine the total time of countdown, plus the link when the countdown expires. Something like this: function stopRotateWheel() { clearTimeout(spinTimeout); var degrees = startAngle * 180 / Math.PI + 90; var arcd = arc * 180 / Math.PI; var index = Math.floor((360 - degrees % 360) / arcd); wheel.save(); wheel.font = '30px "Homestead-Inline", Helvetica, Arial'; var text = prize_descriptions[index]; //wheel.fillText(text, 250 - wheel.measureText(text).width / 2, 250 + 10); wheel.restore(); document.getElementById('timer').innerHTML = "15"; document.getElementById('category').innerHTML = "Your Category is: " + text; /*do an if else*/ if(text=="Geography") { counter = 5; tt=setInterval(function(){startTime("www.geography.com")},1000); /*countdown, and when timer expires... go to another link*/ } else if (text=="Science") { //do the same as above :) } } notice the code startTime("www.geography.com")? that's because we also modify function startTime to accept a parameter (in this case, the website) so that when the countdown is finished the webpage goes to that link :) function startTime(gotoLink) { if(counter == 0) { /*go to that link */ window.location.replace(gotoLink) } else { counter--; } document.getElementById('timer').innerHTML = counter; } try it out!
multiple random images in let it snow plug-in
i've been playing with jason brown's let it snow plug-in. his code only accommodates for a single custom image, and i've been trying to figure out how to change the code so it accommodates multiple custom images within a random range. !function($){ var defaults = { speed: 0, interaction: true, size: 2, count: 200, opacity: 0, color: "#ffffff", windPower: 0, image: false }; $.fn.let_it_snow = function(options){ var settings = $.extend({}, defaults, options), el = $(this), flakes = [], canvas = el.get(0), ctx = canvas.getContext("2d"), flakeCount = settings.count, mX = -100, mY = -100; canvas.width = window.innerWidth; canvas.height = window.innerHeight; (function() { var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60); }; window.requestAnimationFrame = requestAnimationFrame; })(); function snow() { ctx.clearRect(0, 0, canvas.width, canvas.height); for (var i = 0; i < flakeCount; i++) { var flake = flakes[i], x = mX, y = mY, minDist = 100, x2 = flake.x, y2 = flake.y; var dist = Math.sqrt((x2 - x) * (x2 - x) + (y2 - y) * (y2 - y)), dx = x2 - x, dy = y2 - y; if (dist < minDist) { var force = minDist / (dist * dist), xcomp = (x - x2) / dist, ycomp = (y - y2) / dist, deltaV = force / 2; flake.velX -= deltaV * xcomp; flake.velY -= deltaV * ycomp; } else { flake.velX *= .98; if (flake.velY <= flake.speed) { flake.velY = flake.speed } switch (settings.windPower) { case false: flake.velX += Math.cos(flake.step += .05) * flake.stepSize; break; case 0: flake.velX += Math.cos(flake.step += .05) * flake.stepSize; break; default: flake.velX += 0.01 + (settings.windPower/100); } } var s = settings.color; var patt = /^#([\da-fA-F]{2})([\da-fA-F]{2})([\da-fA-F]{2})$/; var matches = patt.exec(s); var rgb = parseInt(matches[1], 16)+","+parseInt(matches[2], 16)+","+parseInt(matches[3], 16); flake.y += flake.velY; flake.x += flake.velX; if (flake.y >= canvas.height || flake.y <= 0) { reset(flake); } if (flake.x >= canvas.width || flake.x <= 0) { reset(flake); } if (settings.image == false) { ctx.fillStyle = "rgba(" + rgb + "," + flake.opacity + ")" ctx.beginPath(); ctx.arc(flake.x, flake.y, flake.size, 0, Math.PI * 2); ctx.fill(); } else { ctx.drawImage($("img#lis_flake").get(0), flake.x, flake.y, flake.size * 2, flake.size * 2); } } requestAnimationFrame(snow); }; function reset(flake) { if (settings.windPower == false || settings.windPower == 0) { flake.x = Math.floor(Math.random() * canvas.width); flake.y = 0; } else { if (settings.windPower > 0) { var xarray = Array(Math.floor(Math.random() * canvas.width), 0); var yarray = Array(0, Math.floor(Math.random() * canvas.height)) var allarray = Array(xarray, yarray) var selected_array = allarray[Math.floor(Math.random()*allarray.length)]; flake.x = selected_array[0]; flake.y = selected_array[1]; } else { var xarray = Array(Math.floor(Math.random() * canvas.width),0); var yarray = Array(canvas.width, Math.floor(Math.random() * canvas.height)) var allarray = Array(xarray, yarray) var selected_array = allarray[Math.floor(Math.random()*allarray.length)]; flake.x = selected_array[0]; flake.y = selected_array[1]; } } flake.size = (Math.random() * 3) + settings.size; flake.speed = (Math.random() * 1) + settings.speed; flake.velY = flake.speed; flake.velX = 0; flake.opacity = (Math.random() * 0.5) + settings.opacity; } function init() { for (var i = 0; i < flakeCount; i++) { var x = Math.floor(Math.random() * canvas.width), y = Math.floor(Math.random() * canvas.height), size = (Math.random() * 3) + settings.size, speed = (Math.random() * 1) + settings.speed, opacity = (Math.random() * 0.5) + settings.opacity; flakes.push({ speed: speed, velY: speed, velX: 0, x: x, y: y, size: size, stepSize: (Math.random()) / 30, step: 0, angle: 180, opacity: opacity }); } snow(); } if (settings.image != false) { $("<img src='"+settings.image+"' style='display: none' id='lis_flake'>").prependTo("body") } init(); $(window).resize(function() { if(this.resizeTO) clearTimeout(this.resizeTO); this.resizeTO = setTimeout(function() { el2 = el.clone(); el2.insertAfter(el); el.remove(); el2.let_it_snow(settings); }, 200); }); if (settings.interaction == true) { canvas.addEventListener("mousemove", function(e) { mX = e.clientX, mY = e.clientY }); }}}(window.jQuery); right at the top of the code, in the defaults properties, is where you point the url of the image. below is what i've put in, so it chooses between image1.jpeg and image2.jpeg image: "img/image'+Math.floor((Math.random() * 2) + 1)+'.jpeg"; however, when the "snowflakes" respawn, the image stays the same instead of choosing a random number again. what do i have to change so that when a snowflake is created, it chooses a random image and becomes it? i hope my questions is clear, let me know if you need more clarity. i'm new to j-script, any help would be appreciated.
The plugin author loads a single custom image into a hidden element with if (settings.image != false) { $("<img src='"+settings.image+"' style='display: none' id='lis_flake'>").prependTo("body") } and loads the snowflakes with this ctx.drawImage($("img#lis_flake").get(0) This solution hasn't been tested, but if you are willing to hack the plugin a bit, you could potentially make the following changes to use two optional images: 1) Add this just before the init(); function call. It adds another hidden image from settings.image2. if (settings.image2) { $("<img src='"+settings.image2+"' style='display: none' id='lis_flake2'>").prependTo("body") } 2) Modify the function init() with flakes.push({ // Create a new property called 'imgNum' to hold either "" or "2"; imgNum: (settings.image2 && Math.floor(Math.random() * 2) === 0 ? "2" : ""), // rest of the code ... speed: speed, 3) Change ctx.drawImage($("img#lis_flake").get(0), flake.x, flake.y, flake.size * 2, flake.size * 2); to ctx.drawImage($("img#lis_flake" + flake.imgNum).get(0), flake.x, flake.y, flake.size * 2, flake.size * 2); to chose the image based on the flake.imgNum set randomly in init(). 4) Change the options you pass into the plugin like so: options = { // other options image: "img/image1.jpeg", image2: "img/image2.jpeg", // other options } These changes should allow the plugin to still work if image2 isn't set.