There is no best, only better. As the title indicates, this article only wants to share a particle movement effect achieved using Canvas. It feels like a bit of a headline, but from another perspective, it can barely be considered dazzling. Although the color has nothing to do with dazzling, the movement effect is still a bit dazzling. Anyway, let's start this so-called dazzling effect!
Go directly to the code. If you don’t understand, you can read the code comments. You will probably understand the general idea.
html code
<!DOCTYPE html><html lang=en><head><meta charset=UTF-8><title>Canvas achieves dazzling particle motion effects - Cloud library front end</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>Spread type: <select name= id=selectType> <option value=back>Return</option> <option value=auto>Random</option> </select> </div> <div class=back-animate>Dispersion effect (effective for homing): <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=Enter Chinese characters and press Enter 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>
There is not much HTML code, just a few operating elements. It’s easy to understand at a glance here. No need to waste too much words. Let’s take a look at the protagonist JavaScript code of this article. However, before looking at the code, we might as well listen to the idea of achieving this effect:
Three Canvases are used in the JavaScript code, this.iCanvas (home page), this.iCanvasCalculate (used to calculate the text width), and this.iCanvasPixel (used to draw text and obtain the position coordinates of the pixels corresponding to the text) .
this.iCanvasCalculate and this.iCanvasPixel do not need to be displayed on the page, they are just auxiliary functions.
Here is the awesome JS implementation code
function Circle() { var This = this; this.init(); this.generalRandomParam(); this.drawCircles(); this.ballAnimate(); this.getUserText(); // After the window changes size, calculations and Get the screen 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); }}//Initialize Circle.prototype.init = function( ){ //Parent element width and height this.stateW = document.body.offsetWidth; this.stateH = document.body.offsetHeight; this.iCanvas = document.createElement(canvas); // Set Canvas to have the same width and height as the parent element this.iCanvasW = this.iCanvas.width = this.stateW; this.iCanvasH = this.iCanvas.height = this.stateH; // Get 2d painting Environment this.ctx = this.iCanvas.getContext(2d); // Insert into the body element document.body.appendChild(this.iCanvas); this.iCanvasCalculate = document.createElement(canvas); // Canvas used to save calculated text width this.mCtx = this.iCanvasCalculate.getContext(2d); this.mCtx.font = 128px Microsoft Yahei; this.iCanvasPixel = document .createElement(canvas); this.iCanvasPixel.setAttribute(style,position:absolute;top:0;left:0;); this.pCtx = null; // Canvas used to draw text // The number of randomly generated circles this.ballNumber = ramdomNumber(1000 , 2000); // Save the array of all balls this.balls = []; // Save the last ball that stopped moving in the animation this.animte = null; this.imageData = null; this.textWidth = 0; // Save the width of the generated text this.textHeight = 150; // Save the height of the generated text this.inputText = ; // Save the content entered by the user this.actionCount = 0; this.ballActor = []; // Save the particles that generate text this.actorNumber = 0; // Save the number of particles that generate text this.backType = back; // Return this.backDynamics = ; // Animation effect this.isPlay = false; // Logo (cannot be generated during text generation)}// Render all circles Circle.prototype.drawCircles = function () { for(var i=0;i <this.ballNumber;i++){ this.renderBall(this.balls[0]); }}// Get user input text Circle.prototype.getUserText = function(){ This = this; // Save this to point to ipu = document.getElementById(input-text); ipu.addEventListener(keydown,function(event){ if(event.which === 13){ // If it is the enter key ipu.value = ipu.value.trim(); // Remove leading and trailing spaces var pat = /[/u4e00-/u9fa5]/; // Chinese judgment var isChinese = pat.test(ipu.value); if(ipu.value.length !=0 && isChinese){ This.inputText = ipu.value; }else{ alert(Please enter Chinese characters); return; } if(This.isPlay) { return } This.getAnimateType(); This.getTextPixel(); This.isPlay = true; } });}// Calculate the width of the text Circle.prototype.calculateTextWidth = function () { this.textWidth = this.mCtx.measureText(this.inputText).width;}// Get the text pixels 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 Microsoft Yahei; 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);}//Get the text particle pixel position 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();}//The particle moves to the specified position 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);}//The particles return to the Circle along the original path .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) } ); }}// Get type|animation effect 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;}//Reset spread Circle.prototype.ballbackType = function(){ if(this.backType == back){ this.ballBackPosition(); }else{ this.ballAutoPosition(); } this.ballActor = [];}// Randomly scatter Circle.prototype.ballAutoPosition = function(ball){ for(var i= 0;i<this.actorNumber;i++){ this.changeStatus(this.ballActor[i]) }}// Change the ball status Circle.prototype.changeStatus = function(ball){ ball.status = 0; if(this.isPlay == true){ this.isPlay = false; }}// Randomly generate the relevant parameters of each circle Circle .prototype.generalRandomParam = function(){ for(var i=0;i<this.ballNumber;i++){ var ball = {}; ball.size = 1; // Randomly generate circle radius // Randomly generate circle center x coordinate 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; }}// Change the position of the circle 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; } }}// Draw a circle Circle.prototype.renderBall = function(ball){ this.ctx. fillStyle = #fff; this.ctx.beginPath(); // This must be added with this.ctx.arc(ball.x, ball.y, ball.size, 0, 2 * Math.PI); this.ctx.closePath(); // This must be added with this.ctx.fill();}// Ball collision judgment 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; } }}// Start animation 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]); } })();}//Generate a random number function ramdomNumber(min, max) { return Math.random() * (max - min) + min;}
After reading the code, I guess it was just a bit of a show-off, and it didn’t give you the desire to make this thing. For this reason, I know you have to be convinced by your eyes. Online DEMO: Dynamic particle example.
No one is perfect, and neither is code. It seems that the code that runs smoothly also has some flaws. Currently, this effect only supports Chinese. As for English, I have to work harder. No matter what, English will definitely join later, it’s just a matter of time. There is also an attribute in the code used to mark whether the generated text can be executed again: this.isPlay, which is still a little flaw. The state change of this.isPlay does not change exactly at the moment when the particles return, but changes the state in advance. But this state will not affect the complete implementation of the effect of this example.
In this example, the dynamics.js library is used, mainly to use some motion functions in it to make the particles move more impressively, that's all.
The above is the entire content of this article. I hope it will be helpful to everyone’s study. I also hope everyone will support VeVb Wulin Network.