JavaScript瀏覽器內(nèi)置的四大Web Worker的使用詳解
一、為什么 Web Worker 能實現(xiàn) “多線程”
瀏覽器的 JS 引擎(如 V8)本身是單線程的,但瀏覽器是多進程 / 多線程架構(gòu)。Web Worker 的本質(zhì)是 瀏覽器為 JS 提供的 “額外線程池” ,核心原理:
- 線程隔離:Worker 線程與主線程是完全隔離的內(nèi)存空間(不共享堆內(nèi)存),通過 “消息隊列” 通信(數(shù)據(jù)傳輸基于結(jié)構(gòu)化克隆算法,深拷貝)。
- 阻塞無關(guān)性:Worker 線程的阻塞(如死循環(huán))不會影響主線程,但會導(dǎo)致自身無法響應(yīng)消息。
- 資源限制:瀏覽器對 Worker 數(shù)量有上限(通常同源下不超過 20 個),且單個 Worker 占用的內(nèi)存有限制(避免濫用系統(tǒng)資源)。
關(guān)鍵區(qū)別:與 Node.js 的 child_process 不同,Web Worker 無法共享內(nèi)存(需通過 SharedArrayBuffer 實現(xiàn)有限共享,見下文),且受瀏覽器安全策略(如跨域限制)約束。
總結(jié): webwork是通過js的方式喚起瀏覽器的內(nèi)置api使用,輔助前端計算的一種方式,就像fetch、ajaix那樣喚起瀏覽器的接口查詢一樣。
多線程四大API
Web Worker 是前端 “多線程” 的基礎(chǔ)規(guī)范,但瀏覽器針對不同場景設(shè)計了 4 種獨立的 Worker 類型—— 它們共享 “線程隔離” 的核心思想(避免阻塞主線程),但定位、能力、使用場景完全不同,均為 W3C 標(biāo)準(zhǔn)定義的原生 API(無需第三方庫),底層由瀏覽器獨立實現(xiàn)(無依賴關(guān)系)。
瀏覽器 4 種核心 Worker 類型對比表
| Worker 類型 | 核心定位 | 底層依賴 | 典型場景 |
|---|---|---|---|
| Web Worker | 主線程的 “計算助手”,處理耗時計算 | 瀏覽器線程池 | 大數(shù)據(jù)排序、加密解密、復(fù)雜算法計算 |
| Shared Worker | 多頁面共享的 “后臺協(xié)調(diào)者”,跨頁面通信 | 瀏覽器共享線程 | 多標(biāo)簽頁登錄狀態(tài)同步、跨頁數(shù)據(jù)協(xié)同 |
| Service Worker | 頁面離線的 “代理服務(wù)器”,攔截請求 + 緩存 | 瀏覽器后臺線程 | 離線應(yīng)用、請求攔截優(yōu)化、瀏覽器推送通知 |
| Worklet | 渲染管線的 “實時處理器”,介入渲染流程 | 瀏覽器渲染線程 | CSS 物理動畫、音頻降噪、Canvas 渲染優(yōu)化 |
二、分類
1. Web Worker(計算型:解決主線程阻塞)
核心能力:
獨立于主線程的 “計算線程”,僅與創(chuàng)建它的主線程通信(一對一);
生命周期與頁面綁定(頁面關(guān)閉則 Worker 銷毀);
不阻塞 UI,專門處理耗時計算(避免頁面卡頓)。
示例:10 萬條數(shù)據(jù)排序
// 主線程(main.js):發(fā)起計算請求,接收結(jié)果
if (window.Worker) {
// 1. 創(chuàng)建 Worker 實例
const sortWorker = new Worker('sort-worker.js');
// 2. 生成 10 萬條隨機大數(shù)據(jù)(模擬復(fù)雜計算場景)
const bigData = Array.from({ length: 100000 }, () => Math.random() * 100000);
// 3. 發(fā)送數(shù)據(jù)給 Worker,觸發(fā)計算
sortWorker.postMessage(bigData);
console.log('主線程:已發(fā)送數(shù)據(jù),等待排序結(jié)果...');
// 4. 接收 Worker 返回的計算結(jié)果
sortWorker.onmessage = (e) => {
console.log('主線程:排序完成,前 10 條數(shù)據(jù):', e.data.slice(0, 10));
sortWorker.terminate(); // 計算完成,銷毀 Worker(避免內(nèi)存泄漏)
};
// 5. 監(jiān)聽 Worker 錯誤(如代碼報錯)
sortWorker.onerror = (err) => {
console.error(`Worker 錯誤:${err.message}(行號:${err.lineno})`);
sortWorker.terminate();
};
} else {
console.error('當(dāng)前瀏覽器不支持 Web Worker');
}
// Worker 線程(sort-worker.js):執(zhí)行耗時計算
self.onmessage = (e) => {
const data = e.data;
console.log('Worker 線程:開始排序 10 萬條數(shù)據(jù)...');
// 耗時計算(示例用內(nèi)置排序,實際可替換為快排、歸并等復(fù)雜算法)
const sortedData = data.sort((a, b) => a - b);
// 向主線程返回結(jié)果
self.postMessage(sortedData);
self.close(); // Worker 主動關(guān)閉,釋放資源
};
運行效果:
主線程可正常處理用戶交互(點擊、滾動),排序在后臺線程執(zhí)行,頁面無卡頓;計算完成后自動銷毀 Worker,無內(nèi)存泄漏。
2. Shared Worker(協(xié)同型:多頁面數(shù)據(jù)同步)
核心能力:
同源多頁面可共享同一個 Worker 實例(突破 “單頁面私有” 限制);
通過 port(通信端口)實現(xiàn)多頁面與 Worker 的雙向通信;
適合跨頁面狀態(tài)同步(無需重復(fù)請求接口)。
示例:多標(biāo)簽頁登錄狀態(tài)同步
// 主線程 - 頁面 A(login.html):登錄后同步狀態(tài)
if (window.SharedWorker) {
// 1. 創(chuàng)建 Shared Worker 實例
const sharedWorker = new SharedWorker('sync-worker.js');
// 2. 激活通信端口(必須調(diào)用 start())
sharedWorker.port.start();
// 3. 模擬登錄按鈕點擊,發(fā)送登錄狀態(tài)給 Worker
document.getElementById('login-btn').addEventListener('click', () => {
const userInfo = { username: 'admin', isLogin: true };
sharedWorker.port.postMessage({ type: 'LOGIN', data: userInfo });
console.log('頁面 A:已發(fā)送登錄狀態(tài)');
});
// 4. 接收 Worker 廣播的消息(如其他頁面同步的狀態(tài))
sharedWorker.port.onmessage = (e) => {
console.log('頁面 A 收到消息:', e.data);
};
}
// 主線程 - 頁面 B(index.html):實時獲取登錄狀態(tài)
if (window.SharedWorker) {
const sharedWorker = new SharedWorker('sync-worker.js');
sharedWorker.port.start();
// 1. 向 Worker 請求當(dāng)前登錄狀態(tài)
sharedWorker.port.postMessage({ type: 'QUERY_STATUS' });
// 2. 接收狀態(tài)(頁面 A 登錄后,頁面 B 實時更新)
sharedWorker.port.onmessage = (e) => {
if (e.data.type === 'LOGIN_STATUS') {
document.getElementById('status').textContent = e.data.isLogin
? `已登錄:${e.data.username}`
: '未登錄';
}
};
}
// Shared Worker 線程(sync-worker.js):維護全局狀態(tài),廣播消息
const connections = []; // 存儲所有連接的頁面端口
let globalState = { isLogin: false, username: '' }; // 全局共享狀態(tài)
// 監(jiān)聽頁面連接(新頁面打開時觸發(fā))
self.onconnect = (e) => {
const port = e.ports[0];
connections.push(port); // 記錄新連接的頁面
port.start();
// 處理頁面發(fā)送的消息
port.onmessage = (msg) => {
switch (msg.data.type) {
case 'LOGIN':
// 更新全局狀態(tài)
globalState = msg.data.data;
// 廣播給所有連接的頁面(同步狀態(tài)到頁面 A、B...)
connections.forEach(p => p.postMessage({
type: 'LOGIN_STATUS',
...globalState
}));
break;
case 'QUERY_STATUS':
// 單獨響應(yīng)某個頁面的狀態(tài)查詢
port.postMessage({ type: 'LOGIN_STATUS', ...globalState });
break;
}
};
// 頁面關(guān)閉時,移除端口(避免內(nèi)存泄漏)
port.onclose = () => {
const index = connections.indexOf(port);
if (index !== -1) connections.splice(index, 1);
};
};
運行效果:
頁面 A 點擊 “登錄” 后,頁面 B 無需刷新,實時顯示 “已登錄:admin”;多標(biāo)簽頁共享同一登錄狀態(tài),無需重復(fù)調(diào)用登錄接口。
3. Service Worker(網(wǎng)絡(luò)型:離線緩存 + 請求攔截)
核心能力:
完全獨立于頁面,運行在瀏覽器后臺(頁面關(guān)閉后仍可活動);
攔截所有網(wǎng)絡(luò)請求,可自定義緩存策略(實現(xiàn)離線訪問);
生命周期包含 “安裝→激活→運行”,需手動管理緩存版本。
示例:離線緩存靜態(tài)資源 + API 請求
// 主線程(main.js):注冊 Service Worker,觸發(fā)離線邏輯
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
try {
// 1. 注冊 Service Worker(腳本需在根目錄,確保作用域覆蓋所有頁面)
const registration = await navigator.serviceWorker.register('/sw.js');
console.log('Service Worker 注冊成功,作用域:', registration.scope);
// 2. 測試離線請求(首次聯(lián)網(wǎng)緩存,后續(xù)斷網(wǎng)可訪問)
fetch('/api/data').then(res => res.json()).then(data => {
console.log('API 請求結(jié)果:', data);
});
} catch (err) {
console.error('Service Worker 注冊失?。?, err);
}
});
}
// Service Worker 線程(sw.js):緩存+請求攔截邏輯
const CACHE_NAME = 'offline-cache-v1'; // 緩存版本(更新時修改版本號)
// 需要緩存的資源列表(靜態(tài)資源+API接口)
const CACHE_ASSETS = [
'/',
'/index.html',
'/styles.css',
'/api/data' // 需緩存的 API 接口
];
// 1. 安裝階段:緩存靜態(tài)資源(僅首次注冊/版本更新時觸發(fā))
self.addEventListener('install', (event) => {
// 等待緩存完成后,再進入激活階段
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(CACHE_ASSETS)) // 批量緩存資源
.then(() => self.skipWaiting()) // 跳過等待,立即激活新 Worker(替換舊版本)
);
});
// 2. 激活階段:清理舊緩存(避免緩存膨脹)
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then(cacheNames => {
// 刪除非當(dāng)前版本的緩存
return Promise.all(
cacheNames.filter(name => name !== CACHE_NAME).map(name => caches.delete(name))
);
}).then(() => self.clients.claim()) // 強制所有打開的頁面使用新 Worker
);
});
// 3. 攔截請求:優(yōu)先從緩存返回,無緩存則請求網(wǎng)絡(luò)
self.addEventListener('fetch', (event) => {
// 僅攔截同源的 GET 請求(避免跨域資源和非冪等請求)
if (event.request.method === 'GET' && event.request.mode === 'same-origin') {
event.respondWith(
caches.match(event.request)
.then(cachedResponse => {
// 緩存命中:直接返回緩存資源
if (cachedResponse) {
console.log('從緩存返回:', event.request.url);
return cachedResponse;
}
// 緩存未命中:發(fā)起網(wǎng)絡(luò)請求,并緩存新結(jié)果
return fetch(event.request).then(networkResponse => {
caches.open(CACHE_NAME).then(cache => {
cache.put(event.request, networkResponse.clone()); // 緩存新請求
});
return networkResponse;
});
})
);
}
});
運行效果:
首次聯(lián)網(wǎng)時,自動緩存 index.html、styles.css 和 /api/data;
斷網(wǎng)后刷新頁面,仍能正常加載頁面和 API 數(shù)據(jù)(從緩存讀?。?/p>
緩存版本更新時,自動清理舊緩存,避免資源冗余。
4. Worklet(實時型:介入渲染流程)
核心能力:
嵌入瀏覽器渲染管線(如 CSS 引擎線程、音頻線程),低延遲(<1ms);
直接干預(yù)渲染過程(動畫、繪制、音頻處理),彌補 Web Worker 通信延遲的缺陷;
細分類型:CSSWorklet(動畫)、AudioWorklet(音頻)、PaintWorklet(繪制)。
示例:CSSWorklet 實現(xiàn)物理彈性動畫
// 主線程(main.js):注冊 Worklet,關(guān)聯(lián)動畫
if ('CSSWorklet' in window) {
try {
// 注冊自定義 Worklet(加載動畫邏輯腳本)
await CSSWorklet.addModule('bounce-worklet.js');
console.log('CSSWorklet 注冊成功');
} catch (err) {
console.error('CSSWorklet 注冊失?。?, err);
}
}
// HTML 結(jié)構(gòu):動畫元素
/*
<div class="box">彈性動畫方塊</div>
*/
// CSS 樣式:綁定 Worklet 動畫
/*
.box {
width: 100px;
height: 100px;
background: red;
/* 使用 Worklet 定義的動畫(名稱與 Worklet 中注冊一致) */
animation: bounce 2s infinite;
}
/* 定義動畫進度(from→to 對應(yīng) Worklet 中的 0→1) */
@keyframes bounce {
from { transform: translateY(0); }
to { transform: translateY(300px); }
}
*/
// CSSWorklet 線程(bounce-worklet.js):自定義動畫邏輯
class BounceWorklet {
// 每一幀的計算(嵌入渲染管線,實時執(zhí)行)
process(inputs, outputs, parameters) {
const [t] = inputs; // 動畫進度(0~1,from→to)
const [output] = outputs; // 輸出結(jié)果(最終的 CSS 樣式值)
// 物理彈性公式(模擬重力+反彈效果,非勻速動畫)
const bounce = Math.sin(t * Math.PI) * Math.exp(-t * 0.5);
// 輸出 transform 樣式(控制方塊位置)
output.value = `translateY(${300 * (1 - bounce)}px)`;
}
}
// 注冊 Worklet 動畫(名稱需與 CSS 中的 @keyframes 名稱一致)
registerAnimator('bounce', BounceWorklet);
運行效果:
紅色方塊以 “物理彈性軌跡” 上下運動(類似小球落地反彈),動畫流暢無卡頓;Worklet 運行在渲染線程,延遲極低,避免 Web Worker 通信導(dǎo)致的動畫掉幀。
三、混合使用:多 Worker 協(xié)同解決復(fù)雜場景
場景需求:離線數(shù)據(jù)分析 Dashboard
需要同時滿足 4 個核心需求:
離線訪問(斷網(wǎng)后仍可查看歷史數(shù)據(jù));
多標(biāo)簽頁同步分析結(jié)果(無需重復(fù)解析);
后臺解析 10 萬條 CSV 數(shù)據(jù)(不阻塞頁面);
實時渲染流暢圖表(動畫無掉幀)。
完整實現(xiàn)代碼(整合 4 種 Worker)
// 主線程(dashboard.js):整合所有 Worker,協(xié)調(diào)流程
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
try {
// 1. 第一步:注冊 Service Worker(離線緩存)
const swRegistration = await navigator.serviceWorker.register('/sw.js');
console.log('Service Worker 注冊成功');
// 2. 第二步:創(chuàng)建 Shared Worker(多頁面同步)
const sharedWorker = new SharedWorker('sync-worker.js');
sharedWorker.port.start();
// 3. 第三步:創(chuàng)建 Web Worker(CSV 數(shù)據(jù)解析)
const csvWorker = new Worker('csv-worker.js');
// 4. 第四步:注冊 CSSWorklet(圖表動畫優(yōu)化)
if ('CSSWorklet' in window) {
await CSSWorklet.addModule('chart-worklet.js');
console.log('CSSWorklet 注冊成功');
}
// 5. 頁面元素:文件上傳、圖表容器、狀態(tài)文本
const fileInput = document.getElementById('file-upload');
const chartContainer = document.getElementById('chart-container');
const statusText = document.getElementById('status');
// 6. 監(jiān)聽文件上傳:觸發(fā) CSV 解析
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file && file.name.endsWith('.csv')) {
statusText.textContent = '正在解析 CSV 文件...';
csvWorker.postMessage(file); // 發(fā)送文件給 Web Worker
} else {
statusText.textContent = '請上傳 CSV 格式文件!';
}
});
// 7. 接收 Web Worker 解析結(jié)果:渲染+同步+緩存
csvWorker.onmessage = (e) => {
const analysisResult = e.data; // 解析結(jié)果(數(shù)據(jù)數(shù)組+統(tǒng)計信息)
// 7.1 渲染圖表(用 CSSWorklet 優(yōu)化動畫)
renderChart(analysisResult, chartContainer);
// 7.2 同步結(jié)果到其他標(biāo)簽頁(通過 Shared Worker)
sharedWorker.port.postMessage({
type: 'ANALYSIS_RESULT',
data: analysisResult
});
// 7.3 緩存結(jié)果到 Service Worker(支持離線訪問)
if (navigator.serviceWorker.controller) {
navigator.serviceWorker.controller.postMessage({
type: 'CACHE_RESULT',
key: 'last-csv-analysis',
data: analysisResult
});
}
// 7.4 更新狀態(tài)文本
statusText.textContent = `解析完成:共 ${analysisResult.totalCount} 條</doubaocanvas>
四、關(guān)于self
1. 來源:Worker 線程的內(nèi)置全局對象,瀏覽器自動注入,無需定義
2. 作用:等同于主線程的 window,是 Worker 線程訪問自身能力的入口
3. 隔離性:不同 Worker 的 self 是獨立實例,互不干擾(如 Web Worker 的 self ≠ Service Worker 的 self)
4. 核心能力:接收/發(fā)送消息(onmessage/postMessage)、管理生命周期(close)、調(diào)用 Worker 專屬 API
Worker 中 self 的簡單代碼示例
// Worker 線程(calc-worker.js)
// self 指向當(dāng)前 Web Worker 實例,僅用于計算相關(guān)邏輯
self.onmessage = (e) => {
const { num1, num2 } = e.data;
const sum = num1 + num2; // 簡單計算:兩數(shù)相加
self.postMessage(sum); // 用 self 向主線程返回結(jié)果
self.close(); // 用 self 主動關(guān)閉 Worker,釋放資源
};
// 主線程(main.js)- 配合使用
const calcWorker = new Worker('calc-worker.js');
calcWorker.postMessage({ num1: 10, num2: 20 }); // 發(fā)數(shù)據(jù)給 Worker
calcWorker.onmessage = (e) => {
console.log('計算結(jié)果:', e.data); // 輸出 30
};
注意
Worker 中不能用 window,必須用 self(因 Worker 無窗口概念)
self 無法訪問 DOM(如 document、body),僅能處理非 UI 邏輯
簡單場景可省略 self(如 onmessage = () => {} 等同于 self.onmessage = () => {}),但復(fù)雜場景建議保留,增強可讀性
五、生產(chǎn)環(huán)境使用的平衡點
1. 錯誤處理與健壯性
Worker 內(nèi)部錯誤不會冒泡到主線程,需單獨監(jiān)聽:
// 主線程
worker.onerror = (err) => {
console.error(`Worker錯誤:${err.message}`);
worker.terminate(); // 出錯后銷毀Worker,避免內(nèi)存泄漏
};
// Worker線程
self.onerror = (err) => {
self.postMessage({ type: 'ERROR', message: err.message });
self.close(); // 主動關(guān)閉
};
網(wǎng)絡(luò)錯誤:Worker 腳本加載失?。?04)時,主線程會觸發(fā) error 事件,需捕獲并降級處理。
2. 內(nèi)存管理與資源回收
- 避免創(chuàng)建過多 Worker:每個 Worker 都是獨立線程,占用內(nèi)存和 CPU,建議通過 “Worker 池” 復(fù)用(如用
p-queue管理 Worker 實例)。 - 及時銷毀無用 Worker:
worker.terminate()(主線程主動銷毀,立即終止)或self.close()(Worker 主動關(guān)閉,清理后終止)。 - 警惕內(nèi)存泄漏:Worker 中若持有
setInterval、未關(guān)閉的fetch請求等,即使調(diào)用terminate也可能導(dǎo)致內(nèi)存泄漏,需先清理資源。
3. 跨域與安全策略
- Worker 腳本必須與主線程同源(協(xié)議、域名、端口一致),若需加載跨域腳本,需通過
importScripts加載且服務(wù)器允許跨域(Access-Control-Allow-Origin)。 - 禁止訪問
file://協(xié)議下的腳本(瀏覽器安全限制),本地開發(fā)需用http-server啟動服務(wù)。 - 敏感操作限制:Worker 中無法使用
localStorage(部分瀏覽器支持,但規(guī)范不推薦),建議用IndexedDB存儲大量數(shù)據(jù)(異步 API,不阻塞)。
4. 兼容性處理與降級方案
瀏覽器支持:IE 完全不支持,Edge 12+、Chrome 4+、Firefox 3.5+ 支持基本特性,SharedArrayBuffer 和 SharedWorker 兼容性較差。
降級邏輯:
if (window.Worker) {
// 使用Worker
} else {
// 降級到主線程執(zhí)行(給用戶提示“當(dāng)前瀏覽器可能卡頓”)
heavyTask();
}
六、性能陷阱
過度使用 Worker 導(dǎo)致性能反降小數(shù)據(jù)計算(如幾毫秒可完成的操作)用 Worker 反而會增加通信開銷(序列化 + 消息傳遞),建議僅對 執(zhí)行時間 > 50ms 的任務(wù)使用 Worker。
頻繁通信導(dǎo)致主線程阻塞若 Worker 與主線程高頻次 postMessage(如每秒 hundreds 次),序列化數(shù)據(jù)會占用主線程資源,導(dǎo)致卡頓。解決方案:
- 批量發(fā)送數(shù)據(jù)(累計一定量后再通信);
- 用
SharedArrayBuffer減少序列化開銷。
Worker 中濫用同步 APIWorker 雖然不阻塞 UI,但內(nèi)部的同步操作(如 XMLHttpRequest 的同步請求、大量同步循環(huán))會阻塞自身線程,導(dǎo)致無法響應(yīng)消息。建議優(yōu)先使用異步 API(fetch、setImmediate)。
忽略線程優(yōu)先級瀏覽器會給 Worker 分配較低的線程優(yōu)先級,若需 “近實時” 處理(如游戲幀計算),可能出現(xiàn)延遲。此時可考慮 requestIdleCallback 結(jié)合 Worker,利用主線程空閑時間處理。
到此這篇關(guān)于JavaScript瀏覽器內(nèi)置的四大Web Worker的使用詳解的文章就介紹到這了,更多相關(guān)JavaScript多線程Web Worker內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
BootStrap 智能表單實戰(zhàn)系列(十)自動完成組件的支持
這篇文章主要介紹了BootStrap 智能表單實戰(zhàn)系列(十)自動完成組件的支持 的相關(guān)資料,需要的朋友可以參考下2016-06-06
js實現(xiàn)iframe自動自適應(yīng)高度的方法
這篇文章主要介紹了js實現(xiàn)iframe自動自適應(yīng)高度的方法,涉及javascript操作iframe框架的技巧,非常具有實用價值,需要的朋友可以參考下2015-02-02
JavaScript 瀏覽器對象模型BOM原理與常見用法實例分析
這篇文章主要介紹了JavaScript 瀏覽器對象模型BOM原理與常見用法,結(jié)合實例形式詳細分析了JavaScript瀏覽器對象模型BOM基本概念、原理、使用方法及操作注意事項,需要的朋友可以參考下2019-12-12
JavaScript數(shù)據(jù)類型及相互間的轉(zhuǎn)換規(guī)則
這篇文章主要介紹了JavaScript數(shù)據(jù)類型及相互間的轉(zhuǎn)換規(guī)則,文章通過圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下2022-09-09
js中將具有數(shù)字屬性名的對象轉(zhuǎn)換為數(shù)組
js中將具有數(shù)字屬性名的對象轉(zhuǎn)換為數(shù)組,雖然不太常用,但我們的確可以給對象添加以數(shù)字為屬性名的屬性2011-03-03
Fullpage.js固定導(dǎo)航欄-實現(xiàn)定位導(dǎo)航欄
FullPage.js 是一個簡單而易于使用的插件,用來創(chuàng)建全屏滾動網(wǎng)站(也被稱為單頁網(wǎng)站)。接下來通過本文給大家介紹Fullpage.js固定導(dǎo)航欄-實現(xiàn)定位導(dǎo)航欄,對fullpage.js導(dǎo)航欄相關(guān)知識感興趣的朋友一起學(xué)習(xí)吧2016-03-03

