SOURCE

console 命令行工具 X clear

                    
>
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;
}