console
const dotCount = 100
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
canvas.width = window.innerWidth
canvas.height = window.innerHeight
let pointsRange = Math.min(window.innerWidth, window.innerHeight)
baseDotRadius = (pointsRange > 300) ? 3 : 1.5
const maxLineDistance = pointsRange / 2
let xcenter = ctx.canvas.width / 2
let ycenter = (ctx.canvas.height / 2)
let dots = []
let alpha = 0
let beta = 0
let gamma = 0
let betaScrollAddition = 0
let gammaScrollAddition = 0
let timedAngleAddition = 0
const randomRange = (min, max) => Math.random() * (max - min) + min
function Dot (x = null, y = null, z = null) {
this.radius = baseDotRadius,
this.opacity = 1,
this.position = {
x: x || randomRange(-pointsRange, pointsRange),
y: y || randomRange(-pointsRange, pointsRange),
z: z || randomRange(-pointsRange, pointsRange),
}
this.initialPosition = {
x: this.position.x,
y: this.position.y,
z: this.position.z
}
}
Dot.prototype.update = function() {
let one = this.initialPosition.x * -Math.sin(beta)
let two = this.initialPosition.y * Math.cos(beta) * Math.sin(gamma)
let three = this.initialPosition.z * Math.cos(beta) * Math.cos(gamma)
this.position.z = one + two + three
let zPercentage = this.position.z / pointsRange
this.radius = baseDotRadius + ((baseDotRadius / 3) * zPercentage)
this.opacity = 0.5 + ((zPercentage + 1) / 4)
let depthOfFieldMultiplier = ((zPercentage + 1) / 2) + 0.5
one = this.initialPosition.x * Math.cos(alpha) * Math.cos(beta)
two = this.initialPosition.y * ((Math.cos(alpha) * Math.sin(beta) * Math.sin(gamma)) - (Math.sin(alpha) * Math.cos(gamma)))
three = this.initialPosition.z * ((Math.cos(alpha) * Math.sin(beta) * Math.cos(gamma)) + (Math.sin(alpha) * Math.sin(gamma)))
this.position.x = (one + two + three) * depthOfFieldMultiplier
one = this.initialPosition.x * Math.sin(alpha) * Math.cos(beta)
two = this.initialPosition.y * ((Math.sin(alpha) * Math.sin(beta) * Math.sin(gamma)) + (Math.cos(alpha) * Math.cos(gamma)))
three = this.initialPosition.z * ((Math.sin(alpha) * Math.sin(beta) * Math.cos(gamma)) - (Math.cos(alpha) * Math.sin(gamma)))
this.position.y = (one + two + three) * depthOfFieldMultiplier
}
const render = () => {
timedAngleAddition += 0.2 * Math.PI / 180
beta = timedAngleAddition + betaScrollAddition
gamma = timedAngleAddition + gammaScrollAddition
ctx.clearRect(0, 0, canvas.width, canvas.height)
dots.forEach((dot, index) => {
dot.update()
ctx.translate(xcenter + dot.position.x, ycenter + dot.position.y)
for (let i = index; i < dots.length; i++) {
let distance = Math.sqrt(Math.pow(dots[i].position.x - dot.position.x, 2) + Math.pow(dots[i].position.y - dot.position.y, 2) + Math.pow(dots[i].position.z - dot.position.z, 2))
if (distance < maxLineDistance) {
ctx.lineWidth = 1
ctx.strokeStyle = 'rgba(57, 136, 100, ' + ((1 - (distance / maxLineDistance)) / 2) + ')'
ctx.beginPath()
ctx.moveTo(0, 0)
ctx.lineTo(dots[i].position.x - dot.position.x, dots[i].position.y - dot.position.y)
ctx.stroke()
}
}
ctx.fillStyle = 'rgba(27, 136, 252, ' + dot.opacity + ')'
ctx.beginPath()
ctx.arc(0, 0, dot.radius, 0, 2 * Math.PI)
ctx.fill()
ctx.setTransform(1, 0, 0, 1, 0, 0)
})
window.requestAnimationFrame(render)
}
document.body.addEventListener('mousemove', (e) => {
let rect = e.target.getBoundingClientRect()
let percentageX = (e.clientX - rect.left) / document.body.clientWidth
let percentageY = (e.clientY - rect.top) / document.body.clientHeight
betaScrollAddition = (percentageX * 2 * Math.PI) - Math.PI
gammaScrollAddition = -(percentageY * 2 * Math.PI) - Math.PI
})
window.addEventListener('resize', () => {
canvas.width = window.innerWidth
canvas.height = window.innerHeight
pointsRange = Math.min(window.innerWidth, window.innerHeight)
})
for (let i = 0; i < dotCount; i++) {
dots.push(new Dot())
}
render()
<canvas id="canvas"></canvas>
<div class="page-wrap">
<h1>3D旋转矩阵</h1>
<p>(柳永春)</p>
</div>
body {
height: 100%;
}
body {
align-items: center;
background-color: #121212;
color: #fff;
display: flex;
font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
font-weight: 100;
justify-content: center;
line-height: 1.5;
text-align: center;
-webkit-font-smoothing: antialiased;
}
canvas {
height: 100%;
left: 0;
position: absolute;
top: 0;
width: 100%;
}
.page-wrap {
pointer-events: none;
}
.text-container {
max-width: 420px;
width: 90%;
}
h1 {
font-size: 22px;
}
p {
font-size: 13px;
}