详解Canvas 实现炫丽的粒子运动效果(粒子生成文字)


Posted in HTML / CSS onFebruary 01, 2018

没有最好,只有更好,如题所示,这篇文章只要是分享一个用 Canvas 来实现的粒子运动效果。感觉有点标题党了,但换个角度,勉勉强强算是炫丽吧,虽然色彩上与炫丽无关,但运动效果上还是算得上有点点炫的。不管怎么样,我们还是开始这个所谓的炫丽效果吧!

直接上代码 ,不懂可以看代码注释。估计就会看明白大概的思路了。

html 代码

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Canvas 实现炫丽的粒子运动效果-云库前端</title>
<style>
* {
    margin: 0;
    padding: 0;
}
html,
body {
    width: 100%;
    height: 100%;
}
canvas {
    display: block;
    background: #000;
}
body::-webkit-scrollbar{
    display: none;
}
.operator-box{
    position: fixed;
    top: 0;
    left: 50%;
    border: 1px solid #fff;
    background: rgba(255,255,255,0.5);
    padding: 20px 10px;
    -webkit-transform: translateX(-50%);
    transform: translateX(-50%);
}
.back-type,.back-animate{
    margin-right: 20px;
}
.flex-box{
    display: flex;
    justify-content: center;
    align-items: center;
}
#input-text{
    line-height: 35px;
    width: 260px;
    height: 35px;
    background: rgba(0, 0, 0,0.7);
    color: #fff;
    font-size: 16px;
    border: none;
    outline: none;
    text-indent: 12px;
    box-shadow: inset 0 0 12px 1px rgba(0,0,0,0.7);
}
#input-text::placeholder{
    color: #ccc;
    line-height: 55px;
    height: 55px;
}
select{
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
    border: none;
    padding: 0px 20px 0px 6px;
    height: 35px;
    color: #fff;
    text-align: left;
    background: rgba(0, 0, 0,0.7) url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAICAYAAAAx8TU7AAAAOUlEQ…R4gPgWEIMAiOYBCS4C8ZDAIrBq4gigNkztQEFMi6AuQHESAPMeXiEMiWfpAAAAAElFTkSuQmCC) no-repeat 190px 12px;
    background-size: 5px 8px;
    box-shadow: inset 0 0 12px 1px rgba(0,0,0,0.7);
}
</style>
</head>
<body>
<div class="operator-box">
<div class="flex-box">
    <div class="back-type">散开类型:
        <select name="" id="selectType">
            <option value="back">归位</option>
            <option value="auto">随机</option>
        </select>
    </div>
    <div class="back-animate">散开效果(对归位有效):
       <select class="back-dynamics" id="selectDynamics">
           <option value="spring">dynamics.spring</option>
           <option value="bounce">dynamics.bounce</option>
           <option value="forceWithGravity">dynamics.forceWithGravity</option>
           <option value="gravity">dynamics.gravity</option>
           <option value="easeInOut">dynamics.easeInOut</option>
           <option value="easeIn">dynamics.easeIn</option>
           <option value="easeOut">dynamics.easeOut</option>
           <option value="linear">dynamics.linear</option>
       </select>
    </div>
    <div class="input-box"><input type="text" placeholder="输入汉字后回车" id="input-text"></div>
</div>
</div>
<script src="dynamics.min.js"></script>
<script src="index.js"></script>
<script>
var iCircle = new Circle();
</script>
</body>
</html>

HTML 代码不多,只要是几个操作元素。这里一看就明白。不费过多口舌。我们来看看本文的主角 JavaScript 代码,不过,在看代码前,我们不妨先听听实现这个效果的思路:

  1. 首先,我们得先生成一堆群众演员(粒子);
  2. 把每个粒子的相关参数挂到自身的一些属性上,因为第个粒子都会有自己的运动轨迹;
  3. 接着得让它们各自运动起来。运动有两种(自由运动和生成文字的运动);

JavaScript 代码中使用了三个 Canvas 画布,this.iCanvas(主场)、this.iCanvasCalculate(用来计算文字宽度)、this.iCanvasPixel(用于画出文字,并从中得到文字对应的像素点的位置坐标)。

this.iCanvasCalculate 和 this.iCanvasPixel 这两个无需在页面中显示出来,只是辅助作用。

下面就献上棒棒的 JS 实现代码

function Circle() {
    var This = this;
    this.init();
    this.generalRandomParam();
    this.drawCircles();
    this.ballAnimate();
    this.getUserText();
    // 窗口改变大小后,生计算并获取画面
    window.onresize = function(){
        This.stateW = document.body.offsetWidth;
        This.stateH = document.body.offsetHeight;
        This.iCanvasW = This.iCanvas.width = This.stateW;
        This.iCanvasH = This.iCanvas.height = This.stateH;
        This.ctx = This.iCanvas.getContext("2d");
    }
}
// 初始化
Circle.prototype.init = function(){
    //父元素宽高
    this.stateW = document.body.offsetWidth;
    this.stateH = document.body.offsetHeight;
    this.iCanvas = document.createElement("canvas");
    // 设置Canvas 与父元素同宽高
    this.iCanvasW = this.iCanvas.width = this.stateW;
    this.iCanvasH = this.iCanvas.height = this.stateH;
    // 获取 2d 绘画环境
    this.ctx = this.iCanvas.getContext("2d");
    // 插入到 body 元素中
    document.body.appendChild(this.iCanvas);
    this.iCanvasCalculate = document.createElement("canvas");
    // 用于保存计算文字宽度的画布
    this.mCtx =  this.iCanvasCalculate.getContext("2d");
    this.mCtx.font = "128px 微软雅黑";
    this.iCanvasPixel = document.createElement("canvas");
    this.iCanvasPixel.setAttribute("style","position:absolute;top:0;left:0;");
    this.pCtx = null; // 用于绘画文字的画布
    // 随机生成圆的数量
    this.ballNumber = ramdomNumber(1000, 2000);
    // 保存所有小球的数组
    this.balls = [];
    // 保存动画中最后一个停止运动的小球
    this.animte = null;
    this.imageData = null;
    this.textWidth = 0; // 保存生成文字的宽度
    this.textHeight = 150; // 保存生成文字的高度
    this.inputText = ""; // 保存用户输入的内容
    this.actionCount = 0;
    this.ballActor = []; // 保存生成文字的粒子
    this.actorNumber = 0; // 保存生成文字的粒子数量
    this.backType = "back"; // 归位
    this.backDynamics = ""; // 动画效果
    this.isPlay = false; // 标识(在生成文字过程中,不能再生成)
}
// 渲染出所有圆
Circle.prototype.drawCircles = function () {
    for(var i=0;i<this.ballNumber;i++){
        this.renderBall(this.balls[0]);
    }
}
// 获取用户输入文字
Circle.prototype.getUserText = function(){
    This = this; // 保存 this 指向
    ipu = document.getElementById("input-text");
    ipu.addEventListener("keydown",function(event){
        if(event.which === 13){ // 如果是回车键
            ipu.value = ipu.value.trim(); // 去头尾空格
            var pat = /[\u4e00-\u9fa5]/; // 中文判断
            var isChinese = pat.test(ipu.value);
            if(ipu.value.length !=0 && isChinese){
                This.inputText = ipu.value;
            }else{
                alert("请输入汉字");
                return;
            }
            if(This.isPlay){
                return
            }
            This.getAnimateType();
            This.getTextPixel();
            This.isPlay = true;
        }
    });
}
// 计算文字的宽
Circle.prototype.calculateTextWidth = function () {
    this.textWidth = this.mCtx.measureText(this.inputText).width;
}
// 获取文字像素点
Circle.prototype.getTextPixel = function () {
    if(this.pCtx){
        this.pCtx.clearRect(0,0,this.textWidth,this.textHeight);
    }
    this.calculateTextWidth(this.inputText);
    this.iCanvasPixel.width = this.textWidth;
    this.iCanvasPixel.height = this.textHeight;
    this.pCtx =  this.iCanvasPixel.getContext("2d");
    this.pCtx.font = "128px 微软雅黑";
    this.pCtx.fillStyle = "#FF0000";
    this.pCtx.textBaseline = "botom";
    this.pCtx.fillText(this.inputText,0,110);
    this.imageData = this.pCtx.getImageData(0,0,this.textWidth,this.textHeight).data;
    this.getTextPixelPosition(this.textWidth,this.textHeight);
}
// 获取文字粒子像素点位置
Circle.prototype.getTextPixelPosition = function (width,height) {
    var left = (this.iCanvasW - width)/2;
    var top = (this.iCanvasH - height)/2;
    var space = 4;
    this.actionCount = 0;
    for(var i=0;i<this.textHeight;i+=space){
        for(var j=0;j<this.textWidth;j+=space){
            var index = j*space+i*this.textWidth*4;
            if(this.imageData[index] == 255){
                if(this.actionCount<this.ballNumber){
                    this.balls[this.actionCount].status = 1;
                    this.balls[this.actionCount].targetX = left+j;
                    this.balls[this.actionCount].targetY = top+i;
                    this.balls[this.actionCount].backX = this.balls[this.actionCount].x;
                    this.balls[this.actionCount].backY = this.balls[this.actionCount].y;
                    this.ballActor.push(this.balls[this.actionCount]);
                    this.actionCount++;
                }
            }
        }
        this.actorNumber = this.ballActor.length;
    }
    this.animateToText();
}
// 粒子运动到指定位置
Circle.prototype.animateToText = function(){
    for(var i=0;i<This.actorNumber;i++){
        dynamics.animate(This.ballActor[i], {
          x: this.ballActor[i].targetX,
          y: this.ballActor[i].targetY
        },{
            type: dynamics.easeIn,
            duration: 1024,
        });
    }
    setTimeout(function(){
        This.ballbackType();
    },3000);
}
// 粒子原路返回
Circle.prototype.ballBackPosition = function(){
    for(var i=0;i<This.actorNumber;i++){
        var ball = This.ballActor[i];
        dynamics.animate(ball, {
          x: ball.backX,
          y: ball.backY
        },{
            type: dynamics[this.backDynamics],
            duration: 991,
            complete:this.changeStatus(ball)
        });
    }
}
// 获取类型|动画效果
Circle.prototype.getAnimateType = function() {
    var selectType = document.getElementById("selectType");
    var selectDynamics = document.getElementById("selectDynamics");
    this.backType = selectType.options[selectType.options.selectedIndex].value;
    this.backDynamics = selectDynamics.options[selectDynamics.options.selectedIndex].value;
}
// 复位散开
Circle.prototype.ballbackType = function(){
    if(this.backType == "back"){
        this.ballBackPosition();
    }else{
        this.ballAutoPosition();
    }
    this.ballActor = [];
}
// 随机散开
Circle.prototype.ballAutoPosition = function(ball){
    for(var i=0;i<this.actorNumber;i++){
        this.changeStatus(this.ballActor[i])
    }
}
// 更改小球状态
Circle.prototype.changeStatus = function(ball){
    ball.status = 0;
    if(this.isPlay == true){
        this.isPlay = false;
    }
}
// 随机生成每个圆的相关参数
Circle.prototype.generalRandomParam = function(){
    for(var i=0;i<this.ballNumber;i++){
        var ball = {};
        ball.size = 1; // 随机生成圆半径
        // 随机生成圆心 x 坐标
        ball.x = ramdomNumber(0+ball.size, this.iCanvasW-ball.size);
        ball.y = ramdomNumber(0+ball.size, this.iCanvasH-ball.size);
        ball.speedX = ramdomNumber(-1, 1);
        ball.speedY = ramdomNumber(-1, 1);
        this.balls.push(ball);
        ball.status = 0;
        ball.targetX = 0;
        ball.targetY = 0;
        ball.backX = 0;
        ball.backY = 0;
    }
}
// 改变圆的位置
Circle.prototype.changeposition = function(){
    for(var i=0;i<this.ballNumber;i++){
        if( this.balls[i].status == 0){
            this.balls[i].x += this.balls[i].speedX;
            this.balls[i].y += this.balls[i].speedY;
        }
    }
}
// 画圆
Circle.prototype.renderBall = function(ball){
    this.ctx.fillStyle = "#fff";
    this.ctx.beginPath(); // 这个一定要加
    this.ctx.arc(ball.x, ball.y, ball.size, 0, 2 * Math.PI);
    this.ctx.closePath(); // 这个一定要加
    this.ctx.fill();
}
// 小球碰撞判断
Circle.prototype.collision = function(ball){
    for(var i=0;i<this.ballNumber;i++){
       if(ball.x>this.iCanvasW-ball.size || ball.x<ball.size){
            if(ball.x>this.iCanvasW-ball.size){
                ball.x = this.iCanvasW-ball.size;
            }else{
                ball.x = ball.size;
            }
            ball.speedX = - ball.speedX;
       }
       if(ball.y>this.iCanvasH-ball.size || ball.y<ball.size){
            if(ball.y>this.iCanvasH-ball.size){
                ball.y = this.iCanvasH-ball.size;
            }else{
                ball.y = ball.size;
            }
            ball.speedY = - ball.speedY;
       }
    }
}
// 开始动画
Circle.prototype.ballAnimate = function(){
    var This = this;
    var animateFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
    (function move(){
        animte = animateFrame(move);
        This.ctx.clearRect(0, 0, This.iCanvasW, This.iCanvasH);
        This.changeposition();
        for(var i=0;i<This.ballNumber;i++){
           This.collision(This.balls[i]);
           This.renderBall(This.balls[i]);
        }
    })();
}
// 生成一个随机数
function ramdomNumber(min, max) {
    return Math.random() * (max - min) + min;
}

看了代码估计也只是心里炫了一下,也没有让你想把这个东西做出来的欲望,为此我知道必需得让你眼睛心服口服才行。在线 DEMO: 动感的粒子示例。

人无完人,代码也一样。看起来运行顺畅的代码也或多或少有一些瑕疵,日前这个效果还只支持中文。英文的话,我得再努力一把,不管怎么样,英文后面肯定是会加入来的,只是时间问题了。还有代码中用于标记是否可再次执行生成文字的 属性:this.isPlay ,还是一点瑕疵,this.isPlay 的状态更改没有准确的在粒子归位的那一瞬间更改,而是提前更改了状态。但这个状态不会影响本例子效果的完整实现。

这个例子中用到了 dynamics.js 库,主要是用到它里面的一些运动函数,让粒子动起来更感人一些,仅此而已。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

HTML / CSS 相关文章推荐
css3 按钮样式简单可扩展创建
Mar 18 HTML / CSS
CSS3——齿轮转动关键代码
May 02 HTML / CSS
基础的CSS3弹性盒Flexbox布局使用实例
Apr 08 HTML / CSS
详解如何在css中引入自定义字体(font-face)
May 17 HTML / CSS
利用HTML5画出一个坦克的形状具体实现代码
Jun 20 HTML / CSS
html特殊符号示例 html特殊字符编码对照表
Jan 14 HTML / CSS
谈一谈HTML5本地存储技术
Mar 02 HTML / CSS
HTML5响应式(自适应)网页设计的实现
Nov 17 HTML / CSS
video.js支持m3u8格式直播的实现示例
May 20 HTML / CSS
HTML5 Blob对象的具体使用
May 22 HTML / CSS
html5小程序飞入购物车(抛物线绘制运动轨迹点)
Oct 19 HTML / CSS
Html5 new XMLHttpRequest()监听附件上传进度
Jan 14 HTML / CSS
HTML5 Canvas图像模糊完美解决办法
Feb 06 #HTML / CSS
详解移动端HTML5音频与视频问题及解决方案
Aug 22 #HTML / CSS
详解Html5 Canvas画线有毛边解决方法
Mar 01 #HTML / CSS
基于 HTML5 Canvas实现 的交互式地铁线路图
Mar 05 #HTML / CSS
利用html5 file api读取本地文件示例(如图片、PDF等)
Mar 07 #HTML / CSS
html5 分层屏幕适配的方法
Mar 16 #HTML / CSS
canvas实现图片马赛克的示例代码
Mar 26 #HTML / CSS
You might like
咖啡店都有些什么常规豆子呢?有什么风味在里面
2021/03/04 咖啡文化
PHP4中实现动态代理
2006/10/09 PHP
15个小时----从修改程序到自己些程序
2006/10/09 PHP
php 验证码(倾斜,正弦干扰线,黏贴,旋转)
2013/06/29 PHP
php函数指定默认值方法的小例子
2013/12/04 PHP
laravel 解决后端无法获取到前端Post过来的值问题
2019/10/22 PHP
JS实现往下不断流动网页背景的方法
2015/02/27 Javascript
JavaScript通过prototype给对象定义属性用法实例
2015/03/23 Javascript
Javascript中setTimeOut和setInterval的定时器用法
2015/06/12 Javascript
js自定义回调函数
2015/12/13 Javascript
理解javascript闭包
2015/12/15 Javascript
将json转换成struts参数的方法
2016/11/08 Javascript
微信小程序的动画效果详解
2017/01/18 Javascript
nodejs+express实现文件上传下载管理网站
2017/03/15 NodeJs
jQuery实现简单的手风琴效果
2020/04/17 jQuery
jquery获取链接地址和跳转详解(推荐)
2017/08/15 jQuery
jQuery中将json数据显示到页面表格的方法
2018/05/27 jQuery
nodejs 如何手动实现服务器
2018/08/20 NodeJs
Vue.js轮播图走马灯代码实例(全)
2019/05/08 Javascript
通过GASP让vue实现动态效果实例代码详解
2019/11/24 Javascript
使用vue-cli4.0快速搭建一个项目的方法步骤
2019/12/04 Javascript
html中创建并调用vue组件的几种方法汇总
2020/11/17 Javascript
[57:09]DOTA2-DPC中国联赛 正赛 Phoenix vs Dynasty BO3 第一场 1月26日
2021/03/11 DOTA
用实例说明python的*args和**kwargs用法
2013/11/01 Python
python里大整数相乘相关技巧指南
2014/09/12 Python
Python中用于计算对数的log()方法
2015/05/15 Python
python3中布局背景颜色代码分析
2020/12/01 Python
摩顿布朗英国官方网上商店:奢华沐浴、身体和头发护理
2016/10/29 全球购物
新闻编辑自荐信
2013/11/03 职场文书
财务会计大学生自我评价
2014/04/09 职场文书
高中教师个人总结
2015/02/10 职场文书
孙振耀退休感言
2015/08/01 职场文书
Python快速实现一键抠图功能的全过程
2021/06/29 Python
Python OpenCV实现图像模板匹配详解
2022/04/07 Python
Golang jwt身份认证
2022/04/20 Golang
Windows Server 2022 超融合部署(图文教程)
2022/06/25 Servers