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

基于JavaScript實現(xiàn)在線PDF拆分工具

 更新時間:2026年05月05日 08:55:04   作者:滕青山  
文章主要講述了一個PDF拆分工具的功能層實現(xiàn),包括PDF的讀取、拆分生成、頁面預(yù)覽等書簽讀取等核心功能的實現(xiàn)細(xì)節(jié);以及工具的主要特點和工作流程;并強(qiáng)調(diào)了統(tǒng)一的頁碼分組作為核心的設(shè)計,需要的朋友可以參考下

這篇只講本項目里“PDF拆分”工具的功能層 JavaScript 實現(xiàn)。主流程可以概括為:

選擇 PDF -> 讀取頁數(shù) -> 生成拆分頁組 -> 復(fù)制指定頁面 -> 生成多個 PDF -> 單文件下載或 ZIP 打包下載

工具基于 Vue 組織交互狀態(tài),核心 PDF 操作使用 pdf-lib,多文件結(jié)果打包使用 JSZip,頁面預(yù)覽和書簽讀取由 pdfjs-dist 輔助完成。

在線工具網(wǎng)址:https://see-tool.com/pdf-split
工具截圖:

1. 文件進(jìn)入流程前先做 PDF 判斷

文件選擇和拖拽上傳共用同一套入口。真正加載前,先判斷文件類型:

export function isPdfSplitFile(file) {
  if (!file) {
    return false;
  }

  var fileType = String(file.type || "").toLowerCase();
  var fileName = String(file.name || "");
  return fileType === "application/pdf" || /\.pdf$/i.test(fileName);
}

這里同時判斷 MIME 和文件后綴,是因為部分瀏覽器環(huán)境下 file.type 可能為空,只依賴 MIME 會誤攔正常 PDF。

加載文件時,會把同一份原始字節(jié)切成兩份用途:

var rawBytes = await file.arrayBuffer();
var splitBytes = rawBytes.slice(0);
var previewBytes = rawBytes.slice(0);
var sourceDoc = await PDFDocument.load(splitBytes);

splitBytes 用于后續(xù)拆分,previewBytes 用于預(yù)覽和書簽讀取。這樣拆分主鏈路和輔助信息鏈路互不影響。

2. 頁碼輸入解析成統(tǒng)一的拆分頁組

拆分邏輯不是直接處理輸入框字符串,而是先轉(zhuǎn)成統(tǒng)一結(jié)構(gòu):

{
  label: "1-3",
  indices: [0, 1, 2]
}

label 用于文件命名,indicespdf-lib 需要的零基頁碼數(shù)組。

頁碼范圍解析支持逗號分隔,也支持倒序區(qū)間:

function buildPageIndices(start, end) {
  var indices = [];
  var page;

  if (start <= end) {
    for (page = start; page <= end; page += 1) {
      indices.push(page - 1);
    }
    return indices;
  }

  for (page = start; page >= end; page -= 1) {
    indices.push(page - 1);
  }

  return indices;
}

所以用戶輸入 1-3,5,8-6 時,會生成三個輸出段:第 1 到 3 頁、第 5 頁、第 8 到 6 頁。

3. 多種拆分模式最終都?xì)w一到 groups

工具支持按頁碼范圍、每 N 頁、每頁單獨、奇偶頁、可視化選擇、書簽、平均拆成 N 份。雖然入口不同,但最終都會變成 groups

buildSplitGroups: function () {
  if (this.splitMode === "ranges") {
    return parsePdfSplitRangeGroups(this.rangeInput, this.totalPages);
  }

  if (this.splitMode === "everyN") {
    return buildPdfSplitCountGroups(
      this.totalPages,
      parsePdfSplitPositiveInt(this.everyNInput),
    );
  }

  if (this.splitMode === "everyPage") {
    return buildPdfSplitEveryPageGroups(this.totalPages);
  }

  if (this.splitMode === "evenOdd") {
    return buildPdfSplitEvenOddGroups(this.totalPages, this.evenOddMode);
  }

  if (this.splitMode === "visual") {
    return buildPdfSplitVisualGroups(this.selectedPages);
  }

  if (this.splitMode === "bookmarks") {
    return buildPdfSplitBookmarkGroups(this.bookmarkItems, this.totalPages);
  }

  if (this.splitMode === "nTimes") {
    return buildPdfSplitNPartsGroups(
      this.totalPages,
      parsePdfSplitPositiveInt(this.nTimesInput),
    );
  }

  return [];
}

這個設(shè)計的好處是,真正拆分 PDF 時不關(guān)心用戶選擇了哪種模式,只消費統(tǒng)一的頁碼分組。

4. 可視化選擇會自動合并連續(xù)頁

可視化模式下,用戶點選的是離散頁碼。工具會先排序、去重,再把連續(xù)頁合并成一個輸出段:

export function buildPdfSplitVisualGroups(selectedPages) {
  var uniquePages = Array.isArray(selectedPages)
    ? selectedPages
        .map(function (page) {
          return Number(page);
        })
        .filter(function (page) {
          return Number.isInteger(page) && page > 0;
        })
        .sort(function (left, right) {
          return left - right;
        })
        .filter(function (page, index, source) {
          return index === 0 || page !== source[index - 1];
        })
    : [];

  if (!uniquePages.length) {
    throw createPdfSplitInputError("emptySelection");
  }

  var groups = [];
  var start = uniquePages[0];
  var end = uniquePages[0];

  for (var i = 1; i < uniquePages.length; i += 1) {
    if (uniquePages[i] === end + 1) {
      end = uniquePages[i];
      continue;
    }

    pushMergedSelectionGroup(groups, start, end);
    start = uniquePages[i];
    end = uniquePages[i];
  }

  pushMergedSelectionGroup(groups, start, end);
  return groups;
}

比如選擇 1、2、3、7、9、10,結(jié)果會拆成 1-37、9-10 三個文件。

5. 書簽拆分按頂層書簽生成區(qū)間

書簽?zāi)J较茸x取 PDF 的 outline,再把書簽所在頁轉(zhuǎn)換成拆分區(qū)間。核心邏輯是:當(dāng)前書簽頁作為開始頁,下一個書簽前一頁作為結(jié)束頁。

export function buildPdfSplitBookmarkGroups(bookmarks, totalPages) {
  var normalizedBookmarks = Array.isArray(bookmarks)
    ? bookmarks
        .filter(function (item) {
          return (
            item &&
            Number.isInteger(Number(item.pageNumber)) &&
            Number(item.pageNumber) >= 1 &&
            Number(item.pageNumber) <= totalPages
          );
        })
        .map(function (item) {
          return {
            title: String(item.title || "").trim() || "bookmark",
            pageNumber: Number(item.pageNumber),
          };
        })
        .sort(function (left, right) {
          return left.pageNumber - right.pageNumber;
        })
    : [];

  var groups = [];

  if (normalizedBookmarks[0].pageNumber > 1) {
    groups.push({
      label: "preface",
      indices: buildPageIndices(1, normalizedBookmarks[0].pageNumber - 1),
      title: "preface",
    });
  }

  for (var index = 0; index < normalizedBookmarks.length; index += 1) {
    var current = normalizedBookmarks[index];
    var next = normalizedBookmarks[index + 1];
    var start = current.pageNumber;
    var end = next ? next.pageNumber - 1 : totalPages;

    groups.push({
      label: current.title,
      indices: buildPageIndices(start, end),
      title: current.title,
    });
  }

  return groups;
}

如果第一個書簽不在第一頁,前面的內(nèi)容會單獨生成一個 preface 分段。

6. 真正拆分 PDF 的核心是 copyPages

拆分主函數(shù)先構(gòu)建 groups,然后每個分組創(chuàng)建一個新的 PDF:

for (index = 0; index < groups.length; index += 1) {
  var group = groups[index];
  var outputDoc = await PDFDocument.create();
  var copiedPages = await outputDoc.copyPages(
    this.sourceDoc,
    group.indices,
  );

  copiedPages.forEach(function (page) {
    outputDoc.addPage(page);
  });

  var outputBytes = await outputDoc.save();
  var outputBlob = new Blob([outputBytes], {
    type: "application/pdf",
  });

  nextOutputs.push({
    name: this.buildOutputName(group, index, groups.length),
    blob: outputBlob,
    size: outputBlob.size,
  });
}

這里不是修改原 PDF,也不是切割二進(jìn)制文件,而是把源文檔里的指定頁面復(fù)制到一個新文檔。group.indices 決定當(dāng)前輸出文件包含哪些頁。

7. 輸出文件名根據(jù)拆分模式生成

文件名會先清理原 PDF 名稱,再結(jié)合模式和頁碼標(biāo)簽生成:

export function buildPdfSplitOutputName(options) {
  var config = options || {};
  var baseName = safePdfSplitBaseName(config.baseName);
  var index = Number(config.index) || 0;
  var total = Number(config.total) || 0;
  var label = String(config.label || "");
  var mode = String(config.mode || "ranges");
  var sequence = String(index + 1).padStart(3, "0");
  var safeLabel = sanitizePdfSplitFileLabel(label) || sequence;

  if (mode === "everyPage") {
    return baseName + "_page_" + safeLabel + ".pdf";
  }

  if (mode === "bookmarks") {
    return baseName + "_" + sequence + "_" + safeLabel + ".pdf";
  }

  if (total === 1) {
    return baseName + "_split.pdf";
  }

  return baseName + "_split_" + sequence + "_p" + safeLabel + ".pdf";
}

這樣拆出多個文件時,用戶能從文件名看出順序和頁碼范圍。

8. 單結(jié)果直接下載,多結(jié)果打包 ZIP

導(dǎo)出時先判斷結(jié)果數(shù)量。只有一個 PDF 時直接下載;多個 PDF 時放進(jìn) ZIP:

downloadResult: async function () {
  if (!this.outputs.length) {
    return;
  }

  if (this.outputs.length === 1) {
    this.downloadOutput(this.outputs[0]);
    return;
  }

  var zip = new JSZip();
  this.outputs.forEach(function (item) {
    zip.file(item.name, item.blob);
  });

  var zipBlob = await zip.generateAsync({
    type: "blob",
    compression: "DEFLATE",
    compressionOptions: {
      level: 6,
    },
  });

  this.downloadBlob(zipBlob, "split_result.zip");
}

瀏覽器下載統(tǒng)一通過 Blob 和臨時 a 標(biāo)簽完成:

downloadBlob: function (blob, filename) {
  var url = URL.createObjectURL(blob);
  var link = document.createElement("a");

  link.href = url;
  link.download = filename;
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
  URL.revokeObjectURL(url);
}

整個 PDF 拆分功能的核心,就是把不同輸入方式都轉(zhuǎn)換成穩(wěn)定的頁碼分組,再用 pdf-lib 復(fù)制頁面生成新文檔,最后根據(jù)結(jié)果數(shù)量決定直接下載還是打包下載。

以上就是基于JavaScript實現(xiàn)在線PDF拆分工具的詳細(xì)內(nèi)容,更多關(guān)于JavaScript在線PDF拆分工具的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 基于JS代碼實現(xiàn)實時顯示系統(tǒng)時間

    基于JS代碼實現(xiàn)實時顯示系統(tǒng)時間

    這篇文章主要介紹了基于JS代碼實現(xiàn)實時顯示系統(tǒng)時間的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下
    2016-06-06
  • javascript控制層顯示或隱藏的方法

    javascript控制層顯示或隱藏的方法

    這篇文章主要介紹了javascript控制層顯示或隱藏的方法,涉及javascript操作頁面元素樣式的相關(guān)技巧,非常簡單實用,需要的朋友可以參考下
    2015-07-07
  • JWT+Passport.js身份驗證實現(xiàn)完整步驟

    JWT+Passport.js身份驗證實現(xiàn)完整步驟

    Passport.js是一個輕量級的身份驗證中間件,廣泛用于Node.js應(yīng)用程序中,它簡化了用戶認(rèn)證過程,支持多種策略,這篇文章主要介紹了JWT+Passport.js身份驗證實現(xiàn)的相關(guān)資料,需要的朋友可以參考下
    2025-09-09
  • Javascript函數(shù)之函數(shù)的作用域是什么詳解

    Javascript函數(shù)之函數(shù)的作用域是什么詳解

    函數(shù)和作用域是JavaScript的重要組成部分,我們在使用JavaScript編寫程序的過程中經(jīng)常要用到這兩部分內(nèi)容,這篇文章主要介紹了Javascript函數(shù)之函數(shù)的作用域是什么的相關(guān)資料,需要的朋友可以參考下
    2025-11-11
  • ES6 迭代器與可迭代對象的實現(xiàn)

    ES6 迭代器與可迭代對象的實現(xiàn)

    這篇文章主要介紹了ES6 迭代器與可迭代對象的實現(xiàn),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-02-02
  • JSON簡介以及用法匯總

    JSON簡介以及用法匯總

    JSON(JavaScript Object Notation)即JavaScript對象表示法,是一種輕量級的數(shù)據(jù)交換格式。它非常便于編程人員對數(shù)據(jù)的處理,也便于機(jī)器對數(shù)據(jù)的解析和生成,應(yīng)用非常廣泛
    2016-02-02
  • 基于javascript實現(xiàn)動態(tài)顯示當(dāng)前系統(tǒng)時間

    基于javascript實現(xiàn)動態(tài)顯示當(dāng)前系統(tǒng)時間

    這篇文章主要介紹了基于javascript實現(xiàn)動態(tài)顯示當(dāng)前系統(tǒng)時間,以一個完整實例形式較為詳細(xì)的分析了js動態(tài)顯示當(dāng)前系統(tǒng)時間的實現(xiàn)技巧,需要的朋友可以參考下
    2016-01-01
  • 使用Docker搭建Jackett的詳細(xì)教程

    使用Docker搭建Jackett的詳細(xì)教程

    Jackett是一個開源的種子搜索器,它提供了一個統(tǒng)一的API,可以讓你通過一個界面來搜索和下載種子文件,這篇文章主要給大家介紹了關(guān)于使用Docker搭建Jackett的相關(guān)資料,需要的朋友可以參考下
    2024-04-04
  • 詳解CocosCreator項目結(jié)構(gòu)機(jī)制

    詳解CocosCreator項目結(jié)構(gòu)機(jī)制

    這篇文章主要介紹了詳解CocosCreator項目結(jié)構(gòu)機(jī)制,只有了解這些機(jī)制后,才能更好的進(jìn)行項目開發(fā),避免潛在錯誤,并且快速的除錯
    2021-04-04
  • javascript實現(xiàn)的仿51job地址多項選擇方式效果

    javascript實現(xiàn)的仿51job地址多項選擇方式效果

    分享一個類似51job方式的地址選擇效果
    2009-12-12

最新評論

寻乌县| 习水县| 大同市| 双桥区| 芜湖市| 元朗区| 汶川县| 武鸣县| 淮滨县| 临泽县| 永济市| 莱州市| 塘沽区| 新密市| 永吉县| 扬州市| 南召县| 墨江| 江源县| 安康市| 茶陵县| 来安县| 普格县| 屏边| 珲春市| 宁国市| 庄河市| 望江县| 沐川县| 江口县| 色达县| 贵定县| 故城县| 旅游| 于田县| 体育| 大港区| 蓬溪县| 进贤县| 香河县| 色达县|