问题描述
理念
我从 YouTube 视频中偶然发现了乘法圆的想法,我认为尝试使用 JavasSript 和 canvas 元素重新创建它会很有趣。 The Original Video
问题
我尽我所能地平滑了动画,但它看起来仍然没有我想要的那么合适。我怀疑想出一个解决方案需要大量的数学知识。详细掌握问题我觉得看代码比较容易
window.onload = () => {
const app = document.querySelector('#app')
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
const { cos,sin,PI } = Math
const Tao = PI * 2
const width = window.innerWidth
const height = window.innerHeight
const cx = width / 2
const cy = height / 2
const baseNumberingSystem = 200
const stop = 34
let color = 'teal'
let multiplier = 0
let drawQue = []
// setup canvas
canvas.width = width
canvas.height = height
class Circle {
constructor(x,y,r,strokeColor,fillColor) {
this.x = x
this.y = y
this.r = r
this.strokeColor = strokeColor || '#fff'
this.fillColor = fillColor || '#fff'
}
draw(stroke,fill) {
ctx.moveto(this.x,this.y)
ctx.beginPath()
ctx.arc(this.x,this.y,this.r,Tao)
ctx.closePath()
if (fill) {
ctx.fillStyle = this.fillColor
ctx.fill()
}
if (stroke) {
ctx.strokeStyle = this.strokeColor
ctx.stroke()
}
}
createChildCircleAt(degree,radius,fillColor) {
const radian = degreetoRadian(degree)
const x = this.x + (this.r * cos(radian))
const y = this.y + (this.r * sin(radian))
return new Circle(x,fillColor)
}
divideCircle(nTimes,radius) {
const degree = 360 / nTimes
let division = 1;
while (division <= nTimes) {
drawQue.push(this.createChildCircleAt(division * degree,radius))
division++
}
}
}
function degreetoRadian(degree) {
return degree * (PI / 180)
}
function draw() {
const mainCircle = new Circle(cx,cy,cy * 0.9)
// empty the que
drawQue = []
// clear canvas
ctx.clearRect(0,width,height)
ctx.fillStyle = "black"
ctx.fillRect(0,height)
// redraw everything
mainCircle.draw()
mainCircle.divideCircle(baseNumberingSystem,4)
drawQue.forEach(item => item.draw())
// draw modular times table
for (let i = 1; i <= drawQue.length; i++) {
const product = i * multiplier;
const firstPoint = drawQue[i]
const secondPoint = drawQue[product % drawQue.length]
if (firstPoint && secondPoint) {
ctx.beginPath()
ctx.moveto(firstPoint.x,firstPoint.y)
ctx.strokeStyle = color
ctx.lineto(secondPoint.x,secondPoint.y)
ctx.closePath()
ctx.stroke()
}
}
}
function animate() {
multiplier+= 0.1
multiplier = parseFloat(multiplier.toFixed(2))
draw()
console.log(multiplier,stop)
if (multiplier === stop) {
clearInterval(animation)
}
}
app.appendChild(canvas)
let animation = setInterval(animate,120)
}
所以主要问题来自于我们将乘数增加小于 1 的值以试图使动画感觉更流畅。示例:multiplier+= 0.1
。当我们这样做时,它会增加我们绘制函数中的 if 块失败的次数,因为 secondPoint 将返回 null。
const product = i * multiplier; // this is sometimes a decimal
const firstPoint = drawQue[i]
const secondPoint = drawQue[product % drawQue.length] // therefore this will often not be found
// Then this if block wont execute. Which is good because if it did we the code would crash
// But I think what we need is an if clause to still draw a line to a value in between the two
// closest indices of our drawQue
if (firstPoint && secondPoint) {
//...
}
可能的解决方案
我认为我需要做的是当我们找不到 secondPoint 时获取 product % drawQue.length
的剩余部分并使用它在 drawQue 数组中两个最近的圆之间创建一个新圆并使用它新圆作为我们线的第二个点。
解决方法
如果你使用 requestAnimationFrame 它看起来很流畅
function animate() {
if (multiplier != stop) {
multiplier+= 0.1
multiplier = parseFloat(multiplier.toFixed(2))
draw()
requestAnimationFrame(animate);
}
}
app.appendChild(canvas)
animate()
,
我可能的解决方案最终奏效了。对于任何感兴趣的人,我会在此处留下添加的 else if 块。我还必须在制作圆对象时将度数值存储在它们中,并计算圆的每个细分之间的距离。
添加 If Else 语句
for (let i = 1; i <= drawQue.length; i++) {
const product = i * multiplier;
const newIndex = product % drawQue.length
const firstPoint = drawQue[i]
const secondPoint = drawQue[newIndex]
if (firstPoint && secondPoint) {
ctx.beginPath()
ctx.moveTo(firstPoint.x,firstPoint.y)
ctx.strokeStyle = color
ctx.lineTo(secondPoint.x,secondPoint.y)
ctx.closePath()
ctx.stroke()
} else if (!secondPoint) {
const percent = newIndex % 1
const closest = drawQue[Math.floor(newIndex)]
const newDegree = closest.degree + (degreeIncriments * percent)
const target = mainCircle.createChildCircleAt(newDegree,4)
if (firstPoint && target) {
ctx.beginPath()
ctx.moveTo(firstPoint.x,firstPoint.y)
ctx.strokeStyle = color
ctx.lineTo(target.x,target.y)
ctx.closePath()
ctx.stroke()
}
}
其他变化
// ...
const degreeIncriments = 360 / baseNumberingSystem
// ...
class Circle {
constructor(/* ... */,degree )
// ...
this.degree = degree || 0
}
希望有人觉得这很有用...