JSFiddle - React, Tailwind, and code Playground
HTML
<script src="https://cdnjs.cloudflare.com/ajax/libs/howler/2.2.4/howler.min.js"></script>
<html>
<body>
<canvas id="canvas"></canvas>
<div id="boingCount"></div>
</body>
</html>
JavaScript
class BoingSpringToy {
constructor(canvas, options = {}) {
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
// Configurable constants
this.anchor = { x: 17, y: 200 }; // fixed wall anchor
this.restLength = options.restLength || 250; // relaxed spring length
this.maxCanvasWidth = options.maxWidth || 600;
this.maxCanvasHeight = options.maxHeight || 400;
// Physics
this.ball = { x: 0, y: 0 }; // current ball position
this.velocity = { x: 0, y: 0 }; // current velocity
this.springFactor = 0.95; // how strongly it pulls back (0.95 = gentle)
this.damping = 0.88; // friction per frame (0.88 = bouncy)
this.noiseStrength = 0.1; // tiny randomness for wobble
// Interaction state
this.isDragging = false;
this.dragPoint = { x: 0, y: 0 };
// Audio (Howler.js must be loaded globally)
this.sound = null;
this.soundEnabled = false;
this.boingCount = parseInt(localStorage.getItem("boingCount") || "0");
// Visual tweaks
this.coilFrequency = 25; // how many loops in the spring
this.coilAmplitude = 25; // base thickness of coil wave
this._initCanvas();
this._initAudio();
this._resetBall();
this._bindEvents();
this._loop();
}
_initCanvas() {
this.resize();
window.addEventListener('resize', () => this.resize());
}
resize() {
const width = Math.min(window.innerWidth * 0.9 - 24, this.maxCanvasWidth);
this.canvas.width = width;
this.canvas.height = this.maxCanvasHeight;
// Recenter anchor vertically and update rest position
this.anchor.y = this.canvas.height / 2;
this.restLength = Math.min((this.canvas.width -...