导读:本期聚焦于小宵创作的《如何使用jQuery封装Presentation API实现网页内容投屏到第二显示屏?》,敬请观看详情。把网页中的图表、视频或监控面板投到外接大屏,通常需要切换浏览器窗口或依赖第三方协议。浏览器原生 Presentation API 提供了第二屏呈现能力,配合 jQuery 可以封装出轻量投屏组件。本文从 PresentationRequest 的创建、可用性检测到连接建立,梳理完整封装思路,并演示主控制页与投影页之间如何通过 PresentationConnection 双向收发消息。文章给出可运行的 jQuery 插件代码,说明消息序列化、连接状态监听、页面关闭清理等细节。无需安装额外软件,在 Chromium 内核浏览器中即可实现网页内容投屏到第二显示屏,适合数据看板、演示文稿等场景。

实现网页内容投屏到第二显示屏并不一定需要依赖 HDMI 采集或专用投屏协议。Chromium 内核浏览器内置的 Presentation API 允许页面发现外接屏幕并请求在该屏幕打开一个展示页面,配合主页面与展示页之间的消息通道,就能把数据、视频地址或交互状态同步过去。本文以 jQuery 插件的形式封装这一过程,让调用方通过几行代码完成连接、发送与关闭。

如何使用jQuery封装Presentation API实现网页内容投屏到第二显示屏?

Presentation API 的定位与浏览器支持

Presentation API 是 W3C 提出的第二屏呈现规范,核心接口包括 PresentationRequest、PresentationConnection、PresentationAvailability 以及接收端的 PresentationReceiver。它和屏幕共享不同:屏幕共享把当前页面镜像到另一个屏幕,而 Presentation API 会在第二屏打开一个独立 URL,这个 URL 与主页面之间建立双向消息通道。主页面可以只承担控制台角色,真正渲染内容的是第二屏页面。

目前该 API 在 Chromium 内核浏览器中支持较完整,例如 Chrome、Edge、Opera 的桌面版本。Firefox 和 Safari 尚未实现主要接口,因此在生产环境中需要做能力检测并给出降级提示。即使在同一浏览器中,部分接口也可能需要用户手势触发,例如 request.start() 通常必须放在点击事件回调里,否则会被浏览器拦截。

直接使用原生 API 涉及不少重复代码:创建请求、监听可用性、处理连接状态、序列化消息等。用 jQuery 封装可以把这些步骤收敛到插件中,业务代码只需传入接收页地址和处理回调即可。下面展示一个轻量插件的实现方式。

用 jQuery 封装投屏插件

插件的核心逻辑包括三步:能力检测与请求对象创建、启动投屏并保存连接、对外暴露发送和关闭方法。以下是一个基础封装:

(function($) {
  $.fn.presentation = function(options) {
    var settings = $.extend({
      receiverUrl: 'receiver.html',
      onConnect: null,
      onMessage: null,
      onClose: null
    }, options);

    var request = null;
    var connection = null;

    function initRequest() {
      var target = new URL(settings.receiverUrl, window.location.href).href;
      if (window.PresentationRequest) {
        request = new PresentationRequest([target]);
      } else if (navigator.presentation && navigator.presentation.defaultRequest) {
        request = navigator.presentation.defaultRequest;
      }
    }

    function bindConnection(conn) {
      connection = conn;
      conn.onconnect = function() {
        if (settings.onConnect) {
          settings.onConnect.apply(this, arguments);
        }
      };
      conn.onmessage = function(event) {
        var data = event.data;
        try {
          data = JSON.parse(event.data);
        } catch (e) {}
        if (settings.onMessage) {
          settings.onMessage(data, event);
        }
      };
      conn.onclose = function() {
        connection = null;
        if (settings.onClose) {
          settings.onClose.apply(this, arguments);
        }
      };
      conn.onterminate = function() {
        connection = null;
      };
    }

    function start() {
      initRequest();
      if (!request) {
        return $.Deferred().reject(new Error('Presentation API not supported')).promise();
      }
      return request.start().then(function(conn) {
        bindConnection(conn);
        return conn;
      });
    }

    function send(data) {
      if (!connection || connection.state !== 'connected') {
        return false;
      }
      var payload = typeof data === 'string' ? data : JSON.stringify(data);
      connection.send(payload);
      return true;
    }

    function close() {
      if (connection) {
        connection.close();
      }
    }

    return {
      start: start,
      send: send,
      close: close,
      getConnection: function() {
        return connection;
      },
      getAvailability: function() {
        if (!request) {
          initRequest();
        }
        if (request && request.getAvailability) {
          return request.getAvailability();
        }
        return $.Deferred().reject(new Error('getAvailability not available')).promise();
      }
    };
  };
})(jQuery);

调用时先为控制按钮绑定事件,在点击回调中启动投屏。这样既能满足用户手势要求,也能把连接对象保存下来方便后续发送消息。示例:

$('#present-btn').on('click', function() {
  var sender = $('#present-btn').presentation({
    receiverUrl: 'receiver.html',
    onConnect: function() {
      console.log('第二屏已连接');
    },
    onMessage: function(data) {
      console.log('收到消息', data);
      if (data && data.type === 'ready') {
        sender.send({ type: 'slide', index: 2 });
      }
    }
  });

  sender.start().then(function(conn) {
    console.log('连接状态', conn.state);
  }).catch(function(err) {
    alert('无法启动投屏:' + err.message);
  });
});

这里有一个容易忽略的问题:插件返回对象后,需要把该对象保存在闭包或全局变量中,否则后续调用 send 时可能拿不到同一个连接上下文。更稳妥的做法是把连接状态挂在按钮元素的数据对象上,或者把插件设计成单例状态。

投屏接收页的消息处理

第二屏打开的 receiver.html 需要通过 navigator.presentation.receiver 获取连接列表。接收端代码通常在页面加载后立即运行:

if (navigator.presentation && navigator.presentation.receiver) {
  navigator.presentation.receiver.connectionList.then(function(list) {
    list.connections.forEach(function(connection) {
      connection.onmessage = function(event) {
        var data = JSON.parse(event.data);
        if (data.type === 'slide') {
          renderSlide(data.index);
        }
      };

      connection.onclose = function() {
        document.body.classList.add('closed');
      };

      connection.send(JSON.stringify({ type: 'ready', ts: Date.now() }));
    });
  });
}

function renderSlide(index) {
  var items = document.querySelectorAll('.slide');
  items.forEach(function(item, i) {
    item.classList.toggle('active', i === index);
  });
}

接收页的 HTML 可以包含视频、图片、图表容器等元素。注意投屏页面通常运行在独立窗口,它不会自动继承主页面样式,需要引入自己的 CSS 和脚本。内容渲染可以直接操作 DOM,比如切换轮播项、替换 <video> 的 src、更新表格数据等。

消息协议建议统一为 JSON 对象,并用 type 字段区分操作类型。这样可以避免后续业务复杂后出现字符串解析混乱。主页面发送动作指令,接收页执行并回报状态;如果需要持续同步,也可以使用定时器批量发送,但要注意 PresentationConnection 的消息频率不宜过高,建议控制在每秒 10 条以内。

常见问题与调试建议

投屏失败的最常见原因是页面没有运行在安全上下文。Presentation API 要求 HTTPS 或 localhost 环境,普通 HTTP 页面在多数 Chromium 版本中无法调用 request.start()。另一个坑是相对路径:PresentationRequest 构造函数期望接收绝对 URL,如果传入 receiver.html 可能被解析为不支持的形式,因此插件内部用 new URL(...) 转成完整地址更稳妥。

用户手势是硬性要求。按钮点击、键盘事件、触摸事件都可以触发 start(),但异步回调中直接调用会被认为丢失手势上下文。遇到 NotAllowedError 时,可以检查是否在 setTimeout 或 Ajax 回调中启动了投屏。

调试第二屏时,可以在 Chrome 地址栏输入 chrome://media-engagement 查看媒体相关状态,但更实际的做法是让接收页保持 DevTools 打开。外接显示器上的窗口无法直接 F12 时,可以先在本地浏览器打开接收页并连接 DevTools,再利用 Presentation API 在测试环境启动。也可以将接收页做成支持 URL 参数的模式,在本机直接访问模拟连接。

jQueryPresentation API网页投屏修改时间:2026-10-04 20:38:35

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/1004/65706.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。