弹力
、磁力
和引力
其实本质上很接近。
之所以说相近,是因为他们都是一种长距离作用力
。
弹簧
在被压缩的状况下会产生扩张
的力量,而在拉长的状况下则会进行收缩
;
引力
是指当两个具有质量
的东西存在於同一空间时,他们会有互相吸引的力量,这个力量也会跟距离呈负相关
;
最後磁力
则是大家都知道的同极相斥、异极相吸
。
其实磁力/引力
的部分我打算只用一个案例进行说明,毕竟性质相像。
下图是这次案例的示意图:
首先,我们在这次的案例中可以用滑鼠移动去操作一个磁铁
,磁铁
本身的磁力具有最大可影响范围
,也就是我们在图片中提到的磁力圈
,而在空间中我们会摆放几颗静止的小铁球
,当滑鼠靠近小铁球
的时候,他们就会被吸进去磁力圈
内部。
大致上是这样的概念。
简单录了一段实作的影片:
Github Page: https://mizok.github.io/ithelp2021/magnet-animation.html
这个案例其实不怎麽难,我们马上就来试试看:
import { Vector2D } from '../class'
const CANVAS = {
width: 800,
height: 600,
background: 'gray'
}
const BALLS = [
{
x: 300,
y: 300,
radius: 25,
mass: 50,
},
{
x: 380,
y: 380,
radius: 25,
mass: 50,
},
{
x: 375,
y: 330,
radius: 25,
mass: 50,
},
{
x: 200,
y: 200,
radius: 55,
mass: 100,
},
{
x: 250,
y: 250,
radius: 15,
mass: 50,
},
{
x: 450,
y: 450,
radius: 10,
mass: 10,
},
{
x: 600,
y: 600,
radius: 75,
mass: 50,
},
]
const getDist = (x0, y0, x1, y1) => {
return Math.sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0));
}
const MAGNET_SIZE = 500;
const MAGNET_FORCE_CONST = 7000;
class Circle {
constructor(x, y, radius, fillColor = 'transparent', strokeColor = 'black', lineWidth = 1) {
this.x = x;
this.y = y;
this.fillColor = fillColor;
this.strokeColor = strokeColor;
this.lineWidth = lineWidth;
this.radius = radius;
}
draw(ctx) {
ctx.save()
ctx.fillStyle = this.fillColor;
ctx.strokeStyle = this.strokeColor;
ctx.lineWidth = this.lineWidth;
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.restore();
}
}
class Ball extends Circle {
constructor(x, y, radius, mass, fillColor = 'rgba(0,0,0,0.25)', strokeColor = 'transparent') {
super(x, y, radius, fillColor, strokeColor);
this.friction = 0.995;
this.force = new Vector2D(0, 0);
this.acc = new Vector2D(0, 0);
this.velocity = new Vector2D(0, 0);
this.mass = mass;
}
refreshLocation(dt) {
this.x += this.velocity.x * dt;
this.y += this.velocity.y * dt;
}
refreshSpeed(dt) {
this.velocity.scaleBy(this.friction);
this.velocity.incrementBy(this.acc.multiply(dt));
}
}
class MagnetAnimation {
constructor(ctx) {
this.ctx = ctx;
this.cvs = ctx.canvas;
this.frameIsPaused = false;
this.balls = [];
this.mouse = {
x: 0,
y: 0
}
this.magnet = null
this.init();
}
init() {
this.time = performance.now();
this.setCanvasSize();
this.initEvents();
this.initBalls();
this.animate();
}
initBalls() {
BALLS.forEach((o, i) => {
const ball = new Ball(o.x, o.y, o.radius, o.mass);
this.balls.push(ball);
})
}
initEvents() {
this.initVisibilityChangeEvent();
this.initMouseEvent();
}
initVisibilityChangeEvent() {
window.addEventListener('visibilitychange', () => {
if (document.visibilityState !== "visible") {
this.frameIsPaused = true;
}
else {
this.frameIsPaused = false;
this.time = performance.now();
}
});
}
initMouseEvent() {
this.cvs.addEventListener('mousedown', () => {
this.isClicked = true;
})
this.cvs.addEventListener('mousemove', (e) => {
if (!this.isClicked) return;
let rect = this.cvs.getBoundingClientRect();
this.mouse.x = e.clientX - rect.left;
this.mouse.y = e.clientY - rect.top;
})
this.cvs.addEventListener('mouseup', () => {
console.log(this.balls);
this.isClicked = false;
})
this.cvs.addEventListener('mouseleave', () => {
this.isClicked = false;
})
}
drawAll() {
this.drawMagnet();
this.drawBalls();
}
drawMagnet() {
new Circle(this.mouse.x, this.mouse.y, MAGNET_SIZE / 2).draw(this.ctx);
}
drawBalls() {
this.balls.forEach((o, i) => {
o.draw(this.ctx);
})
}
animate() {
if (this.frameIsPaused) {
this.animate();
}
const $this = this;
const frameDelay = 10 // frameDelay 是用来做动画抽帧的常数,可以想像成会让动画加速!
const dt = (performance.now() - this.time) * frameDelay / 1000;
this.ctx.clearRect(0, 0, this.cvs.width, this.cvs.height);
this.drawAll();
this.refreshBallsLocation(dt);
this.refreshBallsSpeed(dt);
this.refreshBallsAcc();
this.time = performance.now();
requestAnimationFrame(this.animate.bind($this));
}
refreshBallsLocation(dt) {
this.balls.forEach((o, i) => {
o.refreshLocation(dt);
})
}
refreshBallsSpeed(dt) {
this.balls.forEach((o, i) => {
o.refreshSpeed(dt);
})
}
refreshBallsAcc() {
this.balls.forEach((o, i) => {
const distToMouse = getDist(this.mouse.x, this.mouse.y, o.x, o.y);
if (distToMouse < MAGNET_SIZE / 2 + o.radius && distToMouse > 1e-2) {
o.force = new Vector2D(this.mouse.x - o.x, this.mouse.y - o.y).para(MAGNET_FORCE_CONST / (distToMouse));
o.acc = o.force.multiply(1 / o.mass);
}
else {
o.force = new Vector2D(0, 0);
o.acc = new Vector2D(0, 0);
}
})
}
setCanvasSize() {
this.cvs.width = CANVAS.width;
this.cvs.height = CANVAS.height;
this.cvs.style.backgroundColor = CANVAS.background;
}
}
(() => {
let ctx = document.querySelector('canvas').getContext('2d');
let instance = new MagnetAnimation(ctx);
})()
比较需要解释的应该也就是受力计算的阶段,我在这个部分使用的是 MAGNET_FORCE_CONST / (distToMouse)
去做磁力的运算。
在高中时期我们学到的公式其实长这样:
图片来自 <基本电学 - 台科大图书股份有限公司出版>
在上图中指出,磁力在现实生活中其实是和距离的平方
成反比
的,但是其实很多时候我们去看canvas
的物理模拟
案例,会发现运算的过程似乎跟理论不太一样(我们是取与距离成反比
的运算方法)。
通常这种情形有几个原因
:
动画
,而不是实验
。动画追求的是戏剧效果
而不是物理上的正确性
实际理论
的计算可能会耗费较多的浏览器资源
,所以需要作出取舍当然我们这边也可以改成『取与距离平方成反比
』来做计算,但是我只是想要藉着这个机会提到这个问题。
在越复杂的案例其实越常有这种事发生,像是若实际理论中有大量开根号或大量巢状回圈的计算需求,可能就会因为需要减免资源消耗,而采用结果接近,但细节有差异
的运算过程。
有时候看源码反而需要花更多时间去理解运算的过程,不能完全依赖书上写的公式。
以上就是本次磁力/引力
案例的实作~希望大家喜欢 :D
<<: # Day 26 Page migration (一)
>>: Day20 - this&Object Prototypes Ch3 Objects - Review 开头
How to choose the best mobile repair training inst...
中秋连假也要练习,希望大家也可以努力撑下去,剩13天了! 接续昨天的练习~ 1.还可以调整灯管的颜色...
接续昨天的范例。 今天要聊的是 ng-container 与 ng-template <ng-...
今日目标 实作SAT碰撞侦测 SAT的作法 回顾一下,AABB的作法是不管是甚麽形状,都把物件包进矩...
介绍 Docs 转 Api Blueprint 的整体流程架构与相关服务。 今日要点: 》API篇...