console
/**
* 云朵类
*/
class Cloud {
constructor(props) {
this.x = 0
this.y = 0
this.xpos = 0
this.ypos = 0
this.zpos = 0
this.rotation = 0
this.scaleX = 1
this.scaleY = 1
this.visible = true
Object.assign(this, props)
}
draw(ctx) {
ctx.save()
ctx.translate(this.x, this.y)
ctx.rotate(this.rotation)
ctx.scale(this.scaleX, this.scaleY)
ctx.drawImage(this.image, 0, 0)
ctx.restore()
}
}
function captureMouse(element) {
const mouse = { x: 0, y: 0, e: null }
element.addEventListener('mousemove', e => {
mouse.x = e.offsetX
mouse.y = e.offsetY
mouse.e = e
})
return mouse
}
const image = new Image()
image.onload = init
image.src = 'https://static.webfed.cn/cloud.png'
// 初始化
function init() {
const canvas = document.getElementById('canvas')
const width = canvas.width = document.documentElement.clientWidth
const height = canvas.height = document.documentElement.clientHeight
const ctx = canvas.getContext('2d')
const clouds = createCloud(50)
// 观察点距离(镜头焦距)
const fl = 250
// 消失点,设为画布中心
const vpX = width / 2
// 消失点,设为画布中心
const vpY = height / 2
// 鼠标在canvas上移动的坐标
const mouse = captureMouse(canvas)
let angleY // Y轴角度
let angleX // X轴角度
/**
* 创建云
* @param {Number} nums 云朵数量
* @returns {Object} 返回云朵类集合
*/
function createCloud(nums) {
const clouds = []
while (nums--) {
clouds.push(new Cloud({
image,
xpos: Math.random() * width - width / 2,
ypos: Math.random() * height - height / 2,
zpos: Math.random() * 400 - 200
}))
}
return clouds
}
/**
* 绕X轴旋转
* @param {Object} cloud 云朵
* @param {Number} angle 角度
*/
function rotateX(cloud, angle) {
const cos = Math.cos(angle)
const sin = Math.sin(angle)
// 通过X轴旋转公式得到ypos, zpos
const y1 = cloud.ypos * cos - cloud.zpos * sin
const z1 = cloud.zpos * cos + cloud.ypos * sin
cloud.ypos = y1
cloud.zpos = z1
}
/**
* 绕Y轴旋转
* @param {Object} cloud 云朵
* @param {Number} angle 角度
*/
function rotateY(cloud, angle) {
const sin = Math.sin(angle)
const cos = Math.cos(angle)
// 通过Y轴旋转公式得到xpos, zpos
const x1 = cloud.xpos * cos - cloud.zpos * sin
const z1 = cloud.zpos * cos + cloud.xpos * sin
cloud.xpos = x1
cloud.zpos = z1
}
/**
* 透视(改变x,y和scale值)
* @param {Object} cloud 云朵
*/
function setPerspective(cloud) {
// 防止比例出错要做一个判断
if (cloud.zpos > -fl) {
const scale = fl / (fl + cloud.zpos) // 产生 0~1 之间的一个值,用来做缩放和靠近消失点的一个比例
cloud.scaleX = cloud.scaleY = scale
cloud.x = vpX + cloud.xpos * scale
cloud.y = vpY + cloud.ypos * scale
cloud.visible = true
} else {
cloud.visible = false
}
}
/**
* 计算位置
* @param {Object} cloud 云朵
*/
function move(cloud) {
rotateX(cloud, angleX)
rotateY(cloud, angleY)
setPerspective(cloud)
}
/**
* 排序,让云朵zpos值大的在前面,提升视觉效果
* @param {Object} a 云朵a
* @param {Object} b 云朵b
*/
function zsort(a, b) {
return b.zpos - a.zpos
}
function draw(cloud) {
if (cloud.visible) {
cloud.draw(ctx)
}
}
; (function drawFrame() {
window.requestAnimationFrame(drawFrame)
ctx.clearRect(0, 0, width, height)
angleY = (mouse.x - vpX) * 0.00005
angleX = (mouse.y - vpY) * 0.00005
clouds.sort(zsort)
clouds.forEach(move)
clouds.forEach(draw)
})();
}
<canvas id="canvas" style="width:100%;height:100%;background: #83aeda;"></canvas>
html, body {
width: 100%;
height: 100%;
overflow: hidden;
margin: 0;
}