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-06
JavaScript實(shí)現(xiàn)經(jīng)典貪吃蛇游戲
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)經(jīng)典貪吃蛇游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09
js將日期格式轉(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-09
uniapp微信小程序授權(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

