Cómo simular el movimiento de un péndulo simple en JavaScript y mostrar las fuerzas físicas a las que está sometido.
La física, con su complejidad inherente, a menudo se vuelve más accesible y comprensible mediante representaciones visuales. La simulación de fenómenos físicos, como el péndulo simple, no solo ofrece una perspectiva clara de los conceptos abstractos, sino que también nos ofrece la oportunidad de aplicar conocimientos teóricos mediante ejemplos prácticos con el uso de lenguajes de programación como JavaScript.
En este artículo, exploraremos la creación de una simulación visual de un péndulo simple. Analizaremos las fórmulas básicas de fuerza que actúan sobre el péndulo y aprenderemos a dibujar y ejecutar una animación en el elemento canvas
de HTML programando en JavaScript.
Antes de empezar repasemos un poco las fuerzas que actúan sobre el péndulo. Concretamente hay dos fuerzas que actúan sobre la masa del péndulo, el peso y la tensión de la cuerda.
El peso se define por F = m * g representa la fuerza que actúa hacia abajo en el centro de masa del péndulo, donde (m) es la masa y (g) es la aceleración debida a la gravedad.
Usando trigonometría, el peso se puede descomponer en dos fuerzas:
En la dirección tangencial, la única fuerza responsable del movimiento oscilatorio del péndulo es la componente x del peso - m _ g _ sin(theta). El símbolo negativo indica que es una fuerza de restitución contraria a la dirección del movimiento del péndulo.
La fórmula m _ g _ cos(theta) de la gravedad actúa en la dirección radial hacia afuera del círculo. Esta fuerza contrarresta la fuerza de tensión y contribuye al equilibrio del péndulo en su movimiento circular.
La fuerza de tensión T es la fuerza que mantiene la cuerda del péndulo en su trayectoria circular. Se dirige hacia el punto de suspensión y actúa radialmente hacia adentro. Esta fuerza es la que mantiene el equilibrio entre la gravedad y la velocidad angular del péndulo.
A continuación puedes ver la simulación y todo el código necesario para realizar la simulación. Verás que en los puntos más altos y más bajos las fuerzas se equilibran, es decir, las flechas que representan las fuerzas se superponen o contrarestan.
<!doctype html><html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Simulación de un péndulo</title> <style> .actions { margin-bottom: 4px; }
canvas { border: 1px solid black; } </style> </head>
<body> <div class="actions"> <button id="toggleButton">Stop/Play</button> Haz clic y arrastra el círculo para mover el péndulo. </div> <canvas id="canvas" width="400px" height="300px"></canvas> <script> class Pendulum { constructor(x, y, r) { this.pivot = { x, y } this.bob = { x: 0, y: 0 } this.bobMass = 100 this.ropeRadius = r this.angle = Math.PI / 4 this.gravity = 0.6
this.angleVelocity = 0.0 this.angleAcceleration = 0.0 this.damping = 0.999 // 1-0.990 Arbitrary damping this.ballRadius = 18.0 this.running = true
this.dragging = false }
update() { if (this.running && !this.dragging) { this.angleAcceleration = ((-1 * this.gravity) / this.ropeRadius) * Math.sin(this.angle)
this.angleVelocity += this.angleAcceleration this.angle += this.angleVelocity
this.angleVelocity *= this.damping } }
show(context) { this.bob.x = this.ropeRadius * Math.sin(this.angle) this.bob.y = this.ropeRadius * Math.cos(this.angle)
context.strokeStyle = '#000' context.lineWidth = 1 context.beginPath() context.moveTo(this.pivot.x, this.pivot.y) context.lineTo(this.pivot.x + this.bob.x, this.pivot.y + this.bob.y) context.stroke()
context.fillStyle = '#fff' context.beginPath() context.arc( this.pivot.x + this.bob.x, this.pivot.y + this.bob.y, this.ballRadius, 0, 2 * Math.PI ) context.fill() context.stroke() }
drawForces(context) { context.save() context.lineWidth = 2
this.bob.x = this.ropeRadius * Math.sin(this.angle) this.bob.y = this.ropeRadius * Math.cos(this.angle)
const F = this.bobMass * this.gravity const bobCenterPosition = { x: this.pivot.x + this.bob.x, y: this.pivot.y + this.bob.y, }
drawArrow( bobCenterPosition.x, bobCenterPosition.y, bobCenterPosition.x, bobCenterPosition.y + F, 'red' )
const Fp = -F * Math.sin(this.angle)
const FpEndpointX = bobCenterPosition.x + Fp * Math.cos(this.angle) const FpEndpointY = bobCenterPosition.y - Fp * Math.sin(this.angle)
drawArrow( bobCenterPosition.x, bobCenterPosition.y, FpEndpointX, FpEndpointY, 'blue' ) const Fa = F * Math.cos(this.angle)
const FaEndpointX = bobCenterPosition.x + Fa * Math.sin(this.angle) const FaEndpointY = bobCenterPosition.y + Fa * Math.cos(this.angle)
drawArrow( bobCenterPosition.x, bobCenterPosition.y, FaEndpointX, FaEndpointY, 'green' )
const Ft = F * Math.cos(this.angle)
const FtEndpointX = bobCenterPosition.x - Ft * Math.sin(this.angle) const FtEndpointY = bobCenterPosition.y - Ft * Math.cos(this.angle)
drawArrow( bobCenterPosition.x, bobCenterPosition.y, FtEndpointX, FtEndpointY, 'darkGreen' )
context.beginPath() context.setLineDash([5, 5]) context.moveTo(this.pivot.x, this.pivot.y) context.lineTo(canvas.width / 2, this.ropeRadius * 2) context.stroke() context.setLineDash([])
context.translate(this.pivot.x, this.pivot.y) context.lineWidth = 1 let startAngle = Math.PI / 2 let endAngle = Math.PI / 2 - this.angle context.beginPath() if (endAngle > Math.PI / 2) { context.arc(0, 0, this.ropeRadius / 4, startAngle, endAngle) } else { context.arc(0, 0, this.ropeRadius / 4, endAngle, startAngle) } context.stroke() context.restore() }
clicked(mx, my) { let d = Math.sqrt( Math.pow(mx - (this.pivot.x + this.bob.x), 2) + Math.pow(my - (this.pivot.y + this.bob.y), 2) ) if (d < this.ballRadius) { this.dragging = true } } toggleRunning() { this.running = !this.running } stopDragging() { if (this.dragging) { this.angleVelocity = 0 this.dragging = false } }
drag(mx, my) { if (this.dragging) { let diffX = -mx + this.pivot.x let diffY = my - this.pivot.y const angle = Math.atan2(diffY, diffX) - Math.PI / 2 let minAngle = 0 let maxAngle = Math.PI / 2 + angle if (maxAngle > minAngle) { this.angle = angle } } } }
let pendulum let canvas let toggleButton let context
function setup() { canvas = document.getElementById('canvas') context = canvas.getContext('2d')
pendulum = new Pendulum(canvas.width / 2, 30, 120)
toggleButton = document.getElementById('toggleButton') toggleButton.addEventListener('click', handletoggleRunning)
canvas.addEventListener('mousedown', handleMouseDown) canvas.addEventListener('mouseup', handleMouseUp) canvas.addEventListener('mousemove', handleMouseMove) }
function draw() { context.clearRect(0, 0, canvas.width, canvas.height) pendulum.update() pendulum.show(context) pendulum.drawForces(context) requestAnimationFrame(draw) }
function drawArrow(x1, y1, x2, y2, color) { context.save() context.strokeStyle = color context.fillStyle = color
const PI = Math.PI const headLength = 6 const degreesInRadians225 = (225 * PI) / 180 const degreesInRadians135 = (135 * PI) / 180
const dx = x2 - x1 const dy = y2 - y1 const vectorLength = Math.sqrt(dx * dx + dy * dy)
if (vectorLength < headLength) { context.restore() return }
const angle = Math.atan2(dy, dx)
const x225 = x2 + headLength * Math.cos(angle + degreesInRadians225) const y225 = y2 + headLength * Math.sin(angle + degreesInRadians225) const x135 = x2 + headLength * Math.cos(angle + degreesInRadians135) const y135 = y2 + headLength * Math.sin(angle + degreesInRadians135)
context.beginPath() context.moveTo(x1, y1) context.lineTo(x2, y2) context.moveTo(x2, y2) context.lineTo(x225, y225) context.moveTo(x2, y2) context.lineTo(x135, y135) context.stroke() context.restore() }
function handleMouseDown(event) { pendulum.clicked( event.clientX - canvas.offsetLeft, event.clientY - canvas.offsetTop ) }
function handleMouseUp() { pendulum.stopDragging() }
function handleMouseMove(event) { pendulum.drag( event.clientX - canvas.offsetLeft, event.clientY - canvas.offsetTop ) }
function handletoggleRunning() { pendulum.toggleRunning() }
setup() draw() </script> </body></html>