카테고리 없음

Newton's Cradle

개발남자 2022. 10. 13. 11:47

Newton's Cradle.zip
0.00MB

<html>
<link rel="stylesheet" href="style.css">

<head></head>

<body>
    <div class="container">
        <canvas class="canvas"></canvas>
  </div>
</body>
<script type="text/javascript" src="script.js"></script>

</html>




body {
  width: 100vw;
  height: 100vh;
  margin: 0;
  overflow: hidden;
}

.container {
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  background: #0d0d0d;
}

.canvas {
  width: 600px;
  height: 600px;
  background: antiquewhite;
  border: 10px solid dimgrey;
}





const $canvas = document.querySelector('.canvas');
const context = $canvas.getContext('2d');
const pixelRatio = window.devicePixelRatio || 1;
const canvasStyleWidth = $canvas.clientWidth;
const canvasStyleHeight = $canvas.clientHeight;
$canvas.width = canvasStyleWidth * pixelRatio;
$canvas.height = canvasStyleHeight * pixelRatio;
context.scale(pixelRatio, pixelRatio);
const middleX = canvasStyleWidth / 2;

const platFormYPos = 450;
const platformWidth = 500;
const platformFrontHeight = 50;
const backWidth = 300;
const backFrameBottomY = 410;
const backFrameWidth = 270;
const backFrameHeight = 200;
const frontFrameBottomY = 440;
const frontFrameWidth = 360;
const frontFrameHeight = 270;
const numBalls = 5;
const ballRadius = backFrameWidth / numBalls / 2;
const ballMiddleY = 380;
const frontFrameSingleSegment = frontFrameWidth / (numBalls + 1);
const backFrameSingleSegment = backFrameWidth / (numBalls + 1);
let rotationA = 0;
let backRotationA = 0;
let rotationB = 0;
let backRotationB = 0;
let swingTurn = 1;
let rotationACounter = 0;
let rotationBCounter = 0;


function drawPlatform() {
  // front edge
  context.save();
  context.translate(middleX, platFormYPos);
  let x = - (platformWidth / 2);
  let y = 0;
  context.fillStyle = '#000';
  context.fillRect(x, y, platformWidth, platformFrontHeight);
  const stripHeight = 20;
  y = (platformFrontHeight - stripHeight) / 2;
  context.fillStyle = '#626262';
  context.fillRect(x, y, platformWidth, stripHeight);
  const tinyStripHeight = 6;
  y = (platformFrontHeight - tinyStripHeight) / 2;
  context.fillStyle = '#000';
  context.fillRect(x, y, platformWidth, tinyStripHeight);

  // base
  context.translate(x, 0);
  context.beginPath();
  context.moveTo(0, 0);
  context.lineTo((platformWidth - backWidth) / 2, -50);
  context.lineTo((platformWidth - backWidth) / 2 + backWidth, -50);
  context.lineTo(platformWidth, 0);
  context.fillStyle = '#303030';
  context.fill();
  context.restore();
}

function drawFrame(startingBottomY, width, topY) {
  context.save();
  context.translate(middleX, startingBottomY);
  let x = - (width / 2);
  context.beginPath();
  context.moveTo(x, 0);
  context.arcTo(x, topY, x + width, topY, 30);
  context.arcTo(x + width, topY, x + width, 0, 30);
  context.lineTo(x + width, 0);
  context.strokeStyle = '#afb2b1';
  context.lineWidth = 6;
  context.stroke();
  context.restore();
}

function drawBackFrame() {
  drawFrame(backFrameBottomY, backFrameWidth, -backFrameHeight);
}

function drawFrontFrame() {
  drawFrame(frontFrameBottomY, frontFrameWidth, -frontFrameHeight);
}

function drawBalls() {
  const firstBallMiddleX = middleX - (ballRadius * (numBalls - 1));
  const frontFrameFirstLineX = middleX - Math.floor(numBalls / 2) * frontFrameSingleSegment;
  const backFrameFirstLineX = middleX - Math.floor(numBalls / 2) * backFrameSingleSegment;
  const frontFrameAnchorY = frontFrameBottomY - frontFrameHeight;
  const backFrameAnchorY =  backFrameBottomY - backFrameHeight;

  for (let i = 0; i < numBalls; i++) {
    const frontFrameAnchorX = frontFrameFirstLineX + i * frontFrameSingleSegment;
    const backFrameAnchorX = backFrameFirstLineX + i * backFrameSingleSegment;
    const x = firstBallMiddleX + i * 2 * ballRadius;
    const y  = ballMiddleY;
    const isFirstBall = i === 0;
    const isLastBall = i === numBalls - 1;

    // line from back frame
    context.save();
    if (isFirstBall || isLastBall) {
      context.translate(backFrameAnchorX, backFrameAnchorY);
      context.rotate(isFirstBall ? backRotationA : backRotationB);
      context.translate(-backFrameAnchorX, -backFrameAnchorY);
    }
    context.beginPath();
    context.moveTo(x, ballMiddleY + 10);
    context.lineTo(backFrameAnchorX, backFrameAnchorY);
    context.stroke();
    context.restore();

    // ball
    context.save();
    if (isFirstBall || isLastBall) {
      context.translate(frontFrameAnchorX, frontFrameAnchorY);
      context.rotate(isFirstBall ? rotationA : rotationB);
      context.translate(-frontFrameAnchorX, -frontFrameAnchorY);
    }
    context.beginPath();
    context.arc(x, y, ballRadius, 0, 2 * Math.PI);
    const gradient = context.createRadialGradient(x, y, 4, x, y, ballRadius);
    gradient.addColorStop(0, '#FFF');
    gradient.addColorStop(1, '#121414');
    context.fillStyle = gradient;
    context.fill();

    // line from front frame
    context.translate(0, 0);
    context.beginPath();
    context.moveTo(x, ballMiddleY - ballRadius);
    context.lineTo(frontFrameAnchorX, frontFrameAnchorY);
    context.strokeStyle = '#4e4e4e';
    context.lineWidth = 2;
    context.stroke();
    context.restore();
  }
}

function tick() {
  if (swingTurn === 1) {
    rotationACounter = Math.min(rotationACounter + 0.1, Math.PI);
    rotationA = Math.sin(rotationACounter) * Math.PI / 4;
    backRotationA = Math.sin(rotationACounter) * Math.PI / 3.5;
    if (rotationACounter >= Math.PI) {
      alternate();
    }
  }
  
  if (swingTurn === 2) {
    rotationBCounter = Math.min(rotationBCounter += 0.1, Math.PI);
    rotationB = - Math.sin(rotationBCounter) * Math.PI / 4;
    backRotationB = - Math.sin(rotationBCounter) * Math.PI / 3.5;
    if (rotationBCounter >= Math.PI) {
      alternate();
    }
  } 

  context.clearRect(0, 0, $canvas.width, $canvas.height);
  drawPlatform();
  drawBackFrame();
  drawBalls();
  drawFrontFrame();
  requestAnimationFrame(tick);
}

function alternate() {
  if (swingTurn === 1) {
    swingTurn = 2;
    rotationBCounter = 0;
  } else if (swingTurn === 2) {
    swingTurn = 1;
    rotationACounter = 0;
  }
}

tick();