Permalink
Cannot retrieve contributors at this time
<!doctype html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<title>Bobbing 1</title> | |
<link rel="stylesheet" href="../include/style.css"> | |
<style type="text/css"> | |
#canvas { | |
background: #fff; | |
} | |
</style> | |
</head> | |
<body> | |
<header> | |
Example from <a href="http://amzn.com/1430236655?tag=html5anim-20"><em>Foundation HTML5 Animation with JavaScript</em></a> | |
</header> | |
<canvas id="canvas" width="640" height="340"></canvas> | |
<script type="module"> | |
import {captureMouse, circleContainsPoint, parseColor} from '../include/utils.js'; | |
import Ball from './classes/ball-with-text.js'; | |
window.onload = function () { | |
const canvas = document.getElementById('canvas'), | |
context = canvas.getContext('2d'), | |
{width, height} = canvas, | |
mouse = captureMouse(canvas), | |
spring = 0.03, | |
friction = 0.9, | |
offset = 50; | |
let balls = []; | |
let tempBalls = []; | |
let ballNum = 8; | |
let isDragingBall = false; | |
while(ballNum--) { | |
let ball = new Ball(10, parseColor(Math.random() * 0xffffff)) | |
ball.x = Math.random() * width; | |
ball.y = Math.random() * height; | |
tempBalls.push(ball) | |
} | |
// 让点按X轴升序排序 | |
tempBalls = tempBalls.sort((ballA, ballB) => { | |
return ballA.x - ballB.x | |
}) | |
// 找X轴左右极点 | |
let firstBall = tempBalls[0], | |
lastBall = tempBalls[tempBalls.length -1]; | |
let smallXBalls = tempBalls.filter(ball => ball.x === firstBall.x), | |
bigXBalls = tempBalls.filter(ball => ball.x === lastBall.x) | |
// 处理左右极点有多个的情况 | |
if (smallXBalls.length > 1) { | |
smallXBalls.sort((ballA, ballB) => { | |
return ballB.y - ballA.y | |
}) | |
} | |
if (bigXBalls.length > 1) { | |
bigXBalls.sort((ballA, ballB) => { | |
return ballB.y - ballA.y | |
}) | |
} | |
firstBall = smallXBalls[0] | |
lastBall = bigXBalls[0] | |
// 获得极点连线的角度 | |
let splitLineAngle = Math.atan2(lastBall.y - firstBall.y, lastBall.x - firstBall.x); | |
let upperBalls = [], | |
lowerBalls = []; | |
// 所有其他点跟firstBall计算角度 | |
// 大于splitLineAngle的都是下链 | |
// 其他是上链 | |
tempBalls.forEach(ball => { | |
if (ball === firstBall || ball === lastBall) { | |
return false | |
} | |
let angle = Math.atan2(ball.y - firstBall.y, ball.x - firstBall.x); | |
if (angle > splitLineAngle) { | |
lowerBalls.push(ball) | |
} else { | |
upperBalls.push(ball) | |
} | |
}) | |
// 处理X轴相同情况的排序 | |
lowerBalls = lowerBalls.sort((ballA, ballB) => { | |
if (ballA.x !== ballB.x) { | |
return ballA.x - ballB.x | |
} | |
return ballB.y - ballA.y | |
}) | |
upperBalls = upperBalls.sort((ballA, ballB) => { | |
if (ballA.x !== ballB.x) { | |
return ballB.x - ballA.x | |
} | |
return ballB.y - ballB.x | |
}) | |
// 逆时针连接所有的点 | |
balls = [firstBall].concat(lowerBalls, [lastBall], upperBalls) | |
balls = balls.map((ball, i) => { | |
ball.text = i + 1; | |
return ball | |
}) | |
console.log(balls) | |
console.log({ | |
firstBall: firstBall.text, | |
lastBall: lastBall.text, | |
upperBalls: upperBalls.map(ball => ball.text), | |
lowerBalls: lowerBalls.map(ball => ball.text) | |
}) | |
function getCenterPoint(ball1, ball2) { | |
return { | |
x: (ball1.x + ball2.x) / 2, | |
y: (ball1.y + ball2.y) / 2 | |
} | |
} | |
function getRealTarget(ball, mouse, offset) { | |
const dx = ball.x - mouse.x, | |
dy = ball.y - mouse.y, | |
angle = Math.atan2(dy, dx); | |
return { | |
x: mouse.x + Math.cos(angle) * offset, | |
y: mouse.y + Math.sin(angle) * offset | |
}; | |
} | |
// 拖拽球 | |
function dragBall(ball) { | |
if (ball.isDrag) { | |
ball.x = mouse.x; | |
ball.y = mouse.y; | |
} | |
} | |
// 应用弹力 | |
function applySpring(ball, centerPoint) { | |
const target = getRealTarget(ball, centerPoint, offset); | |
ball.vx += (target.x - ball.x) * spring; | |
ball.vy += (target.y - ball.y) * spring; | |
} | |
// 最终移动球 | |
function moveBall(ball) { | |
ball.vx *= friction; | |
ball.vy *= friction; | |
ball.x += ball.vx; | |
ball.y += ball.vy; | |
} | |
// 遍历所有的球,设定球之间的作用力 | |
function setAllBalls() { | |
for(let i = 0, len = balls.length; i < len; i++) { | |
let nextIndex = (i + 1) % len; | |
let ballA = balls[i], | |
ballB = balls[nextIndex]; | |
const centerPoint = getCenterPoint(ballA, ballB) | |
if (!ballA.isDrag) { | |
applySpring(ballA, centerPoint) | |
} | |
if (!ballB.isDrag) { | |
applySpring(ballB, centerPoint) | |
} | |
} | |
} | |
function drawLines() { | |
context.beginPath(); | |
context.moveTo(balls[0].x, balls[0].y); | |
for (let i = 1, len = balls.length; i <= len; i++) { | |
let index = i % len; | |
context.lineTo(balls[index].x, balls[index].y); | |
} | |
context.stroke(); | |
} | |
canvas.addEventListener('mousedown', () => { | |
balls.forEach(ball => { | |
if (circleContainsPoint(ball, mouse)) { | |
if (!isDragingBall) { | |
ball.isDrag = true | |
isDragingBall = true | |
} | |
} | |
}) | |
}, false); | |
canvas.addEventListener('mouseup', () => { | |
balls.forEach(ball => { | |
ball.isDrag = false | |
}) | |
isDragingBall = false | |
}, false) | |
;(function draw(){ | |
window.requestAnimationFrame(draw); | |
context.clearRect(0, 0, width, height); | |
balls.forEach(dragBall); | |
setAllBalls(); | |
balls.forEach(ball => moveBall(ball)); | |
drawLines() | |
balls.forEach(ball => ball.draw(context)); | |
}()); | |
} | |
</script> | |
</body> | |
</html> |