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

JavaScript實現(xiàn)文字黑洞特效的代碼詳解

 更新時間:2025年03月03日 09:32:22   作者:hy_2095  
這篇文章介紹了用 JavaScript 和 HTML5 Canvas 實現(xiàn)文字黑洞特效的項目,包括項目特征,如黑洞特效、引力效果、文字動畫和交互設(shè)計,以及技術(shù)亮點,還闡述了實現(xiàn)邏輯,涵蓋項目結(jié)構(gòu)、文字和黑洞對象的實現(xiàn)、動畫循環(huán)等,并給出了詳細(xì)的代碼示例和解釋

簡介:

在這篇教程中,我們將通過 JavaScript 和 HTML5 Canvas 實現(xiàn)一個酷炫的“文字黑洞”特效。當(dāng)用戶觸摸屏幕時,黑洞會吞噬周圍的文字,并隨著手指移動;松開手指后,文字會以爆炸效果回到原位。本文將詳細(xì)介紹如何實現(xiàn)引力效果、動畫邏輯以及交互設(shè)計,帶你一步步完成這個有趣的項目。

項目特征

1. 核心功能

  • 黑洞特效:用戶觸摸屏幕時,生成一個黑洞,吸引周圍的文字。
  • 引力效果:文字對象會根據(jù)黑洞的位置和引力范圍被吸引。
  • 文字動畫:文字圍繞黑洞旋轉(zhuǎn),并在黑洞消失后以爆炸效果回到原位。
  • 交互設(shè)計:黑洞會跟隨用戶手指移動,松開手指后黑洞消失。

2. 技術(shù)亮點

  • HTML5 Canvas:用于繪制文字、黑洞和動畫效果。
  • JavaScript 物理模擬:實現(xiàn)引力、速度和位置的計算。
  • 緩動動畫:讓文字回到原位時更加自然。
  • 觸摸事件:支持移動端觸摸交互。

實現(xiàn)邏輯

1. 項目結(jié)構(gòu)

  • Canvas 初始化:設(shè)置畫布大小和樣式。
  • 文字對象:每個字符是一個獨立對象,記錄位置、速度和狀態(tài)。
  • 黑洞對象:記錄黑洞的位置、半徑和狀態(tài)。
  • 動畫循環(huán):通過 requestAnimationFrame 實現(xiàn)平滑動畫。

2. 核心邏輯

(1)文字對象的實現(xiàn)

每個文字對象(Character 類)包含以下屬性:

  • char:字符內(nèi)容。
  • origXorigY:字符的原始位置。
  • xy:字符的當(dāng)前位置。
  • vxvy:字符的速度。
  • isCaptured:是否被黑洞捕獲。
  • angleangularSpeed:用于實現(xiàn)圍繞黑洞旋轉(zhuǎn)的效果。

代碼實現(xiàn):

class Character {
    constructor(char, x, y) {
        this.char = char;
        this.origX = x;
        this.origY = y;
        this.x = x;
        this.y = y;
        this.vx = 0;
        this.vy = 0;
        this.isCaptured = false;
        this.angle = Math.random() * Math.PI * 2;
        this.angularSpeed = (Math.random() - 0.5) * 0.1;
    }

    draw() {
        ctx.fillStyle = '#000';
        ctx.fillText(this.char, this.x, this.y);
    }

    update(blackHole) {
        if (blackHole && blackHole.active) {
            // 計算黑洞對字符的引力
            const dx = blackHole.x - this.x;
            const dy = blackHole.y - this.y;
            const dist = Math.sqrt(dx * dx + dy * dy);

            if (dist < blackHole.radius * 2) {
                const force = (blackHole.radius * 2 - dist) / (blackHole.radius * 2);
                const angle = Math.atan2(dy, dx);

                if (dist < blackHole.radius) {
                    this.isCaptured = true;
                    // 圍繞黑洞旋轉(zhuǎn)
                    this.angle += this.angularSpeed;
                    this.x = blackHole.x + Math.cos(this.angle) * blackHole.radius * 0.8;
                    this.y = blackHole.y + Math.sin(this.angle) * blackHole.radius * 0.8;
                } else {
                    this.vx += Math.cos(angle) * force * 1.5;
                    this.vy += Math.sin(angle) * force * 1.5;
                }
            } else {
                this.isCaptured = false;
            }
        } else {
            // 爆炸效果:從當(dāng)前位置回到原位
            if (this.isCaptured) {
                this.isCaptured = false;
                this.vx = (Math.random() - 0.5) * 10;
                this.vy = (Math.random() - 0.5) * 10;
            }

            const dx = this.origX - this.x;
            const dy = this.origY - this.y;
            const dist = Math.sqrt(dx * dx + dy * dy);

            if (dist > 1) {
                // 緩動回到原位
                const easing = 0.1;
                this.vx += dx * easing;
                this.vy += dy * easing;
            } else {
                this.x = this.origX;
                this.y = this.origY;
                this.vx = 0;
                this.vy = 0;
            }
        }

        // 更新位置
        this.x += this.vx;
        this.y += this.vy;
        this.vx *= 0.95;
        this.vy *= 0.95;
    }
}

(2)黑洞對象的實現(xiàn)

黑洞對象(BlackHole 類)包含以下屬性:

  • xy:黑洞的位置。
  • radius:黑洞的半徑。
  • targetRadius:黑洞的目標(biāo)半徑(用于動態(tài)調(diào)整大小)。
  • active:黑洞是否處于活動狀態(tài)。
  • capturedCount:被吞噬的文字?jǐn)?shù)量。

代碼實現(xiàn):

class BlackHole {
    constructor(x, y, radius) {
        this.x = x;
        this.y = y;
        this.radius = radius;
        this.targetRadius = radius;
        this.active = false;
        this.capturedCount = 0;
    }

    draw() {
        if (this.active) {
            // 根據(jù)吞噬數(shù)量調(diào)整半徑
            this.targetRadius = Math.min(100, 60 + this.capturedCount * 2);
            this.radius += (this.targetRadius - this.radius) * 0.1;

            // 繪制光暈效果
            const gradient1 = ctx.createRadialGradient(
                this.x, this.y, 0,
                this.x, this.y, this.radius * 1.5
            );
            gradient1.addColorStop(0, 'hsla(45, 100%, 70%, 0.8)');
            gradient1.addColorStop(1, 'hsla(45, 100%, 50%, 0)');
            ctx.fillStyle = gradient1;
            ctx.beginPath();
            ctx.arc(this.x, this.y, this.radius * 1.5, 0, Math.PI * 2);
            ctx.fill();

            // 繪制黑洞核心
            const gradient2 = ctx.createRadialGradient(
                this.x, this.y, 0,
                this.x, this.y, this.radius
            );
            gradient2.addColorStop(0, 'hsl(45, 100%, 50%)');
            gradient2.addColorStop(1, 'hsla(45, 100%, 50%, 0)');
            ctx.fillStyle = gradient2;
            ctx.beginPath();
            ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
            ctx.fill();
        }
    }
}

(3)動畫循環(huán)

通過 requestAnimationFrame 實現(xiàn)動畫循環(huán),每一幀更新文字和黑洞的狀態(tài)并重新繪制。

代碼實現(xiàn):

function animate() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // 更新和繪制字符
    let capturedCount = 0;
    characters.forEach(char => {
        char.update(blackHole);
        char.draw();
        if (char.isCaptured) capturedCount++;
    });

    // 更新黑洞吞噬數(shù)量
    if (blackHole) blackHole.capturedCount = capturedCount;

    // 繪制黑洞
    if (blackHole) blackHole.draw();

    requestAnimationFrame(animate);
}
animate();

html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <style>
        * { margin: 0; padding: 0; }
        canvas { touch-action: none; display: block; }
    </style>
</head>
<body>
<script src="index.js"></script>
</body>
</html>

index.js:

const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
document.body.appendChild(canvas);

// 固定 Canvas 尺寸
canvas.width = 320;
canvas.height = 720;

// 文本內(nèi)容
const textContent = `dealing with fonts with multiple weights and styles is easier to assign a different font family to each of them and just use different font families, in the example's code we do show how to load different weights with the same family name, but be aware that one font file has one family, one weight and one style. Loading the font Lato doesn't grant you access to all variants of the fonts, but just one. there is one file per variant.`;

// 字符類
class Character {
    constructor(char, x, y) {
        this.char = char; // 字符內(nèi)容
        this.origX = x; // 原始位置
        this.origY = y;
        this.x = x;
        this.y = y;
        this.vx = 0; // 速度
        this.vy = 0;
        this.isCaptured = false; // 是否被黑洞捕獲
        this.angle = Math.random() * Math.PI * 2; // 初始旋轉(zhuǎn)角度
        this.angularSpeed = (Math.random() - 0.5) * 0.1; // 旋轉(zhuǎn)速度
    }

    draw() {
        ctx.fillStyle = '#000';
        ctx.fillText(this.char, this.x, this.y);
    }

    update(blackHole) {
        if (blackHole && blackHole.active) {
            // 計算黑洞對字符的引力
            const dx = blackHole.x - this.x;
            const dy = blackHole.y - this.y;
            const dist = Math.sqrt(dx * dx + dy * dy);

            if (dist < blackHole.radius * 2) { // 引力范圍稍大一些
                const force = (blackHole.radius * 2 - dist) / (blackHole.radius * 2); // 引力強度
                const angle = Math.atan2(dy, dx);

                if (dist < blackHole.radius) {
                    this.isCaptured = true;
                    // 圍繞黑洞旋轉(zhuǎn)
                    this.angle += this.angularSpeed;
                    this.x = blackHole.x + Math.cos(this.angle) * blackHole.radius * 0.8;
                    this.y = blackHole.y + Math.sin(this.angle) * blackHole.radius * 0.8;
                } else {
                    this.vx += Math.cos(angle) * force * 1.5; // 增加引力強度
                    this.vy += Math.sin(angle) * force * 1.5;
                }
            } else {
                this.isCaptured = false;
            }
        } else {
            // 爆炸效果:從當(dāng)前位置回到原位
            if (this.isCaptured) {
                this.isCaptured = false;
                // 賦予隨機速度模擬爆炸
                this.vx = (Math.random() - 0.5) * 10;
                this.vy = (Math.random() - 0.5) * 10;
            }

            const dx = this.origX - this.x;
            const dy = this.origY - this.y;
            const dist = Math.sqrt(dx * dx + dy * dy);

            if (dist > 1) {
                // 緩動回到原位,減少彈跳力
                const easing = 0.1; // 緩動系數(shù),控制返回速度
                this.vx += dx * easing;
                this.vy += dy * easing;
            } else {
                this.x = this.origX;
                this.y = this.origY;
                this.vx = 0;
                this.vy = 0;
            }
        }

        // 更新位置
        this.x += this.vx;
        this.y += this.vy;
        this.vx *= 0.95; // 速度衰減
        this.vy *= 0.95;
    }
}

// 黑洞類
class BlackHole {
    constructor(x, y, radius) {
        this.x = x;
        this.y = y;
        this.radius = radius;
        this.targetRadius = radius; // 目標(biāo)半徑
        this.active = false;
        this.capturedCount = 0; // 吞噬的文字對象數(shù)量
    }

    draw() {
        if (this.active) {
            // 根據(jù)吞噬數(shù)量調(diào)整半徑
            this.targetRadius = Math.min(100, 60 + this.capturedCount * 2); // 最大半徑為 100
            this.radius += (this.targetRadius - this.radius) * 0.1; // 緩動調(diào)整半徑

            // 繪制光暈效果
            const gradient1 = ctx.createRadialGradient(
                this.x, this.y, 0,
                this.x, this.y, this.radius * 1.5
            );
            gradient1.addColorStop(0, 'hsla(45, 100%, 70%, 0.8)'); // 光暈內(nèi)圈
            gradient1.addColorStop(1, 'hsla(45, 100%, 50%, 0)'); // 光暈外圈
            ctx.fillStyle = gradient1;
            ctx.beginPath();
            ctx.arc(this.x, this.y, this.radius * 1.5, 0, Math.PI * 2);
            ctx.fill();

            // 繪制黑洞核心
            const gradient2 = ctx.createRadialGradient(
                this.x, this.y, 0,
                this.x, this.y, this.radius
            );
            gradient2.addColorStop(0, 'hsl(45, 100%, 50%)'); // 核心顏色
            gradient2.addColorStop(1, 'hsla(45, 100%, 50%, 0)'); // 漸變透明
            ctx.fillStyle = gradient2;
            ctx.beginPath();
            ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
            ctx.fill();
        }
    }
}

// 生成字符對象
const characters = [];
const fontSize = 14;
const lineHeight = 20;
const maxWidth = canvas.width - 20; // 留出邊距
ctx.font = `${fontSize}px Arial`;

let x = 10, y = 30; // 初始位置
for (const char of textContent) {
    if (x + ctx.measureText(char).width > maxWidth || char === '\n') {
        x = 10;
        y += lineHeight;
    }
    if (char !== '\n') {
        characters.push(new Character(char, x, y));
        x += ctx.measureText(char).width;
    }
}

let blackHole = null;

// 觸摸事件處理
canvas.addEventListener('touchstart', (e) => {
    const { clientX, clientY } = e.touches[0];
    const rect = canvas.getBoundingClientRect();
    const x = clientX - rect.left;
    const y = clientY - rect.top;
    blackHole = new BlackHole(x, y, 60); // 黑洞初始半徑 60
    blackHole.active = true;
});

canvas.addEventListener('touchmove', (e) => {
    if (blackHole) {
        const { clientX, clientY } = e.touches[0];
        const rect = canvas.getBoundingClientRect();
        blackHole.x = clientX - rect.left;
        blackHole.y = clientY - rect.top;
    }
});

canvas.addEventListener('touchend', () => {
    if (blackHole) blackHole.active = false;
});

// 動畫循環(huán)
function animate() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // 更新和繪制字符
    let capturedCount = 0;
    characters.forEach(char => {
        char.update(blackHole);
        char.draw();
        if (char.isCaptured) capturedCount++;
    });

    // 更新黑洞吞噬數(shù)量
    if (blackHole) blackHole.capturedCount = capturedCount;

    // 繪制黑洞
    if (blackHole) blackHole.draw();

    requestAnimationFrame(animate);
}
animate();

總結(jié)

通過這篇教程,我們實現(xiàn)了一個基于 JavaScript 和 Canvas 的文字黑洞特效。核心邏輯包括:

  • 引力效果:通過計算字符與黑洞的距離和角度,模擬引力作用。
  • 動畫邏輯:使用緩動函數(shù)和隨機速度實現(xiàn)文字回到原位的爆炸效果。
  • 交互設(shè)計:通過觸摸事件實現(xiàn)黑洞的生成和移動。

以上就是使用JavaScript實現(xiàn)文字黑洞特效的代碼詳解的詳細(xì)內(nèi)容,更多關(guān)于JavaScript文字黑洞特效的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 前端本地數(shù)據(jù)存儲的幾種常見方式總結(jié)

    前端本地數(shù)據(jù)存儲的幾種常見方式總結(jié)

    在前端開發(fā)中,本地數(shù)據(jù)存儲是實現(xiàn)客戶端數(shù)據(jù)持久化的關(guān)鍵技術(shù),以下是幾種常見的前端本地數(shù)據(jù)存儲方式,通過代碼示例講解的非常詳細(xì),需要的朋友可以參考下
    2025-01-01
  • js 創(chuàng)建快捷方式的代碼(fso)

    js 創(chuàng)建快捷方式的代碼(fso)

    js 創(chuàng)建快捷方式的代碼,這個是在本地運行的需要確認(rèn)的,需要的朋友可以參考下。
    2010-11-11
  • JS常用算法實現(xiàn)代碼

    JS常用算法實現(xiàn)代碼

    這篇文章主要為大家詳細(xì)介紹了用JS常用幾個算法的實現(xiàn)代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-11-11
  • javascript 使用 NodeList需要注意的問題

    javascript 使用 NodeList需要注意的問題

    理解NodeList及其近親NamedNodeMap和HTMLCollection,是從整體上透徹理解DOM的關(guān)鍵所在,這三個集合都是“動態(tài)的”,換句話說,每當(dāng)文檔結(jié)構(gòu)發(fā)生變化時,他們都會得到更新。
    2013-03-03
  • JScript實現(xiàn)表格的簡單操作

    JScript實現(xiàn)表格的簡單操作

    這篇文章主要為大家詳細(xì)介紹了JScript實現(xiàn)簡單的表格操作,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-08-08
  • js replace 全局替換的操作方法

    js replace 全局替換的操作方法

    這篇文章主要介紹了js replace 全局替換的操作方法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下
    2018-06-06
  • 為什么JavaScript中0.1 + 0.2 != 0.3

    為什么JavaScript中0.1 + 0.2 != 0.3

    這篇文章主要給大家介紹了關(guān)于為什么JavaScript中0.1 + 0.2 != 0.3的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-12-12
  • js限制input標(biāo)簽中只能輸入中文

    js限制input標(biāo)簽中只能輸入中文

    這篇文章主要介紹了js限制input標(biāo)簽中只能輸入中文的的相關(guān)資料,需要的朋友可以參考下
    2015-06-06
  • js時間日期和毫秒的相互轉(zhuǎn)換

    js時間日期和毫秒的相互轉(zhuǎn)換

    js時間日期和毫秒的相互轉(zhuǎn)換,需要的朋友可以參考一下
    2013-02-02
  • 詳解webpack 熱更新優(yōu)化

    詳解webpack 熱更新優(yōu)化

    這篇文章主要介紹了詳解webpack 熱更新優(yōu)化,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-09-09

最新評論

大城县| 德安县| 滨海县| 兴城市| 浪卡子县| 湖州市| 呼和浩特市| 湖口县| 湟中县| 邵阳县| 浪卡子县| 湟源县| 盈江县| 青冈县| 周口市| 温泉县| 阳原县| 壶关县| 柳林县| 策勒县| 尖扎县| 拉萨市| 达州市| 资源县| 封开县| 申扎县| 拉萨市| 林甸县| 苏尼特右旗| 石门县| 上饶市| 昭平县| 定州市| 万盛区| 屏边| 遵义县| 庆城县| 姜堰市| 延津县| 高阳县| 正蓝旗|