摘要:點(diǎn)都構(gòu)建完畢了,就要構(gòu)建點(diǎn)與點(diǎn)之間的連線了,我們用到雙重遍歷,把兩個(gè)點(diǎn)捆綁成一組,放到數(shù)組中。最后加入鼠標(biāo)移動(dòng)事件,啟動(dòng)定時(shí)器大功告成
廢話不說先上圖:
關(guān)于這個(gè)效果我第一次見是在
https://www.mengxiaozhu.cn/
后來知乎登錄頁(yè)也開始用了
https://www.zhihu.com/
網(wǎng)絡(luò)上還有很多地方都在用,效果還是不錯(cuò)的。
我見了之后覺得挺有意思的就研究了一下原理
下面開始coding:
先寫個(gè)canvas標(biāo)簽
加上一些默認(rèn)的樣式:
*{ margin:0; padding:0; } body{ overflow: hidden; }
這里的overflow:hidden是為了防止出現(xiàn)滾動(dòng)條
下面開始寫JS:
首先我們要得到那個(gè) canvas 并得到繪制上下文:
var canvasEl = document.getElementById("canvas"); var ctx = canvasEl.getContext("2d"); var mousePos = [0, 0];
緊接著我們聲明兩個(gè)變量,分別用于存儲(chǔ)“星星”和邊:
var nodes = []; var edges = [];
然后我們定義一些其他的變量:
var easingFactor = 5.0; //緩動(dòng)因子 var backgroundColor = "#000"; //背景顏色 var nodeColor = "#fff"; //點(diǎn)顏色 var edgeColor = "#fff"; //邊顏色 var pageWidth = window.innerWidth, //窗口寬度 pageHeight = window.innerHeight; //窗口高度
設(shè)置畫布的大小鋪滿整個(gè)屏幕:
window.onresize = function () { canvasEl.width = pageWidth; canvasEl.height = pageHeight; if (nodes.length == 0) { constructNodes(); } render(); }; window.onresize();
準(zhǔn)備工作完成,我們要開始構(gòu)建點(diǎn)了:
function constructNodes() { for (var i = 0; i < 100; i++) { var node = { drivenByMouse: i == 0, x: Math.random() * canvasEl.width, y: Math.random() * canvasEl.height, vx: Math.random() * 1 - 0.5, vy: Math.random() * 1 - 0.5, radius: Math.random() > 0.9 ? 3 + Math.random() * 3 : 1 + Math.random() * 3 }; nodes.push(node); } nodes.forEach(function (e) { nodes.forEach(function (e2) { if (e == e2) { return; } var edge = { from: e, to: e2 } addEdge(edge); }); }); }
先創(chuàng)建100個(gè)點(diǎn),每個(gè)點(diǎn)設(shè)置6個(gè)屬性,drivenByMouse屬性只有第一個(gè)點(diǎn)為true,其他的點(diǎn)為false,第一個(gè)點(diǎn)作為鼠標(biāo)跟隨點(diǎn),不顯示出來,可以與其他點(diǎn)連線。x,y作為點(diǎn)的初始位置,取得是畫布內(nèi)的隨機(jī)點(diǎn),vx,vy表示點(diǎn)的初始速度,范圍為-0.5到0.5之間的隨機(jī)數(shù),radius表示點(diǎn)的半徑,大部分的點(diǎn)為小的,少數(shù)的點(diǎn)為大的。
點(diǎn)都構(gòu)建完畢了,就要構(gòu)建點(diǎn)與點(diǎn)之間的連線了,我們用到雙重遍歷,把兩個(gè)點(diǎn)捆綁成一組,放到 edges 數(shù)組中。注意這里我用了另外一個(gè)函數(shù)來完成這件事,而沒有直接用 edges.push() ,為什么?
假設(shè)我們之前連接了 A、B兩點(diǎn),也就是外側(cè)循環(huán)是A,內(nèi)側(cè)循環(huán)是B,那么在下一次循環(huán)中,外側(cè)為B,內(nèi)側(cè)為A,是不是也會(huì)創(chuàng)建一條邊呢?而實(shí)際上,這兩個(gè)邊除了方向不一樣以外是完全一樣的,這完全沒有必要而且占用資源。因此我們?cè)?addEdge 函數(shù)中進(jìn)行一個(gè)判斷:
function addEdge(edge) { var ignore = false; edges.forEach(function (e) { if (e.from == edge.from & e.to == edge.to) { ignore = true; } if (e.to == edge.from & e.from == edge.to) { ignore = true; } }); if (!ignore) { edges.push(edge); } }
至此,我們的準(zhǔn)備工作就完畢了,下面我們要讓點(diǎn)動(dòng)起來:
function step() { nodes.forEach(function (e) { if (e.drivenByMouse) { return; } e.x += e.vx; e.y += e.vy; function clamp(min, max, value) { if (value > max) { return max; } else if (value < min) { return min; } else { return value; } } if (e.x <= 0 || e.x >= canvasEl.width) { e.vx *= -1; e.x = clamp(0, canvasEl.width, e.x) } if (e.y <= 0 || e.y >= canvasEl.height) { e.vy *= -1; e.y = clamp(0, canvasEl.height, e.y) } }); adjustNodeDrivenByMouse(); render(); window.requestAnimationFrame(step); } function adjustNodeDrivenByMouse() { nodes[0].x += (mousePos[0] - nodes[0].x) / easingFactor; nodes[0].y += (mousePos[1] - nodes[0].y) / easingFactor; }
這段代碼就是遍歷粒子,并且更新其狀態(tài)。根據(jù)一個(gè)簡(jiǎn)單的物理公式 s = s + v,每次執(zhí)行都會(huì) 更新到點(diǎn)的下一步的狀態(tài)。
adjustNodeDrivenByMouse函將第一個(gè)點(diǎn)作為鼠標(biāo)的跟隨點(diǎn),easingFactor為緩動(dòng)因子可以讓點(diǎn)的運(yùn)動(dòng)比鼠標(biāo)運(yùn)動(dòng)的稍慢一點(diǎn)。
然后我們要讓整個(gè)粒子系統(tǒng)連續(xù)地運(yùn)轉(zhuǎn)起來就需要一個(gè)timer了,但是十分不提倡大家使用 setInterval,而是盡可能使用 requestAnimationFrame,它能保證你的幀率鎖定在當(dāng)前瀏覽器的頻率下,一般為60HZ。
剩下的就是繪制了
function render() { ctx.fillStyle = backgroundColor; ctx.fillRect(0, 0, canvasEl.width, canvasEl.height); edges.forEach(function (e) { var l = lengthOfEdge(e); var threshold = canvasEl.width / 8; if (l > threshold) { return; } ctx.strokeStyle = edgeColor; ctx.lineWidth = (1.0 - l / threshold) * 2.5; ctx.globalAlpha = 1.0 - l / threshold; ctx.beginPath(); ctx.moveTo(e.from.x, e.from.y); ctx.lineTo(e.to.x, e.to.y); ctx.stroke(); }); ctx.globalAlpha = 1.0; nodes.forEach(function (e) { if (e.drivenByMouse) { return; } ctx.fillStyle = nodeColor; ctx.beginPath(); ctx.arc(e.x, e.y, e.radius, 0, 2 * Math.PI); ctx.fill(); }); } function lengthOfEdge(edge) { return Math.sqrt(Math.pow((edge.from.x - edge.to.x), 2) + Math.pow((edge.from.y - edge.to.y), 2)); }
繪制的時(shí)候我們要判斷線的長(zhǎng)度如果大于某一個(gè)值,則不繪制該線了,如果在范圍之內(nèi)粗細(xì),與顏色的透明度都與線的長(zhǎng)度相關(guān),點(diǎn)除了第一個(gè)鼠標(biāo)跟隨點(diǎn),其他的畫入即可。
最后加入鼠標(biāo)移動(dòng)事件,啟動(dòng)定時(shí)器:
window.onmousemove = function (e) { mousePos[0] = e.clientX; mousePos[1] = e.clientY; } window.requestAnimationFrame(step);
大功告成??!
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/112079.html
摘要:點(diǎn)都構(gòu)建完畢了,就要構(gòu)建點(diǎn)與點(diǎn)之間的連線了,我們用到雙重遍歷,把兩個(gè)點(diǎn)捆綁成一組,放到數(shù)組中。最后加入鼠標(biāo)移動(dòng)事件,啟動(dòng)定時(shí)器大功告成 廢話不說先上圖:showImg(https://segmentfault.com/img/bVOH83?w=1312&h=586); 關(guān)于這個(gè)效果我第一次見是在https://www.mengxiaozhu.cn/后來知乎登錄頁(yè)也開始用了https:/...
摘要:運(yùn)動(dòng)坐標(biāo)變量坐標(biāo)變量繪制方法畫布渲染清除畫布位置變化繪制繼續(xù)渲染動(dòng)起來的多點(diǎn)多線動(dòng)的是點(diǎn),畫的是線給對(duì)象添加運(yùn)動(dòng)變量和兩個(gè)值表示點(diǎn)在軸和軸的運(yùn)動(dòng)量此處為在之間運(yùn)動(dòng)。 Canvas 點(diǎn)線動(dòng)畫案例 畫圓: arc(x, y, r, start, stop) 畫線: moveTo(x, y) 定義線條開始坐標(biāo)lineTo(x, y) 定義線條結(jié)束坐標(biāo) 填充: fill() 繪制: stro...
摘要:嘗試實(shí)現(xiàn)畫出一個(gè)彈射的小球很簡(jiǎn)單,那怎么用多個(gè)小球?qū)崿F(xiàn)這樣的效果呢。 本文首發(fā)于我的博客,這是我的github,歡迎star。 ??這篇博客是模仿nest.js實(shí)現(xiàn)一個(gè)demo,由簡(jiǎn)單到復(fù)雜,來一步步的實(shí)現(xiàn)它。這里是效果預(yù)覽。我的github里邊還有很多別的前端的demo,喜歡的話可以點(diǎn)個(gè)star,你的支持就是我的動(dòng)力。 從一道面試題開始 實(shí)現(xiàn)一個(gè)半徑10px的小球在500px*5...
閱讀 2012·2021-11-23 10:08
閱讀 2348·2021-11-22 15:25
閱讀 3281·2021-11-11 16:55
閱讀 781·2021-11-04 16:05
閱讀 2618·2021-09-10 10:51
閱讀 718·2019-08-29 15:38
閱讀 1593·2019-08-29 14:11
閱讀 3492·2019-08-29 12:42