本文首發於我的博客,這是我的github,歡迎star。
這篇博客是模仿nest.js實現一個demo
,由簡單到複雜,來一步步的實現它。這裏是效果預覽。我的github裏邊還有很多別的前端的demo
,喜歡的話可以點個star
,你的支持就是我的動力。
從一道面試題開始
實現一個半徑
10px
的小球在500px*500px
的方塊中做直線運動,初始方向隨機,速度保持不變,碰撞到牆壁後反彈。
看下效果,思路很簡單,將小球定位在方塊中,設置xy
方向上的速度,每幀動畫給小球定位的值加上對應方向的速度值,在檢測到小球碰撞牆壁的時候,將對應方向的速度置爲反方向就可以了。這裏是實現的代碼,沒有用到canvas
,但是思路一致。
嘗試實現
畫出一個彈射的小球很簡單,那怎麼用多個小球實現nest.js這樣的效果呢。這樣的特效肯定不能用Dom
直接做,太耗費性能,也做不出來,這時就顯露出canvas
的強大之處了。
同樣的,用canvas
生成多個彈來彈去的小球。首先不要管鼠標如何吸附這些小圓點,只做小球之間的連線。在每次繪製小球之前,判斷一下它和之前的小球的距離是不是小於極限距離,小於就以它倆爲端點繪製一條線。代碼如下,思路都寫在註釋裏:
const theCanvas = document.getElementById('theCanvas'),
ctx = theCanvas.getContext('2d'),
mix = 6000; //會產生連線的極限距離的平方
//將canvas鋪滿瀏覽器
let canvas_width = theCanvas.width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth,
canvas_height = theCanvas.height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight,
points = [];
theCanvas.style = "position: fixed; top: 0px; left: 0px;";
//繪製函數,用requestAnimationFrame調用實現動畫
function draw() {
//清屏
ctx.clearRect(0, 0, canvas_width, canvas_height);
let i,pi,x_dist,y_dist;
//遍歷點集合繪製線條
points.forEach((p, index) => {
p.x += p.xa, //按指定速度移動
p.y += p.ya,
p.xa *= p.x > canvas_width || p.x < 0 ? -1 : 1,
p.ya *= p.y > canvas_height || p.y < 0 ? -1 : 1,
ctx.fillRect(p.x - 0.5, p.y - 0.5, 1, 1); //繪製點,其實是小方塊
//類似於握手問題,兩個點之間只繪製一次線
for(i = index + 1; i < points.length; i++) {
pi = points[i];
x_dist = p.x - pi.x;
y_dist = p.y - pi.y;
dist = x_dist * x_dist + y_dist * y_dist;
//判斷點之間的距離是否小於極限距離
if(dist < mix) {
ctx.beginPath();
ctx.moveTo(p.x, p.y);
ctx.lineTo(pi.x, pi.y);
ctx.stroke();
}
}
}),requestAnimationFrame(draw);
}
//隨機生成100個點
for(let i = 0; i < 100; i++ ) {
let x = Math.random() * canvas_width, //初始座標
y = Math.random() * canvas_height,
xa = 2 * Math.random() - 1, //x速度
ya = 2 * Math.random() - 1; //y速度
points[i] = {x, y, xa, ya};
}
draw();
看下效果,醜陋,和人家的不一樣,很生硬。因爲連線不是突然出現突然消失的,點和點之間的連線是漸漸的出現,然後漸漸消失的。給連線添加動態的屬性,用點和點的之間的距離來計算連線的粗細、透明度,在兩點距離比較遠的時候線會變淡,這樣看起來就舒服多了。
for(i = index + 1; i < points.length; i++) {
pi = points[i];
x_dist = p.x - pi.x;
y_dist = p.y - pi.y;
dist = x_dist * x_dist + y_dist * y_dist;
//根據兩點距離得到一個參數w
w = (mix - dist) / mix;
//判斷點之間的距離是否小於極限距離
if(dist < mix) {
ctx.beginPath();
//根據參數w設置連線寬度和透明度
ctx.lineWidth = w / 2;
ctx.strokeStyle = `rgba(110,110,110,${w + 0.2})`;
ctx.moveTo(p.x, p.y);
ctx.lineTo(pi.x, pi.y);
ctx.stroke();
}
}
添加鼠標事件
先是加入對鼠標的響應。在鼠標進入瀏覽器時添加鼠標這個點,否則移除。
window.onmousemove = e => {
e = e || window.event;
current_point.x = e.clientX;
current_point.y = e.clientY;
};
window.onmouseout = () => {
current_point.x = null;
current_point.y = null;
};
//將鼠標的點添加至點集合中
all_points = [...random_points,current_point];
要實現一個鼠標吸附粒子的效果,思路就是粒子和鼠標的距離在一定範圍內時,給粒子添加一個向着鼠標的速度,結果就好像是粒子受到鼠標的吸附一樣。這是一段鼠標吸附效果的核心代碼:
//當兩點距離小於極限距離時產生連線,當第二個點是鼠標所產生點時,粒子如果在範圍內就會產生向鼠標點的速度,實現吸附效果
dist < pi.max && (pi === current_point && dist >= pi.max / 2 && (p.x -= 0.03 * x_dist, p.y -= 0.03 * y_dist));
加入鼠標的點之後再做一些調整,得到最終的代碼。
其他的粒子特效
還可以利用canvas
的getImageData
屬性,將圖片粒子化,做成輪播圖,點擊這裏預覽,主要思路是用getImageData
取到圖片像素點的信息,每隔一段取一個樣本,以這個樣本繪製粒子,繪製出類似於馬賽克一樣的圖片,然後給粒子加上運動的效果就可以了,這裏是具體的代碼實現。
這篇博客到這就結束了,這是我的github,歡迎來訪,歡迎star。