详解Canvas 实现炫丽的粒子运动效果(粒子生成文字)
没有最好,只有更好,如题所示,这篇文章只要是分享一个用 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 代码,不过,在看代码前,我们不妨先听听实现这个效果的思路:
- 首先,我们得先生成一堆群众演员(粒子);
- 把每个粒子的相关参数挂到自身的一些属性上,因为第个粒子都会有自己的运动轨迹;
- 接着得让它们各自运动起来。运动有两种(*运动和生成文字的运动);
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 库,主要是用到它里面的一些运动函数,让粒子动起来更感人一些,仅此而已。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。