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

时间:2021-05-08

没有最好,只有更好,如题所示,这篇文章只要是分享一个用 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 库,主要是用到它里面的一些运动函数,让粒子动起来更感人一些,仅此而已。

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

    声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。

    相关文章