跳到内容
  • 福昕首页
  • 开发中心
  • SDK文档资料
  • 插件商店
  • 福昕首页
  • 开发中心
  • SDK文档资料
  • 插件商店
申请试用
  • 企业自动化
    • Compressor
  • 福昕CloudAPI
  • 福昕PDF SDK 软件开发工具包
    • 福昕PDF SDK(ActiveX)
    • 福昕PDF SDK(桌面/服务器)
    • 福昕PDF SDK(Plug-in)
    • 福昕 PDF SDK(安卓)
    • 福昕PDF SDK(iOS)
    • 福昕PDF SDK(Web)
  • 福昕管理控制台
    • 公有云
    • 私有云
    • 通用情况
  • 福昕阅读器
    • RMS插件
  • 福昕高级编辑器
    • AI助手
    • Mac版本
      • 常规问题
    • Windows版本
      • 表单
      • ECM集成
      • 互联PDF
      • 企业管理指南
      • 保护
      • 内容编辑
      • 创建PDF
      • 压缩
      • 图章
      • 安装与卸载
      • 常见问题
      • 打印
      • 注释/评论
      • 福昕插件
      • 翻译助手
      • 翻译助手教程
      • 试用与激活
      • 转换
      • 页面管理
    • 教育用户
      • 论文查重
      • 论文畅
      • 操作指南
    • 网页版
      • Foxit eSign
      • 电子签章
    • 订阅
    • 资源
  • 福昕高级编辑器Linux版本
  • 福船图纸管理系统
  • 福昕PDF SDK 软件开发工具包 > 福昕PDF SDK(Web)
  • 标签:
  • 按页渲染,章节授权阅读;

指定 PDF 页面加载范围(按页控制渲染,支持运行时动态切换)

  • 福昕知识库
  • 2026-09-08

适用产品:Foxit PDF SDK for Web(WebPDF Viewer / PDFViewCtrl / UIExtension)
验证版本:11.1.0(文中接口在 8.x/9.x 文档线均已有对应写法,跨版本使用请以所用版本官方文档为准)
适用场景:在线教育/课件展示、章节授权阅读、审阅分工、按章节下发内容等”只允许查看指定页码区间”的场景

一、需求与结论

需求:PDF 全文共 N 页,希望初始化时仅展示/渲染指定页码区间(如第 1-10 页),并且区间可以在运行时动态修改(如讲师授课推进到第 3 章,把展示区间切到第 21-30 页;未授权页显示占位提示)。

结论:Web SDK 没有”指定逻辑页码加载范围”的打开参数(不存在 loadPageRange:[1,10] 之类的选项),但可以通过 customs.PageCustomRender 自定义渲染钩子 实现按页控制”是否渲染”,配合一个闭包持有的可变页码集合实现运行时动态切换。该方案经 WebSDK 11.1 真机自动化验证:初始化限制、三段区间动态切换、区间回切恢复、50 次连续切换压测(0 失败)、跳页+放行联动全部通过。

同时,SDK 原生的按需加载机制(openPDFByHttpRangeRequest + HTTP Range 分块下载)天然只下载当前可见页所需的字节,与本方案叠加使用可以在”展示范围受限”的同时获得”传输量受限”的效果——但要注意:Range 按需加载只是传输优化,内存中最终仍可解析全文,不能作为安全边界。真正的”禁止查看”必须落在渲染控制(本文方案)或服务端文档切分上。

二、核心原理

PDFViewer 构造参数的 customs.PageCustomRender 是一个工厂函数,返回的类会被 SDK 对每个页面实例化一次:

  • 构造参数:eCustom(页面自定义容器 DOM 节点,即 .fv__pdf-custom-page-container)、pdfPageRender(PDFPageRender 实例);
  • render():每页进入渲染流程时被调用。返回 false 则 SDK 跳过该页内容渲染(页面只剩容器框架,无内容 canvas);返回 true/undefined/Promise 正常渲染;
  • destroy():页面被回收/文档关闭时调用,用于清理。

于是”限制可看页码区间”就变成:在 render() 里查询当前页码,不在集合内就返回 false(可以顺手写入占位提示 DOM)。而”运行时动态切换”则是:把允许渲染的页码集合放在工厂闭包里(实例化时无法更换工厂,但闭包变量可变),切换区间时更新集合并触发重渲染。

名称辨析:customs.PDFPageRendering(覆盖类,示例目录 /examples/PDFViewCtrl/override-rendering)是”加载占位 UI”钩子(rendering/rendered/destroy 生命周期),没有”返回 false 跳过渲染”的能力,不要与 PageCustomRender(示例目录 /examples/PDFViewCtrl/load-before-rendering)混淆。

三、完整实现代码(可直接落地)

// ========== 1. 闭包持有"当前允许渲染的页码集合"(0 基页码) ==========
var allowedPages = new Set();
function fillRange(from, to) {            // 1 基闭区间,如 (1, 10) 表示第 1~10 页
    allowedPages.clear();
    for (var i = from - 1; i <= to - 1; i++) allowedPages.add(i);
}
fillRange(1, 10);                          // 初始:仅第 1-10 页

// 曾经进入过允许区间的页码(用于清扫残留内容,见第 4 节要点 3)
var everAllowed = new Set();
function markEverAllowed() { allowedPages.forEach(function (i) { everAllowed.add(i); }); }

var pdfViewer = new PDFViewCtrl.PDFViewer({
    libPath: './lib',
    jr: { licenseSN: licenseSN, licenseKey: licenseKey },
    customs: {
        PageCustomRender: (function () {
            function CustomPageCustomRender(eCustom, pdfPageRender) {
                this.eCustom = eCustom;
                this.pdfPageRender = pdfPageRender;
            }
            CustomPageCustomRender.prototype.render = function () {
                var self = this;
                return self.pdfPageRender.getPDFPage().then(function (page) {
                    var idx = page.getIndex();                     // 0 基页码
                    if (!allowedPages.has(idx)) {
                        // 不在区间内:显示占位提示并跳过该页内容渲染
                        self.eCustom.innerHTML =
                            '<div class="locked-page">第 ' + (idx + 1) + ' 页不在当前展示区间内</div>';
                        return false;                              // ★ 核心:跳过渲染
                    }
                    // 在区间内:清掉历史占位,交还 SDK 正常渲染
                    if (self.eCustom.innerHTML !== '') self.eCustom.innerHTML = '';
                });
            };
            CustomPageCustomRender.prototype.destroy = function () {
                this.eCustom.innerHTML = '';
            };
            return CustomPageCustomRender;
        })(),

        // ★ 关键点 1:滚动宿主 = #pdf-viewer 的父元素(见第 4 节要点 1)
        ScrollWrap: PDFViewCtrl.CustomScrollWrap
    }
});
pdfViewer.init('#pdf-viewer');

// ========== 2. 打开文档(HTTP Range 按需加载,可选但推荐) ==========
pdfViewer.openPDFByHttpRangeRequest(
    { range: { url: '/docs/course.pdf' } },     // 服务器需支持 Range/206
    { fileName: 'course.pdf', isRenderOnDocLoaded: true }
);

// ========== 3. 运行时动态切换展示区间 ==========
// 清扫"曾允许、现已锁定"页面的残留内容层
function sweepLockedPages(total) {
    for (var i = 0; i < total; i++) {
        if (allowedPages.has(i) || !everAllowed.has(i)) continue;
        var lay = document.querySelector('#pdf-viewer .fv__pdf-page-layout[data-index="' + i + '"]');
        if (!lay) continue;
        var pc = lay.querySelector('.fv__pdf-page-container');
        if (pc && pc.childNodes.length) pc.innerHTML = '';          // 清残留 canvas
        var custom = lay.querySelector('.fv__pdf-custom-page-container');
        if (custom && !custom.querySelector('.locked-page')) {
            custom.innerHTML = '<div class="locked-page">第 ' + (i + 1) + ' 页不在当前展示区间内</div>';
        }
    }
}

function setVisibleRange(from, to) {       // 1 基闭区间
    markEverAllowed();                     // 记录旧区间
    fillRange(from, to);
    markEverAllowed();                     // 记录新区间
    sweepLockedPages(/* 文档总页数 */);

    var doc = pdfViewer.getPDFDocRender(); // 注意:PDFViewCtrl 下同步返回,不是 Promise
    doc.goToPage(from - 1);

    // ★ 关键点 2:对新区间逐页显式强制重渲染(见第 4 节要点 2)
    var seq = Promise.resolve();
    for (var i = from; i <= to; i++) {
        (function (idx0) {
            seq = seq.then(function () {
                var pr = doc.getPDFPageRender(idx0);
                return pr ? pr.render().catch(function () {}) : Promise.resolve();
            });
        })(i - 1);
    }
    return seq.then(function () {
        return doc.goToPage(from - 1);     // 回到区间首页
    });
}

// 讲师授课到第 3 章(第 21-30 页):
setVisibleRange(21, 30);

页面骨架(注意滚动容器关系,与官方 load-before-rendering 示例一致):

<style>
  /* 滚动容器是 #pdf-viewer 的父元素;配合 CustomScrollWrap 使用 */
  #viewer-scroll { width: 100%; height: 500px; overflow: auto; }
  .locked-page { display:flex; align-items:center; justify-content:center;
                 height:100%; color:#9ca3af; font-size:15px; }
</style>
<div id="viewer-scroll"><div id="pdf-viewer"></div></div>

四、四个必须注意的关键点(踩坑实录)

要点 1:自行包裹滚动容器时,必须传 customs.ScrollWrap = PDFViewCtrl.CustomScrollWrap

缺省的 ScrollWrap 监听的是 window/document 滚动。如果采用”外层 div(overflow:auto)包住 #pdf-viewer“的常见布局而不传此项,外层容器滚动时 SDK 完全感知不到,除首屏页外其它页面永远不会触发渲染(表现为大片空白页)。PDFViewCtrl.CustomScrollWrap 会把滚动宿主设为 #pdf-viewer 的父元素,与官方示例一致。(UIExtension 全功能模式下使用其内置 UI 布局时无此问题。)

要点 2:曾被锁定(render 返回 false)的页,重新放行后必须逐页显式调用 PDFPageRender.render()

页面一旦因 PageCustomRender.render() 返回 false 被跳过,之后 goToPage、redraw(true) 等常规渲染链路不会重新调用该页的 PageCustomRender.render()(实测逐页 goToPage 无法恢复)。必须对新区间内每一页显式调用 doc.getPDFPageRender(idx).render() 强制重渲染(见上文代码)。否则会出现”切回旧区间后页面不恢复”的问题。

要点 3:锁定前已渲染过的页面,切换区间后要清扫残留内容

被锁定页在锁定前若已渲染出内容,切区间后其 canvas 仍会残留在 .fv__pdf-page-container 中。需要维护 everAllowed(曾经允许过的页)集合,切换后对”曾允许、现已锁定”的页面清除 canvas 并补占位提示。注意不要把新区间内”尚未轮到渲染”的页误判为锁定页(这也是需要 everAllowed 而不是直接全量清扫的原因)。

要点 4:这是”渲染层”控制,不等于内容安全

  • 该方案只控制页面内容是否渲染,文档逻辑上所有页都存在:getCurrentPageIndex、缩略图、书签、文本搜索等仍能感知全部页面。若要给用户”完全不可见”的体验,需再屏蔽缩略图/书签/导航/搜索入口。
  • 若业务要求严格不可见 + 防下载(如仅授权章节),推荐服务端方案:用福昕 GSDK 预先把原 PDF 按章节切分为子 PDF,Web 端按需打开对应子文档;或对 PDF 加密并按权限下发。渲染层控制适合”引导视线/分步展示”类场景,不适合作为唯一的安全手段。
  • openPDFByHttpRangeRequest 的按需下载只优化传输(chunkSize 默认 128KB,按 Range 分块取),不能防止用户拿到完整文件——服务端仍需鉴权控制文件访问。

五、可用 API 速查(WebSDK 11.1 验证)

API说明
customs.PageCustomRender工厂函数返回类;render() 返回 false 跳过该页渲染;构造参数 eCustom/pdfPageRender
customs.ScrollWrap: PDFViewCtrl.CustomScrollWrap滚动宿主改为 #pdf-viewer 父元素;自定滚动布局必传
pdfPageRender.getPDFPage() → page.getIndex()在 render() 中取当前页 0 基页码
pdfViewer.getPDFDocRender()取 PDFDocRender(PDFViewCtrl 下同步返回,非 Promise)
doc.goToPage(index) / doc.getCurrentPageIndex()跳页 / 读当前页(0 基)
doc.getPDFPageRender(idx).render()对指定页强制重渲染(放行已锁定页的关键调用;非文档化 API,升级版本需回归验证)
pdfViewer.redraw(force)重绘当前可见页(force=true 同时刷新缓存)
openPDFByHttpRangeRequest({range:{url}}, {fileName, isRenderOnDocLoaded})HTTP Range 按需加载打开远程文档;服务器需支持 Range/206
ViewerEvents.renderPageSuccess / beforeRenderPage / renderFileSuccess渲染成功/渲染前/文档渲染完成事件(viewer.eventEmitter.on)

注意:Web 端没有 Android/iOS 那种 onPageChanged(old, cur) 页面变化事件;感知翻页可用 renderPageSuccess 事件 + getCurrentPageIndex() 组合,或在跳页动作处主动读取。

六、场景落地建议

需求推荐做法
初始化只展示第 1-10 页初始 fillRange(1,10) + PageCustomRender,打开后视口内只渲染区间内页
授课进度切换章节(如跳第 21-30 页)setVisibleRange(21, 30):更新集合 + 清扫 + 逐页 PDFPageRender.render() + goToPage 回首页
监听翻页进度/同步 UIrenderPageSuccess 事件里读 getCurrentPageIndex() 更新进度条
锁定页占位体验在 render() 中向 eCustom 写占位 DOM,比留白友好
严格不可见 + 防下载服务端 GSDK 切分子 PDF / 文档加密+权限下发;渲染层控制仅作辅助
大文件传输优化openPDFByHttpRangeRequest(Range 分块,chunkSize 默认 128KB),服务器配好 Accept-Ranges/206

七、版本与兼容性说明

  • 文中实现基于 WebSDK 11.1.0 的 PDFViewCtrl 模块验证;PageCustomRender 在 8.1 Developer Guide 与官方示例(load-before-rendering)中已存在,属稳定能力,但 PDFPageRender.render() 为非文档化 API(11.1 API 参考的 PDFPageRender 类页面未列出),升级 SDK 版本时请对本功能做回归验证。
  • UIExtension(全功能 UI)模式:完整用法见下方「UIExtension(PDFUI)模式的差异」,同样验证通过(6 项全过,压测 50 次 0 失败)。
  • 完整可运行验证工程(含自动化 6 项验证脚本)见内部档案 IS-006-web-pagerange-customrender。

八、UIExtension(PDFUI)模式的差异与完整示例

UIExtension 全功能 UI 模式下,方案同样适用且验证通过(WebSDK 11.1.0 / index-pdfui.html / run-repro-pdfui.mjs:初始化限 1-10 页、三段区间切换、区间回切恢复、50 次连续切换 0 失败、跳页+放行联动 全部 ✅)。与裸 PDFViewer 模式有以下关键差异:

差异点裸 PDFViewerUIExtension.PDFUI
customs 传入层级new PDFViewer({ customs: {...} })new PDFUI({ viewerOptions: { customs: {...} } })
ScrollWrap必须显式传 PDFViewCtrl.CustomScrollWrap(自定滚动布局时需要)不要传 —— PDFUI 的 constructPDFViewer 会自动用 UI 容器创建滚动宿主(lib 取证:i.ScrollWrap||(i.ScrollWrap=Gx.Z.create(this.wrapperElement))),自带内置滚动布局
打开文档pdfViewer.openPDFByHttpRangeRequest(...)pdfui.openPDFByHttpRangeRequest(...)(直接在 PDFUI 实例上调用,内部转发给 viewer)
取 PDFViewer / PDFDocRenderpdfViewer.getPDFDocRender()(同步)await pdfui.getPDFViewer()(Promise)→ viewer.getPDFDocRender()(同步)
事件订阅pdfViewer.eventEmitter.on(ViewerEvents.xxx, fn)同(拿到 viewer 实例后仍用 viewer.eventEmitter),另有 pdfui.addViewerEventListener(type, fn) 便捷接口
加载文件lib/PDFViewCtrl.full.js + PDFViewCtrl.csslib/UIExtension.full.js + UIExtension.css,须加载 uix-addons/allInOne.js(或 mobile 版)addons

PageCustomRender 钩子本身、render() 返回 false 跳页机制、放行后逐页 PDFPageRender.render() 恢复、everAllowed 清扫逻辑,在两种模式下完全一致(UIExtension lib 中同样是 new(customs.PageCustomRender||默认)(.fv__pdf-custom-page-container, 页渲染器) 实例化)。PDFUI 模式初始化代码:

var pdfui = new UIExtension.PDFUI({
    viewerOptions: {
        libPath: './lib',
        jr: { licenseSN: licenseSN, licenseKey: licenseKey },
        customs: {
            PageCustomRender: (function () {
                function CustomPageCustomRender(eCustom, pdfPageRender) {
                    this.eCustom = eCustom;
                    this.pdfPageRender = pdfPageRender;
                }
                CustomPageCustomRender.prototype.render = function () {
                    return this.pdfPageRender.getPDFPage().then(function (page) {
                        var idx = page.getIndex();
                        if (!allowedPages.has(idx)) {        // allowedPages 同为闭包可变集合
                            this.eCustom.innerHTML = '<div class="locked-page">第 ' + (idx + 1) + ' 页不在当前展示区间内</div>';
                            return false;
                        }
                    });
                };
                CustomPageCustomRender.prototype.destroy = function () { this.eCustom.innerHTML = ''; };
                return CustomPageCustomRender;
            })()
            // 注意:不传 ScrollWrap,PDFUI 会自动创建
        }
    },
    renderTo: '#pdf-ui',
    appearance: UIExtension.appearances.adaptive,
    fragments: [],
    addons: UIExtension.PDFViewCtrl.DeviceInfo.isMobile ?
        './lib/uix-addons/allInOne.mobile.js' : './lib/uix-addons/allInOne.js'
});

// 打开文档:pdfui.openPDFByHttpRangeRequest(...)(或 openPDFByFile)
// 动态切换:await pdfui.getPDFViewer() → viewer.getPDFDocRender() → 与裸 PDFViewer 相同

本文基于 Foxit PDF SDK for Web 11.1.0 实测验证编写(2026-09)。示例中的 licenseSN/licenseKey 请替换为你自己的授权。

相关内容

Websdk删除注释侧边栏的部分二级弹框

如何在PDF表格中增加单选题?

PDF调了书签顺序,页面也能跟着重排?福昕教你一键同步!

PDF流式编辑,改文字自动重排版

PPT转PDF后如何改文字?不用重做PPT也能改!

多篇文献批量搜关键词,一键高亮导出,效率拉满!

使用工具去除PDF水印是否合法?

Linux版本中如何实现OFD与PDF格式互转

Linux版本中如何自动创建书签(需要2025.1版本)

Linux版本中如何实现在文档中进行手写签名

推荐内容

Websdk删除注释侧边栏的部分二级弹框

如何在PDF表格中增加单选题?

PDF调了书签顺序,页面也能跟着重排?福昕教你一键同步!

指定 PDF 页面加载范围(按页控制渲染,支持运行时动态切换)

PDF流式编辑,改文字自动重排版

PPT转PDF后如何改文字?不用重做PPT也能改!

多篇文献批量搜关键词,一键高亮导出,效率拉满!

使用工具去除PDF水印是否合法?

Linux版本中如何实现OFD与PDF格式互转

Linux版本中如何自动创建书签(需要2025.1版本)

产品
  • 应用行业
  • 白皮书
开发支持
  • 开发中心
  • SDK文档资料

销售咨询:010-50951668

客服电话:0591-38509808

销售咨询
微信公众号

©2026 福建福昕软件开发股份有限公司 版权所有

隐私策略