最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

JavaScript實(shí)現(xiàn)一個(gè)電子小蜘蛛

 更新時(shí)間:2024年10月03日 09:19:59   作者:秋刀魚(yú)不做夢(mèng)  
這篇文章主要介紹了JavaScript實(shí)現(xiàn)一個(gè)電子小蜘蛛,具體的樣子就是讓它會(huì)跟隨著我們的鼠標(biāo)進(jìn)行移動(dòng),那么我們?nèi)绾螌?shí)現(xiàn)這樣的效果呢,下面來(lái)詳細(xì)講解實(shí)現(xiàn)方法,需要的朋友可以參考下

前言

在學(xué)習(xí)完JavaScript之后,我們就可以使用JavaScript來(lái)實(shí)現(xiàn)一下好玩的效果了,本篇文章講解的是如何純使用JavaScript來(lái)實(shí)現(xiàn)一個(gè)網(wǎng)頁(yè)中的電子蜘蛛。

在開(kāi)始學(xué)習(xí)如何編寫(xiě)一個(gè)網(wǎng)頁(yè)蜘蛛之前,先讓我們看一下這個(gè)電子蜘蛛長(zhǎng)什么樣:

——我們可以看到,其會(huì)跟隨著我們的鼠標(biāo)進(jìn)行移動(dòng),那么我們?nèi)绾螌?shí)現(xiàn)這樣的效果呢?接下來(lái)讓我們開(kāi)始講解。

HTML代碼

我們的html代碼十分的簡(jiǎn)單,就是創(chuàng)建一個(gè)畫(huà)布,而我們接下來(lái)的操作,都是在此上邊進(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>秋刀魚(yú)不做夢(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)頁(yè)背景顏色為黑色 */
            background: rgb(0, 0, 0);
        }
    </style>
</head>
<body>
    <!-- 創(chuàng)建一個(gè)畫(huà)布用于圖形繪制 -->
    <canvas id="canvas"></canvas>
</body>
</html>

可以看到我們的HTML代碼非常的簡(jiǎn)單,接下來(lái)讓我們開(kāi)始在其上邊進(jìn)行操作!

JavaScript代碼

在開(kāi)始編寫(xiě)JavaScript代碼之前,先讓我們理清一下思路:

總體流程

  • 頁(yè)面加載時(shí),canvas 元素和繪圖上下文初始化。
  • 定義觸手對(duì)象,每條觸手由多個(gè)段組成。
  • 監(jiān)聽(tīng)鼠標(biāo)移動(dòng)事件,實(shí)時(shí)更新鼠標(biāo)的位置。
  • 通過(guò)動(dòng)畫(huà)循環(huán)繪制觸手,觸手根據(jù)鼠標(biāo)的位置動(dòng)態(tài)變化,形成流暢的動(dòng)畫(huà)效果。

大致的流程就是上邊的步驟,但是我相信讀者在沒(méi)用自己完成此代碼的編寫(xiě)之前,可能不能理解上邊的流程,不過(guò)沒(méi)關(guān)系,現(xiàn)在讓我們開(kāi)始我們的網(wǎng)頁(yè)小蜘蛛的編寫(xiě):

寫(xiě)在前面:為了讓讀者可以更好的理解代碼的邏輯,我們給沒(méi)一句代碼都加上了注釋?zhuān)Mx者可以根據(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í)來(lái)調(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是小寫(xiě)的
    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 }
}
// 等待頁(yè)面加載完成后執(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 類(lèi)
    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 類(lèi)
    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"
                // 開(kāi)始新路徑
                c.beginPath()
                // 從觸手起始位置開(kāi)始繪制線(xiàn)條
                c.moveTo(this.x, this.y)
                // 遍歷所有的segment對(duì)象,并使用他們的show方法回執(zhí)線(xiàn)條
                for (let i = 0; i < this.n; i++) {
                    this.segments[i].show()
                }
                // 設(shè)置線(xiàn)條樣式
                c.strokeStyle = "hsl(" + (this.rand * 60 + 180) +
                    ",100%," + (this.rand * 60 + 25) + "%)"
                // 設(shè)置線(xiàn)條寬度
                c.lineWidth = this.rand * 2
                // 設(shè)置線(xiàn)條端點(diǎn)樣式
                c.lineCap = "round"
                // 設(shè)置線(xiàn)條連接處樣式
                c.lineJoin = "round"
                // 繪制線(xiàn)條
                c.stroke()
                // 設(shè)置全局合成操作為“source-over”
                c.globalCompositeOperation = "source-over"
            }
        }
        // 繪制觸手的圓形頭的方法
        show2(target) {
            // 開(kāi)始新路徑
            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)畫(huà)的函數(shù)
    function loop() {
        // 使用requestAnimFrame函數(shù)循環(huán)執(zhí)行
        window.requestAnimFrame(loop)
        // 清空canvas
        c.clearRect(0, 0, w, h)
        // 繪制動(dòng)畫(huà)
        draw()
    }
    // 監(jiān)聽(tīng)窗口大小改變事件
    window.addEventListener("resize", function () {
        // 重置canvas的大小
        w = canvas.width = window.innerWidth
        w = canvas.height = window.innerHeight
        // 循環(huán)執(zhí)行回執(zhí)動(dòng)畫(huà)的函數(shù)
        loop()
    })
    // 循環(huán)執(zhí)行回執(zhí)動(dòng)畫(huà)的函數(shù)
    loop()
    // 使用setInterval函數(shù)循環(huán)
    setInterval(loop, 1000 / 60)
    // 監(jiān)聽(tīng)鼠標(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)聽(tīng)鼠標(biāo)離開(kāi)事件
    canvas.addEventListener("mouseleave", function (e) {
        // 將mouse設(shè)為false
        mouse.x = false
        mouse.y = false
    })
}

這里我們?cè)诖笾碌氖崂硪幌律鲜龃a的流程:

初始化階段

  • init 函數(shù):當(dāng)頁(yè)面加載時(shí),init 函數(shù)被調(diào)用,獲取 canvas 元素并設(shè)置其寬高為窗口的大小。獲取到的 2D 繪圖上下文(context)用于后續(xù)繪制。
  • window.onload:頁(yè)面加載完成后,初始化 canvascontext,并設(shè)置鼠標(biāo)初始狀態(tài)。

觸手對(duì)象的定義

  • segment 類(lèi):這是觸手的一段,每個(gè)段有起始點(diǎn)(pos)、長(zhǎng)度(l)、角度(ang),并通過(guò)角度計(jì)算出下一段的位置(nextPos)。
  • tentacle 類(lèi):代表完整的觸手,由若干個(gè) segment 組成。觸手的起始點(diǎn)在屏幕中心,并且每個(gè)觸手包含多個(gè)段。tentacle 的主要方法有: move:根據(jù)鼠標(biāo)位置更新每一段的位置。show:繪制觸手的路徑。

事件監(jiān)聽(tīng)

canvas.addEventListener("mousemove", ...):當(dāng)鼠標(biāo)移動(dòng)時(shí),捕捉鼠標(biāo)的位置并存儲(chǔ)在 mouse 變量中。每次鼠標(biāo)移動(dòng)會(huì)更新 mouselast_mouse 的坐標(biāo),用于后續(xù)的動(dòng)畫(huà)。

動(dòng)畫(huà)循環(huán)

  • draw 函數(shù):這是一個(gè)遞歸的函數(shù),用于創(chuàng)建動(dòng)畫(huà)效果。 首先,它會(huì)在每一幀中為畫(huà)布填充半透明背景,使得之前繪制的內(nèi)容逐漸消失,產(chǎn)生拖影效果。
  • 然后,遍歷所有觸手(tentacles),調(diào)用它們的 moveshow 方法,更新位置并繪制每一幀。
  • 最后,使用 requestAnimFrame(draw) 不斷遞歸調(diào)用 draw,形成一個(gè)動(dòng)畫(huà)循環(huán)。

觸手的行為

  • 觸手的運(yùn)動(dòng)是通過(guò) move 函數(shù)實(shí)現(xiàn)的,觸手的最后一個(gè)段首先更新位置,然后其他段依次跟隨。
  • 觸手的繪制通過(guò) show 函數(shù),遍歷所有段并繪制線(xiàn)條,最后顯示在屏幕上。

——這樣我們就完成了電子小蜘蛛的制作了?。?!

最后,在讓我們看一下最終效果:

到此這篇關(guān)于JavaScript實(shí)現(xiàn)一個(gè)電子小蜘蛛的文章就介紹到這了,更多相關(guān)JavaScript電子蜘蛛內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論

华亭县| 汝城县| 嘉祥县| 吉水县| 巴里| 宝山区| 鄂州市| 涪陵区| 贵阳市| 西昌市| 化州市| 盈江县| 察雅县| 织金县| 廉江市| 微山县| 灌云县| 高碑店市| 永顺县| 秭归县| 扶绥县| 砚山县| 金川县| 嘉祥县| 清水河县| 津南区| 岐山县| 九寨沟县| 高唐县| 右玉县| 江津市| 长春市| 盐亭县| 朝阳市| 德保县| 清新县| 皮山县| 莒南县| 府谷县| 萝北县| 武穴市|