用canvas让美女沉浸在音符的海洋里

本文介绍了如何使用canvas实现一个美女图片背景上的音符动态飘落效果。通过创建文字类、生成文字配置、更新文字状态以及动画循环,实现了音符在四个方向上的随机浮动和旋转,为页面增添趣味性。代码示例详细展示了实现过程,适合前端开发者参考学习。

今天跟大家聊点轻松的话题——《用canvas让美女沉浸在音符的海洋里》,效果如下所示:

2dee5cd478f5fc0713ce16d48c9d3c51.gif

茶已备好,只待君来!感谢关注 前端点线面 (>‿<),本号定期推荐原创深度好文,帮助每一位在前端领域打拼的伙伴们走向前列,关注号主可阅读14个门类100+篇)的原创文章;获取《前端百题斩》pdf(助力薪资double)、20+思维导图(知识系统化、记忆简单化),并进前端划水交流群(小姐姐多多

一、前期准备

为了能够实现这个效果,要进行如下准备:

  1. 一张梦寐以求的美女图片;

  2. 能够简单使用canvas;

  3. 准备一些音乐符号;

  4. 准备编辑器,由于该内容很偏向于实战,边敲边看效果更佳。

二、具体实现

美女图片仅仅是作为背景使用,所以就不过多的讲述了,直接设置一下background属性即可,下面主要讲述一下音符动画的实现。

2.1 音符符号

音符符号都可以用文字表示出来,没必要一个个自己去画,自己收集了一些,如下表格所示:【哎、当时自己不知道,还想着怎么画呢,最后经过一波神奇的百度才知道原来直接写文字就行,展示毫无障碍】

音符符号含义
八分音符
二个八分音符
十六分音符
降记号
升记号
𝄞音乐符号g谱号
音乐自然标志
𓏢竖琴
……

2.2    封装绘制文字的类

既然音符符号可以用文字表示,那复杂的事情就变得简单了,直接封装一个绘制文字的类,话不多说开整。

// index.js
class Draw {
    // 传入一个canvas的DOM节点
    constructor(canvasDom) {
        this._canvasDom = canvasDom;
        this.ctx = this._canvasDom.getContext('2d');
        this.width = this._canvasDom.width;
        this.height = this._canvasDom.height;
    }

    // 清空画布,毕竟要让音符动起来,不清空画布那还了得
    clearCanvas() {
        this.ctx.clearRect(0, 0, this.width, this.height);
    }

    // 根据传入的参数绘制文字
    drawText(textObj) {
        const {
            x,
            y,
            rotateRad,
            font,
            content,
            fillStyle = '#000000',
            textAlign = 'start',
            textBaseline = 'middle'
        } = textObj;

        this.ctx.save();
        this.ctx.translate(x, y);
        this.ctx.rotate(rotateRad);
        this.ctx.fillStyle = fillStyle;
        this.ctx.textAlign = textAlign;
        this.ctx.textBaseline = textBaseline;
        this.ctx.font = font;
        this.ctx.fillText(content, 0, 0);
        this.ctx.restore();
    }
}

2.3 创建文字条件

在封装文字类的时候已经发现其接收一个对象,然后根据对象来进行绘制,那我们接下来就是要根据需求创建一个这样的对象,怎么创建呢?如下所示:

// index.js
/**
* @param {string} content 绘制的内容
* @param {object} canvasObj canvas相关的内容
* param {object} conditionsObj 生成文字配置所需要的条件
*/
function createTextObj(content, canvasObj, conditionsObj) {
    const {width, height} = canvasObj;

    const {
        fontMin = 20,
        fontMax = 40,
        direction = 3, // 0:从左到右;1:从右到左;2:从上到下;3:从下到上
        baseStep = 0.5
    } = conditionsObj;

    let textX = 0;
    let textY = 0;

    // 注意:这个位置预制了direction条件,因为咱们的音符要动起来,所以设置一下从哪个方向进行浮动
    // 预制的初始坐标肯定不能被我们看到,所以需要根据方向决定初始坐标
    switch(direction) {
        case 0: {
            textX = (-0.1 - 0.1 * Math.random()) * width;
            textY = Math.random() * height;
            break;
        }
        case 1: {
            textX = (1.1 + 0.1 * Math.random()) * width;
            textY = Math.random() * height;
            break;
        }
        case 2: {
            textX = Math.random() * width;
            textY = (-0.1 - 0.1 * Math.random()) * height;
            break;
        }
        case 3: {
            textX = Math.random() * width;
            textY = (1.1 + 0.1 * Math.random()) * height;
            break;
        }
    }

    // 都是一个方位也不好看呀,所以要旋转一下
    const rotateRad = Math.PI * Math.random();
    const font = Math.random() * (fontMax - fontMin) + fontMin + 'px serif';
    // 设置一下直线运动和旋转运动的步长
    const step = Math.random() + baseStep;
    const rotateStep = Math.random() * Math.PI / 100;
    const fillStyle = 'rgba(' + Math.random() * 255 + ',' + Math.random() * 255 + ',' + Math.random() * 255 + ',' + (0.5 + 0.5 * Math.random()) + ')';

    return {
        x: textX,
        y: textY,
        rotateRad,
        font,
        fillStyle,
        content,
        step,
        rotateStep,
        direction
    };
}

2.4 更新文字配置

既然音符会动起来,则咱们就要逐帧进行更新,那更新函数就不能避免了,更新函数如下所示:

// index.js
/**
* @param {object} canvasObj canvas相关的内容
* @param {Array} textObjArr 文字配置对象的数组
* param {object} conditionsObj 生成文字配置所需要的条件
*/
function updateTextObjArr(canvasObj, textObjArr, conditionsObj) {
    const {width, height} = canvasObj;

    textObjArr.forEach((textObj, index) => {
        const {step, rotateStep, x, y, direction} = textObj;

        // 根据运动方向做对应的更新
        // 当音符符号运动出可视区域后直接在生成一个新的音符,毕竟要保证整个音符的数量
        switch(direction) {
            case 0: {
                if (x > width + 10) {
                    textObjArr[index] = createTextObj(getRandomValFromArr(TEXT_CONTENT_ARR), canvasObj, conditionsObj);
                } else {
                    textObj.x += step;
                }
                break;
            }
            case 1: {
                if (x < -10) {
                    textObjArr[index] = createTextObj(getRandomValFromArr(TEXT_CONTENT_ARR), canvasObj, conditionsObj);
                } else {
                    textObj.x -= step;
                }
                break;
            }
            case 2: {
                if (y > height + 10) {
                    textObjArr[index] = createTextObj(getRandomValFromArr(TEXT_CONTENT_ARR), canvasObj, conditionsObj);
                } else {
                    textObj.y += step;
                }
                break;
            }
            case 3: {
                if (y < -10) {
                    textObjArr[index] = createTextObj(getRandomValFromArr(TEXT_CONTENT_ARR), canvasObj, conditionsObj);
                } else {
                    textObj.y -= step;
                }
                break;
            }
        }

        textObj.rotateRad += rotateStep;
    });

    return textObjArr;
}

2.5 动起来

万事俱备,只欠东风,下面就是我们调动这些函数让整个内容动起来的关键时刻

<!DOCTYPE html>
<html>

<head>
    <title>音乐字符</title>
    <style>
        canvas {
            width: 500px;
            height: 300px;
            background: url('https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fwww.1230530.com%2Fpublic%2Fuploads%2Fimages%2F20211017%2F2538_20211017231117c46c5.jpg&refer=http%3A%2F%2Fwww.1230530.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1649658529&t=2b38137ce9cb301fc27a869e40b58629');
            background-size: 100% 100%;
        }
    </style>
</head>

<body>
    <canvas width="500" height="300" id="canvasId"></canvas>
    <script src="./index.js"></script>
    <script>
        // 从数组中随机获取一个值
        function getRandomValFromArr(arr) {
            return arr[Math.floor(Math.random() * arr.length)];
        }

        // 创建一系列的文字对象
        function createTextObjArr(count, canvasObj, conditionsObj) {
            const textObjArr = [];
            for (let i = 0; i < count; i++) {
                textObjArr.push(createTextObj(getRandomValFromArr(TEXT_CONTENT_ARR), canvasObj, conditionsObj));
            }

            return textObjArr;
        }

        // 初始音符
        const TEXT_CONTENT_ARR = ['♪', '♫', '♬', '♭', '♯', '𝄞', '♮', '𓏢'];

        // canvas节点
        const canvasDom = document.getElementById('canvasId');

        // 获取绘制文字的实例
        const drawInstance = new Draw(canvasDom);

        const canvasObj = {
            width: drawInstance.width,
            height: drawInstance.height
        };

        // 生成30个随机音符符号
        const count = 30;
        const conditionsObj = {
            direction: 2
        };

        const textObjArr = createTextObjArr(count, canvasObj, conditionsObj)

        // 动画动起来
        function animate() {
            drawInstance.clearCanvas();
            textObjArr.forEach(textObj => drawInstance.drawText(textObj));
            window.requestAnimationFrame(animate);
            updateTextObjArr(canvasObj, textObjArr, conditionsObj);
        }

        animate();
    </script>
</body>

</html>

··············· 执鸢者简介 ·················

看号主详细信息,来这

瞅一瞅

79aba9db5041eb35bf1144cfa8a00cdb.png

识别方二维码加我微信、拉你进交流

7bf8b73d82ee113304ee3b030f4ed8f0.png

【1】前端百题斩系列

【2】Vue系列

【3】React系列

【4】前端基础系列

【5】基础知识

【6】浏览器系列

【7】构建工具系列

【8】网络系列

【9】Node系列

【10】源码系列

【11】前端有意思

【12】手撕算法系列

【13】个人总结

【14】杂科

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值