JavaScript實(shí)現(xiàn)一個(gè)電子小蜘蛛
前言
在學(xué)習(xí)完JavaScript之后,我們就可以使用JavaScript來實(shí)現(xiàn)一下好玩的效果了,本篇文章講解的是如何純使用JavaScript來實(shí)現(xiàn)一個(gè)網(wǎng)頁中的電子蜘蛛。
在開始學(xué)習(xí)如何編寫一個(gè)網(wǎng)頁蜘蛛之前,先讓我們看一下這個(gè)電子蜘蛛長(zhǎng)什么樣:
——我們可以看到,其會(huì)跟隨著我們的鼠標(biāo)進(jìn)行移動(dòng),那么我們?nèi)绾螌?shí)現(xiàn)這樣的效果呢?接下來讓我們開始講解。
HTML代碼
我們的html代碼十分的簡(jiǎn)單,就是創(chuàng)建一個(gè)畫布,而我們接下來的操作,都是在此上邊進(jìn)行操作的:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>秋刀魚不做夢(mèng)</title> <!-- 引入外部的JavaScript文件 --> <script src="./test.js"></script> <style> /* 移除body的默認(rèn)外邊距和內(nèi)邊距 */ body { margin: 0px; padding: 0px; position: fixed; /* 設(shè)置網(wǎng)頁背景顏色為黑色 */ background: rgb(0, 0, 0); } </style> </head> <body> <!-- 創(chuàng)建一個(gè)畫布用于圖形繪制 --> <canvas id="canvas"></canvas> </body> </html>
可以看到我們的HTML代碼非常的簡(jiǎn)單,接下來讓我們開始在其上邊進(jìn)行操作!
JavaScript代碼
在開始編寫JavaScript代碼之前,先讓我們理清一下思路:
總體流程
- 頁面加載時(shí),
canvas
元素和繪圖上下文初始化。 - 定義觸手對(duì)象,每條觸手由多個(gè)段組成。
- 監(jiān)聽鼠標(biāo)移動(dòng)事件,實(shí)時(shí)更新鼠標(biāo)的位置。
- 通過動(dòng)畫循環(huán)繪制觸手,觸手根據(jù)鼠標(biāo)的位置動(dòng)態(tài)變化,形成流暢的動(dòng)畫效果。
大致的流程就是上邊的步驟,但是我相信讀者在沒用自己完成此代碼的編寫之前,可能不能理解上邊的流程,不過沒關(guān)系,現(xiàn)在讓我們開始我們的網(wǎng)頁小蜘蛛的編寫:
寫在前面:為了讓讀者可以更好的理解代碼的邏輯,我們給沒一句代碼都加上了注釋,希望讀者可以根據(jù)注釋的幫助一點(diǎn)一點(diǎn)的理解代碼:
JavaScript代碼:
// 定義requestAnimFrame函數(shù) window.requestAnimFrame = function () { // 檢查瀏覽器是否支持requestAnimFrame函數(shù) return ( window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || // 如果所有這些選項(xiàng)都不可用,使用設(shè)置超時(shí)來調(diào)用回調(diào)函數(shù) function (callback) { window.setTimeout(callback) } ) } // 初始化函數(shù),用于獲取canvas元素并返回相關(guān)信息 function init(elemid) { // 獲取canvas元素 let canvas = document.getElementById(elemid) // 獲取2d繪圖上下文,這里d是小寫的 c = canvas.getContext('2d') // 設(shè)置canvas的寬度為窗口內(nèi)寬度,高度為窗口內(nèi)高度 w = (canvas.width = window.innerWidth) h = (canvas.height = window.innerHeight) // 設(shè)置填充樣式為半透明黑 c.fillStyle = "rgba(30,30,30,1)" // 使用填充樣式填充整個(gè)canvas c.fillRect(0, 0, w, h) // 返回繪圖上下文和canvas元素 return { c: c, canvas: canvas } } // 等待頁面加載完成后執(zhí)行函數(shù) window.onload = function () { // 獲取繪圖上下文和canvas元素 let c = init("canvas").c, canvas = init("canvas").canvas, // 設(shè)置canvas的寬度為窗口內(nèi)寬度,高度為窗口內(nèi)高度 w = (canvas.width = window.innerWidth), h = (canvas.height = window.innerHeight), // 初始化鼠標(biāo)對(duì)象 mouse = { x: false, y: false }, last_mouse = {} // 定義計(jì)算兩點(diǎn)距離的函數(shù) function dist(p1x, p1y, p2x, p2y) { return Math.sqrt(Math.pow(p2x - p1x, 2) + Math.pow(p2y - p1y, 2)) } // 定義 segment 類 class segment { // 構(gòu)造函數(shù),用于初始化 segment 對(duì)象 constructor(parent, l, a, first) { // 如果是第一條觸手段,則位置坐標(biāo)為觸手頂部位置 // 否則位置坐標(biāo)為上一個(gè)segment對(duì)象的nextPos坐標(biāo) this.first = first if (first) { this.pos = { x: parent.x, y: parent.y, } } else { this.pos = { x: parent.nextPos.x, y: parent.nextPos.y, } } // 設(shè)置segment的長(zhǎng)度和角度 this.l = l this.ang = a // 計(jì)算下一個(gè)segment的坐標(biāo)位置 this.nextPos = { x: this.pos.x + this.l * Math.cos(this.ang), y: this.pos.y + this.l * Math.sin(this.ang), } } // 更新segment位置的方法 update(t) { // 計(jì)算segment與目標(biāo)點(diǎn)的角度 this.ang = Math.atan2(t.y - this.pos.y, t.x - this.pos.x) // 根據(jù)目標(biāo)點(diǎn)和角度更新位置坐標(biāo) this.pos.x = t.x + this.l * Math.cos(this.ang - Math.PI) this.pos.y = t.y + this.l * Math.sin(this.ang - Math.PI) // 根據(jù)新的位置坐標(biāo)更新nextPos坐標(biāo) this.nextPos.x = this.pos.x + this.l * Math.cos(this.ang) this.nextPos.y = this.pos.y + this.l * Math.sin(this.ang) } // 將 segment 回執(zhí)回初始位置的方法 fallback(t) { // 將位置坐標(biāo)設(shè)置為目標(biāo)點(diǎn)坐標(biāo) this.pos.x = t.x this.pos.y = t.y this.nextPos.x = this.pos.x + this.l * Math.cos(this.ang) this.nextPos.y = this.pos.y + this.l * Math.sin(this.ang) } show() { c.lineTo(this.nextPos.x, this.nextPos.y) } } // 定義 tentacle 類 class tentacle { // 構(gòu)造函數(shù),用于初始化 tentacle 對(duì)象 constructor(x, y, l, n, a) { // 設(shè)置觸手的頂部位置坐標(biāo) this.x = x this.y = y // 設(shè)置觸手的長(zhǎng)度 this.l = l // 設(shè)置觸手的段數(shù) this.n = n // 初始化觸手的目標(biāo)點(diǎn)對(duì)象 this.t = {} // 設(shè)置觸手的隨機(jī)移動(dòng)參數(shù) this.rand = Math.random() // 創(chuàng)建觸手的第一條段 this.segments = [new segment(this, this.l / this.n, 0, true)] // 創(chuàng)建其他的段 for (let i = 1; i < this.n; i++) { this.segments.push( new segment(this.segments[i - 1], this.l / this.n, 0, false) ) } } // 移動(dòng)觸手到目標(biāo)點(diǎn)的方法 move(last_target, target) { // 計(jì)算觸手頂部與目標(biāo)點(diǎn)的角度 this.angle = Math.atan2(target.y - this.y, target.x - this.x) // 計(jì)算觸手的距離參數(shù) this.dt = dist(last_target.x, last_target.y, target.x, target.y) // 計(jì)算觸手的目標(biāo)點(diǎn)坐標(biāo) this.t = { x: target.x - 0.8 * this.dt * Math.cos(this.angle), y: target.y - 0.8 * this.dt * Math.sin(this.angle) } // 如果計(jì)算出了目標(biāo)點(diǎn),則更新最后一個(gè)segment對(duì)象的位置坐標(biāo) // 否則,更新最后一個(gè)segment對(duì)象的位置坐標(biāo)為目標(biāo)點(diǎn)坐標(biāo) if (this.t.x) { this.segments[this.n - 1].update(this.t) } else { this.segments[this.n - 1].update(target) } // 遍歷所有segment對(duì)象,更新它們的位置坐標(biāo) for (let i = this.n - 2; i >= 0; i--) { this.segments[i].update(this.segments[i + 1].pos) } if ( dist(this.x, this.y, target.x, target.y) <= this.l + dist(last_target.x, last_target.y, target.x, target.y) ) { this.segments[0].fallback({ x: this.x, y: this.y }) for (let i = 1; i < this.n; i++) { this.segments[i].fallback(this.segments[i - 1].nextPos) } } } show(target) { // 如果觸手與目標(biāo)點(diǎn)的距離小于觸手的長(zhǎng)度,則回執(zhí)觸手 if (dist(this.x, this.y, target.x, target.y) <= this.l) { // 設(shè)置全局合成操作為lighter c.globalCompositeOperation = "lighter" // 開始新路徑 c.beginPath() // 從觸手起始位置開始繪制線條 c.moveTo(this.x, this.y) // 遍歷所有的segment對(duì)象,并使用他們的show方法回執(zhí)線條 for (let i = 0; i < this.n; i++) { this.segments[i].show() } // 設(shè)置線條樣式 c.strokeStyle = "hsl(" + (this.rand * 60 + 180) + ",100%," + (this.rand * 60 + 25) + "%)" // 設(shè)置線條寬度 c.lineWidth = this.rand * 2 // 設(shè)置線條端點(diǎn)樣式 c.lineCap = "round" // 設(shè)置線條連接處樣式 c.lineJoin = "round" // 繪制線條 c.stroke() // 設(shè)置全局合成操作為“source-over” c.globalCompositeOperation = "source-over" } } // 繪制觸手的圓形頭的方法 show2(target) { // 開始新路徑 c.beginPath() // 如果觸手與目標(biāo)點(diǎn)的距離小于觸手的長(zhǎng)度,則回執(zhí)白色的圓形 // 否則繪制青色的圓形 if (dist(this.x, this.y, target.x, target.y) <= this.l) { c.arc(this.x, this.y, 2 * this.rand + 1, 0, 2 * Math.PI) c.fillStyle = "whith" } else { c.arc(this.x, this.y, this.rand * 2, 0, 2 * Math.PI) c.fillStyle = "darkcyan" } // 填充圓形 c.fill() } } // 初始化變量 let maxl = 400,//觸手的最大長(zhǎng)度 minl = 50,//觸手的最小長(zhǎng)度 n = 30,//觸手的段數(shù) numt = 600,//觸手的數(shù)量 tent = [],//觸手的數(shù)組 clicked = false,//鼠標(biāo)是否被按下 target = { x: 0, y: 0 }, //觸手的目標(biāo)點(diǎn) last_target = {},//上一個(gè)觸手的目標(biāo)點(diǎn) t = 0,//當(dāng)前時(shí)間 q = 10;//觸手每次移動(dòng)的步長(zhǎng) // 創(chuàng)建觸手對(duì)象 for (let i = 0; i < numt; i++) { tent.push( new tentacle( Math.random() * w,//觸手的橫坐標(biāo) Math.random() * h,//觸手的縱坐標(biāo) Math.random() * (maxl - minl) + minl,//觸手的長(zhǎng)度 n,//觸手的段數(shù) Math.random() * 2 * Math.PI,//觸手的角度 ) ) } // 繪制圖像的方法 function draw() { // 如果鼠標(biāo)移動(dòng),則計(jì)算觸手的目標(biāo)點(diǎn)與當(dāng)前點(diǎn)的偏差 if (mouse.x) { target.errx = mouse.x - target.x target.erry = mouse.y - target.y } else { // 否則,計(jì)算觸手的目標(biāo)點(diǎn)的橫坐標(biāo) target.errx = w / 2 + ((h / 2 - q) * Math.sqrt(2) * Math.cos(t)) / (Math.pow(Math.sin(t), 2) + 1) - target.x; target.erry = h / 2 + ((h / 2 - q) * Math.sqrt(2) * Math.cos(t) * Math.sin(t)) / (Math.pow(Math.sin(t), 2) + 1) - target.y; } // 更新觸手的目標(biāo)點(diǎn)坐標(biāo) target.x += target.errx / 10 target.y += target.erry / 10 // 更新時(shí)間 t += 0.01; // 繪制觸手的目標(biāo)點(diǎn) c.beginPath(); c.arc( target.x, target.y, dist(last_target.x, last_target.y, target.x, target.y) + 5, 0, 2 * Math.PI ); c.fillStyle = "hsl(210,100%,80%)" c.fill(); // 繪制所有觸手的中心點(diǎn) for (i = 0; i < numt; i++) { tent[i].move(last_target, target) tent[i].show2(target) } // 繪制所有觸手 for (i = 0; i < numt; i++) { tent[i].show(target) } // 更新上一個(gè)觸手的目標(biāo)點(diǎn)坐標(biāo) last_target.x = target.x last_target.y = target.y } // 循環(huán)執(zhí)行繪制動(dòng)畫的函數(shù) function loop() { // 使用requestAnimFrame函數(shù)循環(huán)執(zhí)行 window.requestAnimFrame(loop) // 清空canvas c.clearRect(0, 0, w, h) // 繪制動(dòng)畫 draw() } // 監(jiān)聽窗口大小改變事件 window.addEventListener("resize", function () { // 重置canvas的大小 w = canvas.width = window.innerWidth w = canvas.height = window.innerHeight // 循環(huán)執(zhí)行回執(zhí)動(dòng)畫的函數(shù) loop() }) // 循環(huán)執(zhí)行回執(zhí)動(dòng)畫的函數(shù) loop() // 使用setInterval函數(shù)循環(huán) setInterval(loop, 1000 / 60) // 監(jiān)聽鼠標(biāo)移動(dòng)事件 canvas.addEventListener("mousemove", function (e) { // 記錄上一次的鼠標(biāo)位置 last_mouse.x = mouse.x last_mouse.y = mouse.y // 更新點(diǎn)前的鼠標(biāo)位置 mouse.x = e.pageX - this.offsetLeft mouse.y = e.pageY - this.offsetTop }, false) // 監(jiān)聽鼠標(biāo)離開事件 canvas.addEventListener("mouseleave", function (e) { // 將mouse設(shè)為false mouse.x = false mouse.y = false }) }
這里我們?cè)诖笾碌氖崂硪幌律鲜龃a的流程:
初始化階段
init
函數(shù):當(dāng)頁面加載時(shí),init
函數(shù)被調(diào)用,獲取canvas
元素并設(shè)置其寬高為窗口的大小。獲取到的 2D 繪圖上下文(context
)用于后續(xù)繪制。window.onload
:頁面加載完成后,初始化canvas
和context
,并設(shè)置鼠標(biāo)初始狀態(tài)。
觸手對(duì)象的定義
segment
類:這是觸手的一段,每個(gè)段有起始點(diǎn)(pos
)、長(zhǎng)度(l
)、角度(ang
),并通過角度計(jì)算出下一段的位置(nextPos
)。tentacle
類:代表完整的觸手,由若干個(gè)segment
組成。觸手的起始點(diǎn)在屏幕中心,并且每個(gè)觸手包含多個(gè)段。tentacle
的主要方法有:move
:根據(jù)鼠標(biāo)位置更新每一段的位置。show
:繪制觸手的路徑。
事件監(jiān)聽
canvas.addEventListener("mousemove", ...)
:當(dāng)鼠標(biāo)移動(dòng)時(shí),捕捉鼠標(biāo)的位置并存儲(chǔ)在 mouse
變量中。每次鼠標(biāo)移動(dòng)會(huì)更新 mouse
和 last_mouse
的坐標(biāo),用于后續(xù)的動(dòng)畫。
動(dòng)畫循環(huán)
draw
函數(shù):這是一個(gè)遞歸的函數(shù),用于創(chuàng)建動(dòng)畫效果。 首先,它會(huì)在每一幀中為畫布填充半透明背景,使得之前繪制的內(nèi)容逐漸消失,產(chǎn)生拖影效果。- 然后,遍歷所有觸手(
tentacles
),調(diào)用它們的move
和show
方法,更新位置并繪制每一幀。 - 最后,使用
requestAnimFrame(draw)
不斷遞歸調(diào)用draw
,形成一個(gè)動(dòng)畫循環(huán)。
觸手的行為
- 觸手的運(yùn)動(dòng)是通過
move
函數(shù)實(shí)現(xiàn)的,觸手的最后一個(gè)段首先更新位置,然后其他段依次跟隨。 - 觸手的繪制通過
show
函數(shù),遍歷所有段并繪制線條,最后顯示在屏幕上。
——這樣我們就完成了電子小蜘蛛的制作了?。?!
最后,在讓我們看一下最終效果:
到此這篇關(guān)于JavaScript實(shí)現(xiàn)一個(gè)電子小蜘蛛的文章就介紹到這了,更多相關(guān)JavaScript電子蜘蛛內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript實(shí)現(xiàn)數(shù)組隨機(jī)排序的方法
這篇文章主要介紹了JavaScript實(shí)現(xiàn)數(shù)組隨機(jī)排序的方法,涉及javascript數(shù)組遍歷與排序的相關(guān)技巧,需要的朋友可以參考下2015-06-06JavaScript實(shí)現(xiàn)經(jīng)典貪吃蛇游戲
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)經(jīng)典貪吃蛇游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09js將日期格式轉(zhuǎn)換為YYYY-MM-DD HH:MM:SS
這篇文章主要介紹了js將日期格式轉(zhuǎn)換為YYYY-MM-DD HH:MM:SS,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09uniapp微信小程序授權(quán)登錄并獲取手機(jī)號(hào)的方法
這篇文章主要給大家介紹了關(guān)于uniapp微信小程序授權(quán)登錄并獲取手機(jī)號(hào)的相關(guān)資料,我們?cè)趗niapp開發(fā)微信小程序的過程中,經(jīng)常需要在微信端登錄,需要的朋友可以參考下2023-06-06原生JavaScript實(shí)現(xiàn)的簡(jiǎn)單放大鏡效果示例
這篇文章主要介紹了原生JavaScript實(shí)現(xiàn)的簡(jiǎn)單放大鏡效果,涉及javascript事件響應(yīng)及頁面元素屬性動(dòng)態(tài)操作相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2018-02-02