沒有最好,只有更好,如題所示,這篇文章只要是分享一個用 Canvas 來實現(xiàn)的粒子運動效果。感覺有點標(biāo)題黨了,但換個角度,勉勉強強算是炫麗吧,雖然色彩上與炫麗無關(guān),但運動效果上還是算得上有點點炫的。不管怎么樣,我們還是開始這個所謂的炫麗效果吧!
直接上代碼 ,不懂可以看代碼注釋。估計就會看明白大概的思路了。
html 代碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Canvas 實現(xiàn)炫麗的粒子運動效果-云庫前端</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 代碼,不過,在看代碼前,我們不妨先聽聽實現(xiàn)這個效果的思路:
-
首先,我們得先生成一堆群眾演員(粒子);
-
把每個粒子的相關(guān)參數(shù)掛到自身的一些屬性上,因為第個粒子都會有自己的運動軌跡;
-
接著得讓它們各自運動起來。運動有兩種(自由運動和生成文字的運動);
JavaScript 代碼中使用了三個 Canvas 畫布,this.iCanvas(主場)、this.iCanvasCalculate(用來計算文字寬度)、this.iCanvasPixel(用于畫出文字,并從中得到文字對應(yīng)的像素點的位置坐標(biāo))。
this.iCanvasCalculate 和 this.iCanvasPixel 這兩個無需在頁面中顯示出來,只是輔助作用。
下面就獻上棒棒的 JS 實現(xiàn)代碼
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");
// 設(shè)置Canvas 與父元素同寬高
this.iCanvasW = this.iCanvas.width = this.stateW;
this.iCanvasH = this.iCanvas.height = this.stateH;
// 獲取 2d 繪畫環(huán)境
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; // 用于繪畫文字的畫布
// 隨機生成圓的數(shù)量
this.ballNumber = ramdomNumber(1000, 2000);
// 保存所有小球的數(shù)組
this.balls = [];
// 保存動畫中最后一個停止運動的小球
this.animte = null;
this.imageData = null;
this.textWidth = 0; // 保存生成文字的寬度
this.textHeight = 150; // 保存生成文字的高度
this.inputText = ""; // 保存用戶輸入的內(nèi)容
this.actionCount = 0;
this.ballActor = []; // 保存生成文字的粒子
this.actorNumber = 0; // 保存生成文字的粒子數(shù)量
this.backType = "back"; // 歸位
this.backDynamics = ""; // 動畫效果
this.isPlay = false; // 標(biāo)識(在生成文字過程中,不能再生成)
}
// 渲染出所有圓
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;
}
// 復(fù)位散開
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])
}
}
// 更改小球狀態(tài)
Circle.prototype.changeStatus = function(ball){
ball.status = 0;
if(this.isPlay == true){
this.isPlay = false;
}
}
// 隨機生成每個圓的相關(guān)參數(shù)
Circle.prototype.generalRandomParam = function(){
for(var i=0;i<this.ballNumber;i++){
var ball = {};
ball.size = 1; // 隨機生成圓半徑
// 隨機生成圓心 x 坐標(biāo)
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]);
}
})();
}
// 生成一個隨機數(shù)
function ramdomNumber(min, max) {
return Math.random() * (max - min) + min;
}
看了代碼估計也只是心里炫了一下,也沒有讓你想把這個東西做出來的欲望,為此我知道必需得讓你眼睛心服口服才行。在線 DEMO: 動感的粒子示例。
人無完人,代碼也一樣??雌饋磉\行順暢的代碼也或多或少有一些瑕疵,日前這個效果還只支持中文。英文的話,我得再努力一把,不管怎么樣,英文后面肯定是會加入來的,只是時間問題了。還有代碼中用于標(biāo)記是否可再次執(zhí)行生成文字的 屬性:this.isPlay ,還是一點瑕疵,this.isPlay 的狀態(tài)更改沒有準(zhǔn)確的在粒子歸位的那一瞬間更改,而是提前更改了狀態(tài)。但這個狀態(tài)不會影響本例子效果的完整實現(xiàn)。
這個例子中用到了 dynamics.js 庫,主要是用到它里面的一些運動函數(shù),讓粒子動起來更感人一些,僅此而已。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。