matterjs实现沙漏动画效果代码
代码语言:html
所属分类:动画
代码描述:matterjs实现沙漏动画效果代码,显示沙漏通过的粮食量、所用时间和平均每秒通过量。
下面为部分代码预览,完整代码请点击下载或在bfwstudio webide中打开
<!DOCTYPE html> <html lang="en" > <head> <meta charset="UTF-8"> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> canvas { border: 1px solid black; } </style> </head> <body translate="no"> <div style="display: flex; align-items: start; scale:0.6; margin-top:-20vh"> <canvas id="hourglassCanvas" width="400" height="500"></canvas> <div id="dataDisplay" style="margin-left: 20px; font-family: Arial; font-size: 26px;"> <p id="grainsPassed">Number of Grains Passed: 0</p> <p id="currentTime">Current Time: 00:00.000</p> </div> </div> <script type="text/javascript" src="//repo.bfw.wiki/bfwrepo/js/matter.0.19.0.js"></script> <script > let canvas, ctx; let grains = []; const numGrains = 2000; let grainsPassedCenter = 0; let timerStarted = false; let timerEnded = false; let startTime; let endTime; let lastGrainPassedTime = null; const glassThickness = 6; window.onload = function () { initializeCanvas(); initializeGrains(); startTimer(); animate(); }; function initializeCanvas() { canvas = document.getElementById("hourglassCanvas"); ctx = canvas.getContext("2d"); canvas.width = 800; canvas.height = 1000; } function initializeGrains() { grains = []; grainsPassedCenter = 0; const startY = canvas.height / 2 - neckHeight / 2 - glassThickness; // Just above the center const endY = 120 + glassThickness; const spreadX = hourglassWidth - 2 * glassThickness; const cellSize = 4; const spatialHash = new Map(); for (let i = 0; i < numGrains; i++) { let x, y; let placed = false; let attempts = 0; const maxAttempts = 1000; while (!placed && attempts < maxAttempts) { x = canvas.width / 2 + (Math.random() - 0.5) * spreadX; y = startY - Math.random() * (startY - endY); // Fill upwards attempts++; const cellX = Math.floor(x / cellSize); const cellY = Math.floor(y / cellSize); const cellKey = `${cellX},${cellY}`; const nearbyKeys = [ cellKey, `${cellX - 1},${cellY}`, `${cellX + 1},${cellY}`, `${cellX},${cellY - 1}`, `${cellX},${cellY + 1}`, `${cellX - 1},${cellY - 1}`, `${cellX + 1},${cellY - 1}`, `${cellX - 1},${cellY + 1}`, `${cellX + 1},${cellY + 1}`]; let overlaps = false; for (const key of nearbyKeys) { if (spatialHash.has(key)) { for (const otherGrain of spatialHash.get(key)) { const dx = x - otherGrain.x; const dy = y - otherGrain.y; if (dx * dx + dy * dy < 16) { // 4^2, square of 2 * collisionRadius overlaps = true; break; } } if (overlaps) break; } } if (!overlaps && isInsideHourglass(x, y, 2)) { // Check if inside hourglass placed = true; const grain = { x: x, y: y, radius: 3, collisionRadius: 3, velocity: { x: 0, y: 0 }, acceleration: 0, passedCenter: false, isDone: false }; grains.push(grain); if (!spatialHash.has(cellKey)) { spatialHash.set(cellKey, []); } spatialHash.get(cellKey).push(grain); } } // If we couldn't place the grain after max attempts, skip it if (!placed) { console.warn(`Couldn't place grain ${i}`); } } console.log(`Placed ${grains.length} out of ${numGrains} grains`); } function startTimer() { timerStarted = true; startTime = Date.now(); } // Hourglass parameters const hourglassWidth = 500; const hourglassHeight = 800; const neckWidth = 26; const neckHeight = 40; const curvature = 200; function drawHourglass() { const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height); gradient.addColorStop(0, "rgba(0, 255, 255, 0.7)"); // Cyan gradient.addColorStop(0.3, "rgba(255, 255, 255, 0.9)"); // White gradient.addColorStop(0.5, "rgba(0, 255, 255, 0.7)"); // Cyan gradient.addColorStop(0.7, "rgba(255, 255, 255, 0.9)"); // White gradient.addColorStop(1, "rgba(0, 255, 255, 0.7)"); // Cyan // Draw the main outline ctx.beginPath(); // Top half ctx.moveTo(canvas.width / 2 - hourglassWidth / 2, 100); // Doubled from 50 ctx.lineTo(canvas.width / 2 + hourglassWidth / 2, 100); // Doubled from 50 ctx.quadraticCurveTo( canvas.width / 2 + hourglassWidth / 2, canvas.height / 2 - curvature, canvas.width / 2 + neckWidth / 2, canvas.height / 2 - neckHeight / 2); // Straight center part ctx.lineTo( canvas.width / 2 + neckWidth / 2, canvas.height / 2 + neckHeight / 2); // Bottom half ctx.quadraticCurveTo( canvas.width / 2 + hourglassWidth / 2, canvas.height / 2 + curvature, canvas.width / 2 + hourglassWidth / 2, canvas.height - 100 // Doubled from 50 ); ctx.lineTo(canvas.width / 2 - hourglassWidth / 2, canvas.height - 100); ctx.quadraticCurveTo( canvas.width / 2 - hourglassWidth / 2, canvas.height / 2 + curvature, canvas.width / 2 - neckWidth / 2, canvas.height / 2 + neckHeight / 2); // Straight center part ctx.lineTo( canvas.width / 2 - neckWidth / 2, canvas.height / 2 - neckHeight / 2); ctx.quadraticCurveTo( canvas.width / 2 - hourglassWidth / 2, canvas.height / 2 - curvature, canvas.width / 2 - hourglassWidth / 2, 100); ctx.closePath(); ctx.strokeStyle = gradient; ctx.lineWidth = 6; ctx.stroke(); //drawCollisionBoundary(); } function drawCollisionBoundary() { ctx.beginPath(); for ( let y = 100 + glassThickness; y <= canvas.height - 100 - glassThickness; y++) { // Doubled from 50 const width = getHourglassWidthAtY(y); const x1 = canvas.width / 2 - width / 2; const x2 = canvas.width / 2 + width / 2; ctx.moveTo(x1, y); ctx.lineTo(x2, y); } ctx.strokeStyle = "rgba(255, 0, 0, 0.3)"; ctx.lineWidth = 1; ctx.stroke(); } function drawSandGrains() { grains.forEach(grain => { if (!grain.isSettled) { ctx.fillStyle = "rgb(255, 0, 99)"; } else { ctx.fillStyle = "rgb(255,255,255)"; } ctx.fillRect( grain.x - grain.radius, grain.y - grain.radius, grain.radius * 2, grain.radius * 2); }); } function getHourglassWidthAtY(y) { const halfHeight = (canvas.height - 200) / 2; const centerY = canvas.height / 2; if (y < centerY - neckHeight / 2) { // Top half const t = (y - 100) / (halfHeight - neckHeight / 2); return hourglassWidth * (1 - t * t) + neckWidth * t * t - 2 * glassThickness; } else if (y > centerY + neckHeight / 2) { // Bottom half const t = (canvas.height - 100 - y) / (halfHeight - neckHeight / 2); return hourglassWidth * (1 - t * t) + neckWidth * t * t - 2 * glassThickness; } else { // Neck return neckWidth - 2 * glassThickness; } } function isInsideHourglass(x, y, radius) { const hourglassCenter = canvas.width / 2; const hourglassWidthAtY = getHourglassWidthAtY(y); return ( x >= hourglassCenter - hourglassWidthAtY / 2 + radius && x <= hourglassCenter + hourglassWidthAtY / 2 - radius && y >= 100 + glassThickness + radius && y <= canvas.height - 100 - glassThickness - radius); } function getHourglassNormalAt(x, y) { const dy = 1; const widthAtY = getHourglassWidthAtY(y); const widthAtYPlusDy = getHourglassWidthAtY(y + dy); const dx = (widthAtYPlusDy - widthAtY) / 2; const length = Math.sqrt(dx * dx + dy * dy); return { x: dy / length, y: -dx / length }; } let grainsReachedBottom = 0; function updateSandGrains() { const spatialHash = new Map(); const cellSize = 4; const settlingThreshold = 0.05; // Velocity threshold for considering a grain settled const settlingFriction = 0.95; // Higher friction for settled grains const settlingYThreshold = canvas.height * 0.8; // 80% down the hourglass grains.forEach(grain => { let canSettle = grain.y > settlingYThreshold; // Apply small random forces in the neck area if ( grain.y > canvas.height / 2 - neckHeight / 2 && grain.y < canvas.height / 2 + neckHeight / 2) { grain.velocity.x += (Math.random() - 0.5) * 0.01; grain.velocity.y += Math.random() * 0.01; grain.isSettled = false; // Grains in the neck are ne.........完整代码请登录后点击上方下载按钮下载查看
网友评论0